--- title: "StorageClass y drivers CSI en Kubernetes con NFS" description: "Aprende cómo funcionan las StorageClass y los drivers CSI en Kubernetes e instala el driver CSI de NFS para crear volúmenes persistentes de forma dinámica." category: "Kubernetes Avanzado" language: es source: https://cubepath.com/es/docs/kubernetes-avanzado/clases-de-almacenamiento-de-kubernetes-y-controladores-csi updated: 2026-09-25 --- # StorageClass y drivers CSI en Kubernetes con NFS 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: | Campo | Qué hace | Valores habituales | |-------|----------|--------------------| | `provisioner` | Driver CSI que crea los volúmenes | `nfs.csi.k8s.io`, `rbd.csi.ceph.com`, `rancher.io/local-path` | | `parameters` | Opciones propias del driver (servidor, pool, tipo de disco) | Dependen del driver | | `reclaimPolicy` | Qué pasa con el volumen al borrar el PVC | `Delete` (por defecto) o `Retain` | | `volumeBindingMode` | Cuándo se crea el volumen | `Immediate` o `WaitForFirstConsumer` | | `allowVolumeExpansion` | Permite ampliar un PVC editando su tamaño | `true` o `false` | | `mountOptions` | Opciones de montaje que se aplican en el nodo | `nfsvers=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: ```bash 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: ```output 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: ```bash sudo apt update sudo apt install nfs-kernel-server ``` Crea el directorio que exportarás. El driver creará dentro un subdirectorio por cada volumen: ```bash 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: ```bash sudo nano /etc/exports ``` ```text /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: ```bash sudo exportfs -ra sudo exportfs -v ``` ```output /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: ```bash 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: ```bash 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: ```bash showmount -e 10.0.0.10 ``` ```output 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: ```bash 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`: ```bash helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system ``` > Consejo: en producción fija la versión con `--version` para que una reinstalación no cambie de versión sin que lo decidas. `helm search repo csi-driver-nfs` muestra las disponibles. 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`: ```bash kubectl -n kube-system get pods -l app.kubernetes.io/instance=csi-driver-nfs ``` ```output 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`: ```bash kubectl get csidrivers ``` ```output NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE nfs.csi.k8s.io false false false false Persistent 1m ``` ## Paso 4: Crear la StorageClass Crea un archivo con la definición de la clase: ```bash nano nfs-storageclass.yaml ``` ```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: ```bash kubectl apply -f nfs-storageclass.yaml kubectl get storageclass nfs-csi ``` ```output 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: ```bash 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: ```bash nano shared-pvc.yaml ``` ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: shared-data spec: accessModes: - ReadWriteMany storageClassName: nfs-csi resources: requests: storage: 5Gi ``` ```bash 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: ```output 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: ```bash ls /srv/nfs/k8s ``` ```output pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23 ``` > Nota: NFS no aplica cuotas, así que los 5 GiB del PVC son orientativos. El límite real es el espacio libre del disco exportado. ## 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: ```bash nano writers.yaml ``` ```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 ``` ```bash 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: ```bash kubectl exec deploy/writers -- tail -n 4 /data/log.txt ``` ```output 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: ```bash nano nfs-retain.yaml ``` ```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 ``` ```bash 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: ```bash kubectl patch pv pvc-3f1c2a9e-8d4b-4c61-9a7e-2b5d0e6f1a23 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' ``` Borra ahora el Deployment y el PVC, y observa el PV: ```bash kubectl delete deployment writers kubectl delete pvc shared-data kubectl get pv ``` ```output 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 ` y elimina el subdirectorio a mano en el servidor NFS. > Nota: la política `Recycle` está obsoleta y ningún driver CSI la soporta. Usa `Delete` o `Retain`. ## 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. | Driver | Provisioner | Cuándo usarlo | |--------|-------------|---------------| | csi-driver-nfs | `nfs.csi.k8s.io` | Volúmenes compartidos `ReadWriteMany` con un servidor NFS existente | | Ceph RBD (mediante Rook) | `.rbd.csi.ceph.com` | Bloques replicados de alto rendimiento en varios nodos | | local-path-provisioner | `rancher.io/local-path` | Pruebas 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: ```bash 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. 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 ` 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.