Una StorageClass le dice a Kubernetes cómo crear volúmenes persistentes bajo demanda: qué driver usar, con qué parámetros y qué hacer con los datos cuando se borra el volumen. Los drivers CSI (Container Storage Interface) son los plugins que hablan con el almacenamiento real, ya sea NFS, Ceph o el disco de un proveedor cloud. En este tutorial montarás un servidor NFS en Ubuntu 24.04, instalarás el driver oficial csi-driver-nfs en tu clúster y crearás una StorageClass que aprovisiona volúmenes ReadWriteMany automáticamente.

Requisitos previos

Para seguir esta guía necesitas:

  • Un clúster de Kubernetes 1.30 o superior (kubeadm, k3s o similar) sobre servidores Ubuntu 24.04, por ejemplo VPS de CubePath conectados por red privada.
  • kubectl configurado con permisos de administrador del clúster y Helm 3 instalado en tu equipo.
  • Un servidor adicional con Ubuntu 24.04 y un usuario no root con sudo, que hará de servidor NFS. Puede ser un nodo del clúster en un entorno de pruebas.
  • Conectividad entre los nodos y el servidor NFS por el puerto TCP 2049, idealmente por red privada.

Conceptos: qué define una StorageClass

Antes de instalar nada conviene saber qué campos vas a configurar:

CampoQué haceValores habituales
provisionerDriver CSI que crea los volúmenesnfs.csi.k8s.io, rbd.csi.ceph.com, rancher.io/local-path
parametersOpciones propias del driver (servidor, pool, tipo de disco)Dependen del driver
reclaimPolicyQué pasa con el volumen al borrar el PVCDelete (por defecto) o Retain
volumeBindingModeCuándo se crea el volumenImmediate o WaitForFirstConsumer
allowVolumeExpansionPermite ampliar un PVC editando su tamañotrue o false
mountOptionsOpciones de montaje que se aplican en el nodonfsvers=4.1, noatime

El flujo es siempre el mismo: una aplicación crea un PersistentVolumeClaim (PVC) que pide una StorageClass, el driver CSI crea el volumen en el backend y Kubernetes genera el PersistentVolume (PV) y lo enlaza al PVC.

Comprueba qué clases tiene ya tu clúster:

kubectl get storageclass

En un clúster k3s verás la clase local-path, marcada como predeterminada. En un clúster kubeadm recién creado la lista suele estar vacía:

NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  12d

local-path guarda los datos en el disco de un solo nodo, por lo que el volumen no sigue al pod si este cambia de nodo. NFS resuelve eso y además permite que varios pods escriban a la vez.

Paso 1: Preparar el servidor NFS

En el servidor NFS, instala el paquete del servidor:

sudo apt update
sudo apt install nfs-kernel-server

Crea el directorio que exportarás. El driver creará dentro un subdirectorio por cada volumen:

sudo mkdir -p /srv/nfs/k8s

Declara la exportación en /etc/exports. Sustituye 10.0.0.0/24 por el rango de tu red privada, para que solo los nodos del clúster puedan montarla:

sudo nano /etc/exports
/srv/nfs/k8s 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)

La opción no_root_squash es necesaria para que el driver, que se ejecuta como root, pueda crear los subdirectorios y ajustar sus permisos. Por eso es importante limitar la exportación a la red privada.

Aplica la configuración y comprueba que la exportación está activa:

sudo exportfs -ra
sudo exportfs -v
/srv/nfs/k8s  10.0.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)

Si usas UFW, permite NFS solo desde la red privada:

sudo ufw allow from 10.0.0.0/24 to any port 2049 proto tcp

Paso 2: Instalar el cliente NFS en los nodos

Cada nodo que vaya a ejecutar pods con volúmenes NFS necesita las utilidades de montaje. Ejecuta en todos los nodos del clúster:

sudo apt update
sudo apt install nfs-common

Desde uno de los nodos, comprueba que ve la exportación. Sustituye 10.0.0.10 por la IP privada de tu servidor NFS:

showmount -e 10.0.0.10
Export list for 10.0.0.10:
/srv/nfs/k8s 10.0.0.0/24

Paso 3: Instalar el driver CSI de NFS con Helm

El proyecto kubernetes-csi/csi-driver-nfs mantiene el driver oficial y publica su chart de Helm en GitHub. Añade el repositorio:

helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm repo update

Instala el driver en el namespace kube-system:

helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system

El chart despliega un controlador (csi-nfs-controller), que crea y borra los volúmenes, y un DaemonSet (csi-nfs-node), que los monta en cada nodo. Espera a que todos los pods estén en Running:

kubectl -n kube-system get pods -l app.kubernetes.io/instance=csi-driver-nfs
NAME                                  READY   STATUS    RESTARTS   AGE
csi-nfs-controller-6f8b9c7d5b-x2lqp   4/4     Running   0          45s
csi-nfs-node-7kq2m                    3/3     Running   0          45s
csi-nfs-node-p9z4d                    3/3     Running   0          45s

El driver queda registrado en el clúster como un objeto CSIDriver:

kubectl get csidrivers
NAME             ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES        AGE
nfs.csi.k8s.io   false            false            false             <unset>         false               Persistent   1m

Paso 4: Crear la StorageClass

Crea un archivo con la definición de la clase:

nano nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
  server: 10.0.0.10
  share: /srv/nfs/k8s
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
  - nfsvers=4.1

server y share indican al driver dónde crear los volúmenes. Con reclaimPolicy: Delete, el subdirectorio de cada volumen se borra al eliminar su PVC. Para datos que no quieras perder por un borrado accidental, más adelante verás cómo usar Retain.

Aplica el manifiesto y comprueba la clase:

kubectl apply -f nfs-storageclass.yaml
kubectl get storageclass nfs-csi
NAME      PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-csi   nfs.csi.k8s.io   Delete          Immediate           false                  5s

Si quieres que los PVC que no indican storageClassName usen esta clase, márcala como predeterminada. Antes quita la marca de la clase que la tenga (en k3s, local-path), porque solo debe haber una:

kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl patch storageclass nfs-csi -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Paso 5: Aprovisionar un volumen de forma dinámica

Crea un PVC que pida 5 GiB en modo ReadWriteMany, que permite montarlo desde varios pods y nodos a la vez:

nano shared-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-data
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-csi
  resources:
    requests:
      storage: 5Gi
kubectl apply -f shared-pvc.yaml
kubectl get pvc shared-data

En pocos segundos el PVC pasa a Bound y aparece un PV creado automáticamente:

NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
shared-data   Bound    pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23   5Gi        RWX            nfs-csi        4s

En el servidor NFS verás el subdirectorio que ha creado el driver:

ls /srv/nfs/k8s
pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23

Paso 6: Usar el volumen desde varios pods

Para comprobar que el volumen es compartido, crea un Deployment con dos réplicas en el que cada pod escribe su nombre en el mismo archivo:

nano writers.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: writers
spec:
  replicas: 2
  selector:
    matchLabels:
      app: writers
  template:
    metadata:
      labels:
        app: writers
    spec:
      containers:
        - name: writer
          image: busybox:1.36
          command: ["sh", "-c", "while true; do echo \"$(date) $HOSTNAME\" >> /data/log.txt; sleep 10; done"]
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: shared-data
kubectl apply -f writers.yaml
kubectl rollout status deployment/writers

Pasado medio minuto, lee el archivo desde uno de los pods. Deberías ver líneas de los dos:

kubectl exec deploy/writers -- tail -n 4 /data/log.txt
Thu Sep 24 10:12:01 UTC 2026 writers-7c9d8f6b4-2xkqz
Thu Sep 24 10:12:03 UTC 2026 writers-7c9d8f6b4-mw7tn
Thu Sep 24 10:12:11 UTC 2026 writers-7c9d8f6b4-2xkqz
Thu Sep 24 10:12:13 UTC 2026 writers-7c9d8f6b4-mw7tn

Paso 7: Conservar datos con la política Retain

Con Delete, borrar el PVC borra también los datos. Para bases de datos u otros datos importantes es más seguro Retain: el PV y su contenido se conservan y tú decides qué hacer con ellos.

Los parámetros de una StorageClass no se pueden modificar una vez creada, así que crea una segunda clase:

nano nfs-retain.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi-retain
provisioner: nfs.csi.k8s.io
parameters:
  server: 10.0.0.10
  share: /srv/nfs/k8s
reclaimPolicy: Retain
volumeBindingMode: Immediate
mountOptions:
  - nfsvers=4.1
kubectl apply -f nfs-retain.yaml

También puedes cambiar la política de un PV que ya existe, por ejemplo el que acabas de crear. Sustituye el nombre por el de tu PV:

kubectl patch pv pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

Borra ahora el Deployment y el PVC, y observa el PV:

kubectl delete deployment writers
kubectl delete pvc shared-data
kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                 STORAGECLASS   AGE
pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23   5Gi        RWX            Retain           Released   default/shared-data   nfs-csi        9m

El PV queda en estado Released y el archivo log.txt sigue en el servidor NFS. Un PV liberado no se reutiliza automáticamente: cuando ya no necesites los datos, borra el PV con kubectl delete pv <nombre> y elimina el subdirectorio a mano en el servidor NFS.

Otros drivers CSI habituales

El procedimiento es el mismo con cualquier driver: instalarlo (normalmente con Helm), crear una StorageClass con su provisioner y sus parameters, y pedir volúmenes con PVC.

DriverProvisionerCuándo usarlo
csi-driver-nfsnfs.csi.k8s.ioVolúmenes compartidos ReadWriteMany con un servidor NFS existente
Ceph RBD (mediante Rook)<namespace>.rbd.csi.ceph.comBloques replicados de alto rendimiento en varios nodos
local-path-provisionerrancher.io/local-pathPruebas o clústeres de un nodo, datos ligados a un nodo

Para almacenamiento de bloque (bases de datos), usa volumeBindingMode: WaitForFirstConsumer: así el volumen se crea en la zona o nodo donde el scheduler coloca el pod, y no al revés.

Solución de problemas

El PVC se queda en Pending. Revisa los eventos del PVC y los registros del controlador:

kubectl describe pvc shared-data
kubectl -n kube-system logs deploy/csi-nfs-controller -c nfs

Un error access denied by server indica que la IP del nodo no está en el rango de /etc/exports. Un Connection timed out apunta al firewall del puerto 2049.

El pod se queda en ContainerCreating con bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program. Falta nfs-common en el nodo donde se programó el pod. Instálalo como en el paso 2.

kubectl apply falla al cambiar una StorageClass con Forbidden: updates to parameters are forbidden. Los campos provisioner, parameters y reclaimPolicy son inmutables. Borra la clase con kubectl delete storageclass <nombre> y vuelve a crearla; los PV existentes no se ven afectados.

Conclusión

Ya tienes un servidor NFS exportando a tu clúster, el driver csi-driver-nfs instalado y dos StorageClass que crean volúmenes compartidos bajo demanda, una que borra los datos con el PVC y otra que los conserva. Como siguientes pasos puedes usar la clase en el volumeClaimTemplates de un StatefulSet, programar copias de seguridad del directorio exportado o evaluar Rook con Ceph si necesitas almacenamiento replicado sin un único servidor NFS como punto de fallo.