apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: nfs-photo namespace: nfs spec: interval: 30m chartRef: kind: OCIRepository name: nfs-photo driftDetection: mode: enabled values: controllers: app: strategy: Recreate podDisruptionBudget: maxUnavailable: "0" initContainers: fix-permissions: image: repository: hectorm/nfs-ganesha tag: v10 command: - sh - -c - chmod -R a+rwX /disks/base /disks/sub1 # native sidecar (restartPolicy: Always): mounts the two disks and # exposes their union at /disks/merged via FUSE. It must be up and # actually mounted *before* the "app" container is created, since # HostToContainer propagation only relays mounts made *after* the # receiving container's own mount exists - it won't retroactively # pick up a FUSE mount that's already there. Running it as a # restartPolicy:Always init container (started, gated on the # startupProbe below, before regular containers) guarantees that # ordering; a regular sidecar container racing "app" on every pod # start does not. mergerfs: dependsOn: - fix-permissions restartPolicy: Always image: repository: ghcr.io/hotio/mergerfs tag: release-v2.42.0 args: - "-o" - "allow_other,use_ino,func.create=ff,category.action=all,category.create=ff,category.search=ff" - "/disks/base:/disks/sub1" - "/disks/merged" securityContext: privileged: true probes: startup: enabled: true custom: true spec: exec: command: - sh - -c - grep -q fuse.mergerfs /proc/mounts periodSeconds: 1 timeoutSeconds: 2 failureThreshold: 30 resources: requests: cpu: 50m memory: 64Mi limits: memory: 512Mi # runs after mergerfs is confirmed mounted (see dependsOn) and has # /export, /disks/base and /disks/sub1 all mounted too, so the # /proc/self/mounts snapshot it takes covers every path ganesha # exports. ganesha's FSAL_VFS resolves each EXPORT's Path against # this frozen /etc/mtab, not the live /proc/mounts - any exported # path missing from it (e.g. this ran before mergerfs, or without # that path mounted here at all) fails at startup with # "resolve_posix_filesystem() ... No such file or directory" # even though the path is mounted fine in every other container. mtab-setup: dependsOn: - mergerfs image: repository: hectorm/nfs-ganesha tag: v10 command: - sh - -c - cat /proc/self/mounts > /mtab-vol/mtab containers: app: image: repository: hectorm/nfs-ganesha tag: v10 securityContext: readOnlyRootFilesystem: true capabilities: drop: - ALL add: - CHOWN - DAC_OVERRIDE - DAC_READ_SEARCH - FOWNER - FSETID - SETGID - SETUID - SYS_RESOURCE probes: liveness: enabled: true spec: initialDelaySeconds: 2 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 readiness: enabled: true spec: initialDelaySeconds: 1 periodSeconds: 2 timeoutSeconds: 1 failureThreshold: 2 resources: requests: cpu: 50m memory: 128Mi limits: memory: 512Mi service: app: controller: app # cluster-internal only. pinned (not DNS name): webdav's PV mounts # this over NFS via the kubelet's in-tree kernel NFS client, which # runs in the node's host network namespace and can't reliably # resolve/route Kubernetes Service DNS names - it needs a stable IP # it can reach directly. type: ClusterIP clusterIP: 10.43.166.34 ports: nfs: port: 2049 protocol: TCP persistence: config: type: configMap name: nfs-photo-config globalMounts: - path: /etc/ganesha/ganesha.conf subPath: ganesha.conf readOnly: true base: type: persistentVolumeClaim accessMode: ReadWriteOnce size: 100Gi storageClass: longhorn-single-replica retain: true advancedMounts: app: mergerfs: - path: /disks/base fix-permissions: - path: /disks/base # exported directly as its own NFS share (Pseudo=/base, see # configmap.yaml) alongside the merged /export view. app: - path: /disks/base mtab-setup: - path: /disks/base sub1: type: persistentVolumeClaim accessMode: ReadWriteOnce size: 100Gi storageClass: longhorn-single-replica retain: true advancedMounts: app: mergerfs: - path: /disks/sub1 fix-permissions: - path: /disks/sub1 # exported directly as its own NFS share (Pseudo=/sub1, see # configmap.yaml) alongside the merged /export view. app: - path: /disks/sub1 mtab-setup: - path: /disks/sub1 merged: type: emptyDir advancedMounts: app: mergerfs: - path: /disks/merged mountPropagation: Bidirectional app: - path: /export mountPropagation: HostToContainer mtab-setup: - path: /export mountPropagation: HostToContainer dev-fuse: type: hostPath hostPath: /dev/fuse advancedMounts: app: mergerfs: - path: /dev/fuse run: type: emptyDir globalMounts: - path: /run tmp: type: emptyDir globalMounts: - path: /tmp recovery: type: emptyDir globalMounts: - path: /recovery etc-mtab: type: emptyDir advancedMounts: app: app: - path: /etc/mtab subPath: mtab mtab-setup: - path: /mtab-vol