Almacenamiento en Kubernetes
Introducción
En esta lección entenderás cómo Kubernetes abstrae el almacenamiento y cómo elegir la opción adecuada según el tamaño del clúster y tus SLOs. Verás en profundidad la diferencia entre aprovisionamiento estático y dinámico, el ciclo de vida de PV/PVC/StorageClass y cómo se relaciona con backends: almacenamiento nativo de nube, Longhorn (entornos pequeños), Rook‑Ceph (entornos medianos), además de NFS y almacenamiento local.
Objetivos:
- Comprender PV, PVC, StorageClass y CSI a nivel conceptual y operativo.
- Elegir entre aprovisionamiento estático vs dinámico con criterio.
- Aplicar patrones de topología, seguridad, snapshots, clones y expansión.
Conceptos Fundamentales
Componentes clave
- Volumes: Montajes en un Pod. Pueden ser efímeros (p.ej.,
emptyDir,configMap,secret) o persistentes vía PVC. - PersistentVolume (PV): Recurso del clúster que representa capacidad real (bloque o fichero) ya aprovisionada o creada por un driver CSI.
- PersistentVolumeClaim (PVC): Solicitud de almacenamiento por parte de una aplicación (tamaño, modos de acceso, clase deseada).
- StorageClass (SC): Plantilla que describe cómo aprovisionar PV de forma dinámica (driver CSI, parámetros, topología).
- CSI (Container Storage Interface): Estándar que permite a proveedores de almacenamiento integrarse con Kubernetes.
Modos y parámetros esenciales
- accessModes:
- RWO (ReadWriteOnce): Lectura/escritura por un único nodo a la vez. Ideal para bases de datos.
- RWX (ReadWriteMany): Varios nodos pueden leer/escribir. Ideal para contenido compartido.
- ROX (ReadOnlyMany): Múltiples nodos en solo lectura.
- volumeMode:
Filesystem(lo más común) oBlock(bloque crudo para motores de BD/alto rendimiento). - reclaimPolicy:
DeleteoRetain(evita borrados accidentales).Recycleestá obsoleto. - volumeBindingMode (en SC):
Immediate: el volumen se crea al crear el PVC.WaitForFirstConsumer: se retrasa hasta conocer el Pod consumidor y su topología (recomendado).
- allowedTopologies / nodeAffinity: restringe dónde puede ubicarse un volumen (zonas, nodos).
Ciclo de vida y flujo de aprovisionamiento
- El usuario crea un PVC describiendo tamaño y, opcionalmente,
storageClassName. - Si el PVC referencia una StorageClass, el controlador CSI la usa para aprovisionamiento dinámico y crea un PV a medida.
- Si no hay StorageClass (o se indica
storageClassName: "") Kubernetes busca PV estáticos preexistentes que cumplan el PVC (tamaño, accessModes, selector/labels). - El PVC se vincula (binding) a un PV compatible. Con
WaitForFirstConsumer, la creación/binding se alinea con el scheduling del Pod respetando la topología. - El Pod monta el volumen. Al borrar el PVC, la política de
reclaimPolicydecide si se borra el backend (Delete) o se retiene (Retain).
Idea clave: En dinámico, “pides y se crea”. En estático, “usas lo que ya existe”.
Aprovisionamiento estático vs dinámico
Aprovisionamiento dinámico (con StorageClass)
- ¿Cuándo? Entornos cloud o SDS con drivers CSI maduros (EBS/PD/Managed Disks, EFS/Filestore/Azure Files, Longhorn, Rook‑Ceph).
- Ventajas: Simplicidad operativa, escalado, parámetros por clase (tipo de disco, IOPS, fsType), snapshots integrables,
WaitForFirstConsumer. - Consideraciones: Define una SC por perfil (rendimiento, costes, HA); usa
allowVolumeExpansion: true; marca una SC por defecto si procede.
Ejemplo StorageClass (AWS EBS gp3):
# filepath: /k8s/examples/storage/aws-ebs-gp3-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
Ejemplo PVC dinámico:
# filepath: /k8s/examples/storage/pvc-rwo-dinamico.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datos-app
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
storageClassName: gp3
Aprovisionamiento estático (PV preexistente)
- ¿Cuándo?
- NAS/NFS ya desplegado y gestionado por otro equipo.
- Local PV con afinidad a un nodo (latencia mínima o requisitos de data locality).
- Integración con cabinas legacy/SAN, entornos air‑gapped.
- Restauraciones/migraciones donde reaprovechas un disco concreto (attach por
volumeName).
- Ventajas: Control fino del backend, independencia del controlador CSI.
- Consideraciones: Requiere gestión manual del ciclo de vida y coordinación con operaciones.
Ejemplo PV/PVC estático (NFS) con selector:
# filepath: /k8s/examples/storage/nfs-pv-pvc-estatico.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
labels:
uso: compartido
spec:
capacity:
storage: 200Gi
accessModes: ["ReadWriteMany"]
nfs:
server: 10.0.0.10
path: /export/k8s
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 50Gi
storageClassName: "" # fuerza búsqueda de PVs estáticos
selector:
matchLabels:
uso: compartido
Binding directo a un PV concreto:
# filepath: /k8s/examples/storage/pvc-binding-directo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datos-existentes
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
storageClassName: ""
volumeName: pv-lun-42 # debe existir y ser compatible
Profundizando en StorageClass
- Parámetros comunes:
provisioner: driver CSI (p.ej.,ebs.csi.aws.com,driver.longhorn.io,rook-ceph.rbd.csi.ceph.com).parameters: tipo de disco/pool, fsType, réplicas (según driver).mountOptions: opciones de montaje (p.ej.noatime,discard).reclaimPolicy: por volumen creado.allowVolumeExpansion: expansión online si el backend lo soporta.volumeBindingMode: recomiendaWaitForFirstConsumerpara respetar zonas/nodos.allowedTopologies: restringe a zonas/regiones concretas.
Snapshots, clones y expansión
- VolumeSnapshot: punto en el tiempo de un PVC. Requiere
VolumeSnapshotClass. - Restore desde snapshot: crear PVC con
dataSourceapuntando al snapshot. - Clones: PVC nuevo con
dataSourcea otro PVC (misma SC). - Expansión: aumentar
resources.requests.storageen el PVC si SC lo permite.
Ejemplo Snapshot y Restore (EBS CSI):
# filepath: /k8s/examples/storage/snapshot-restore.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-aws-vsc
driver: ebs.csi.aws.com
deletionPolicy: Delete
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: datos-app-snap
spec:
source:
persistentVolumeClaimName: datos-app
volumeSnapshotClassName: csi-aws-vsc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datos-app-restore
spec:
storageClassName: gp3
dataSource:
name: datos-app-snap
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Clone de PVC:
# filepath: /k8s/examples/storage/clone-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datos-app-clone
spec:
storageClassName: gp3
dataSource:
name: datos-app
kind: PersistentVolumeClaim
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Almacenamiento por backend
Nativo de nube (recomendado en cloud)
- AWS: EBS (RWO), EFS (RWX); GCP: Persistent Disk (RWO), Filestore (RWX); Azure: Managed Disks (RWO), Azure Files (RWX).
- Pros: Integración nativa, snapshots, rendimiento gestionado.
- Contras: Costes por GB/IOPS; dependencia del proveedor.
Longhorn (entornos pequeños/on‑prem)
- SDS ligero con réplica entre nodos y UI integrada.
- Pros: Fácil instalación, backups a S3, snapshots.
- Contras: Overhead de red/CPU; menos ideal para clústeres grandes.
SC Longhorn:
# filepath: /k8s/examples/storage/longhorn-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn
provisioner: driver.longhorn.io
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "30"
fsType: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
Rook‑Ceph (entornos medianos)
- Operador que despliega Ceph: RBD (bloque, RWO) y CephFS (ficheros, RWX).
- Pros: Escalable, resiliente, RWX y RWO, múltiples pools.
- Contras: Mayor complejidad operativa.
SC RBD (RWO):
# filepath: /k8s/examples/storage/rook-ceph-rbd-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFeatures: layering
csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions: ["discard"]
SC CephFS (RWX):
# filepath: /k8s/examples/storage/rook-cephfs-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
clusterID: rook-ceph
fsName: myfs
pool: myfs-data0
reclaimPolicy: Delete
allowVolumeExpansion: true
NFS (compartido simple)
- Pros: RWX sencillo y maduro.
- Contras: Cuello de botella de metadatos; no ideal para altas escrituras o BD.
PV/PVC NFS (estático):
# filepath: /k8s/examples/storage/nfs-pv-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
spec:
capacity:
storage: 200Gi
accessModes: ["ReadWriteMany"]
nfs:
server: 10.0.0.10
path: /export/prod
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc2
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 50Gi
volumeName: nfs-pv2
Almacenamiento local
emptyDir(efímero) y Local PV (persistente atado a nodo).- Pros: Baja latencia; ideal para cachés o cargas con data locality.
- Contras: Dependencia del nodo; no hay HA si cae el host.
Local PV:
# filepath: /k8s/examples/storage/local-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
spec:
capacity:
storage: 200Gi
volumeMode: Filesystem
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["worker-1"]
Recomendaciones por tamaño de clúster
-
Pequeño (1–5 nodos, single‑zone)
- Preferido: Longhorn (RWO y RWX básico). NFS para RWX sencillo.
- En cloud: discos gestionados (EBS/PD/Managed Disks) + EFS/Filestore/Azure Files.
- Aprovisionamiento: dinámico con SC siempre que sea posible; estático para NFS/local.
-
Mediano (5–30 nodos, multi‑zona posible)
- Preferido: Rook‑Ceph (RBD para RWO, CephFS para RWX).
- Alternativa cloud: nativo de nube + servicios RWX (EFS/Filestore/Azure Files).
- Aprovisionamiento: dinámico con SC; estático para integraciones puntuales o local PV.
-
Grande (30+ nodos o alta IOPS)
- Preferido: Nativo de nube optimizado (io2, pd-ssd) o Rook‑Ceph con NVMe y red 25/40GbE.
- RWX exigente: CephFS/Filestore/FSx adecuados al proveedor.
- Aprovisionamiento: dinámico; usa
WaitForFirstConsumer, topologías y cuotas.
Consideraciones transversales:
- RWO intensivo (BD): bloque SSD/NVMe, antiafinidad de Pods, PDBs,
WaitForFirstConsumer. - RWX compartido: CephFS/EFS/Filestore/Azure Files según latencia/SLA.
- Restauraciones/migraciones: PV estático con
volumeNameo PVC desde snapshot.
Seguridad (DevSecOps)
- Cifrado en reposo: habilítalo en el backend (EBS/PD/Disks, Ceph, Longhorn).
- Cifrado en tránsito: NFSv4/TLS, Ceph msgr2, políticas de red.
- Permisos:
fsGroup,fsGroupChangePolicy, SELinux/AppArmor. - Secretos: CSI Secrets Store o KMS del proveedor.
Observabilidad y rendimiento
- Métricas: driver CSI, kubelet, Longhorn, Rook‑Ceph (mgr).
- Alertas: latencia elevada,
degraded/réplicas fuera de sync, uso >80%. - Pruebas:
fiocon perfiles representativos; revisa IOPS/latencia/throughput.
Anti‑patrones
- Usar NFS para BD de alta escritura.
- No usar
WaitForFirstConsumeren entornos multi‑zona. - Mezclar discos heterogéneos sin etiquetado ni afinidades.
Ejemplos Prácticos
Desplegar una app con PVC RWO dinámico:
# filepath: /k8s/examples/storage/deploy-app-rwo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-rwo
spec:
replicas: 1
selector:
matchLabels:
app: app-rwo
template:
metadata:
labels:
app: app-rwo
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: datos
mountPath: /var/lib/app
volumes:
- name: datos
persistentVolumeClaim:
claimName: datos-app
StatefulSet con PVC (dinámico) y WaitForFirstConsumer:
# filepath: /k8s/examples/storage/statefulset-rwo.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: "db"
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: datos
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: datos
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 50Gi
Ejercicios
- Crea un PV/PVC estático en NFS usando selector de labels y monta un Pod de prueba.
- Define una StorageClass con
WaitForFirstConsumer; crea un PVC y verifica la zona donde se crea el volumen. - Implementa Longhorn en 3 nodos, configura
numberOfReplicas: 2y simula la caída de un nodo. - Despliega Rook‑Ceph y crea SC de RBD y CephFS; prueba un Deployment (RWO) y otro (RWX).
- Crea un VolumeSnapshot y restaura un PVC; mide diferencias de rendimiento con
fio.
Conclusiones
- Usa aprovisionamiento dinámico con StorageClass como opción por defecto; aporta consistencia y automatización.
- Recurre a estático cuando necesites reutilizar capacidad existente, local PV o integrarte con backends legacy.
- Elige el backend según tamaño y requisitos: nube nativa (simplicidad), Longhorn (pequeños), Rook‑Ceph (medianos), NFS (RWX simple), local (latencia mínima).
- Diseña desde el inicio con topología, seguridad, snapshots y backups en mente.
Recursos Adicionales
- Documentación de Kubernetes: Volumes, PV/PVC, StorageClass, Snapshots
- Longhorn Docs
- Rook‑Ceph Docs
- Velero (backup y DR)