Skip to main content

Shared storage (RWX)

The cluster's default StorageClasses (csi-cinder-*) are all OVH Cinder block volumes — ReadWriteOnce only, so they can't be mounted by multiple pods. For workloads that need to share files between pods (e.g. uploads, generated assets, shared caches) the cluster runs an in-cluster NFS server that exposes a single ReadWriteMany StorageClass: nfs.

How it works

┌──────────────────────────────────────────────────────┐
│ namespace: nfs-server-provisioner │
│ │
│ StatefulSet nfs-server-provisioner-0 │
│ │ │
│ │ writes to │
│ ▼ │
│ PVC (csi-cinder-high-speed-gen2, 20Gi, RWO) ◀──┐ │
│ │ │
└──────────────────────────────────────────────────┼──┘

StorageClass `nfs` ──────────────────────────────► │
provisions one subdirectory per PVC inside that volume
  • One Cinder block volume backs the entire shared pool.
  • The NFS server pod exposes it as NFSv4.1.
  • Every app PVC with storageClassName: nfs becomes a subdirectory under that volume, mounted RWX by every pod that claims it.

Where it lives

  • ArgoCD app: k8s/app/system/nfs-server-provisioner/nfs-server-provisioner.app.yaml
  • Helm values: k8s/app/system/nfs-server-provisioner/helm/values.yaml
  • Chart: nfs-server-provisioner (kubernetes-sigs)

Using it from an app chart

Every app chart pulls in the component-shared-volume subchart. It's driven by a single list under global — empty by default, so nothing is rendered. Add entries in your project values file:

global:
sharedVolumes:
- name: shared
size: 2Gi
mounts:
- path: /var/app/var/cache
subPath: cache
- path: /var/app/public/cache
subPath: public-cache
- name: uploads
size: 20Gi
mounts:
- path: /var/app/public/uploads

This renders one PVC per entry (<nameOverride>-shared, <nameOverride>-uploads) on the nfs StorageClass, plus matching volumes: and volumeMounts: on the workload — including multiple subPath mounts of the same PVC where listed. All replicas see the same directories.

Practical guidance: /uploads, /cache, /public/cache

Two common shapes:

  1. One PVC, three mounts — when the three paths belong to one app and grow together (typical Symfony). Mount cache/, public-cache/, and uploads/ subdirectories of a single PVC. Cheaper and simpler; one capacity number to tune.
  2. Two PVCs — when one path is user-precious (uploads) and the others are rebuildable cache. Splitting lets you size/retain them differently and makes it safe to kubectl delete pvc the cache if it ever gets weird.

See component-shared-volume for a side-by-side comparison.

When to use it

Good fit:

  • User uploads / generated images that any replica may serve
  • Shared cache directories (e.g. Symfony var/cache when warm-up is expensive)
  • Cross-pod file handoffs between web and worker pods

Not a good fit:

  • Database storage — use Cinder block (csi-cinder-high-speed-gen2) via the database operator
  • High-throughput / latency-sensitive paths — NFS adds round-trips
  • Anything that must survive an NFS-server pod restart with zero blip — there's only one server pod

Operational notes

  • Single point of failure. nfs-server-provisioner is a single pod; a restart causes a short NFS reconnect for every consumer.
  • Pool capacity. All RWX PVCs live inside the one 20 GiB backing Cinder volume. Raise persistence.size in the chart values if you outgrow it (the PVC has allowVolumeExpansion: true).
  • Reclaim policy. The nfs StorageClass uses Delete — removing a project's PVC removes its data. Switch to Retain in the values file if a consumer's data is precious.