Volúmenes Persistentes en Kubernetes: Guía Completa de Producción

Los Volúmenes Persistentes (PV) y las Reclamaciones de Volúmenes Persistentes (PVC) proporcionan abstracción de almacenamiento en Kubernetes, permitiendo que las aplicaciones con estado persistan datos más allá de los ciclos de vida de los pods. Esta guía completa cubre conceptos de PV/PVC, clases de almacenamiento, aprovisionamiento dinámico y mejores prácticas de producción para persistencia de datos en Kubernetes.

Tabla de Contenidos

Introducción

Los Volúmenes Persistentes de Kubernetes desacoplan el almacenamiento de los pods, proporcionando almacenamiento duradero que sobrevive a reinicios y reprogramaciones de pods. Comprender PV, PVC y StorageClasses es esencial para ejecutar aplicaciones con estado como bases de datos, colas de mensajes y sistemas de almacenamiento de archivos.

¿Por Qué Volúmenes Persistentes?

  • Persistencia de Datos: Sobrevive reinicios y eliminaciones de pods
  • Abstracción de Almacenamiento: Desacopla almacenamiento de especificaciones de pods
  • Aprovisionamiento Dinámico: Creación automática de volúmenes
  • Portabilidad: API de almacenamiento consistente entre proveedores
  • Gestión del Ciclo de Vida: Ciclo de vida de almacenamiento independiente

Jerarquía de Volúmenes

StorageClass
    ↓
PersistentVolume (PV)
    ↓
PersistentVolumeClaim (PVC)
    ↓
Montaje de Volumen en Pod

Prerrequisitos

  • Cluster de Kubernetes (1.19+)
  • kubectl configurado
  • Backend de almacenamiento (local, NFS, proveedor en la nube, etc.)
  • Comprensión básica de Pods y Deployments

Verificar configuración:

kubectl version --client
kubectl get nodes
kubectl get storageclass

Conceptos de Almacenamiento

Tipos de Volúmenes

Volúmenes Efímeros:

  • emptyDir: Almacenamiento temporal, ciclo de vida del pod
  • configMap: Datos de configuración
  • secret: Datos sensibles

Volúmenes Persistentes:

  • hostPath: Sistema de archivos del nodo (solo desarrollo)
  • nfs: Network File System
  • csi: Plugins de Container Storage Interface
  • Nube: awsEBS, gcePersistentDisk, azureDisk

Estados del Ciclo de Vida

Estados de PV:

  • Available: Listo para reclamar
  • Bound: Reclamado por PVC
  • Released: PVC eliminado, datos retenidos
  • Failed: Recuperación automática fallida

Estados de PVC:

  • Pending: Esperando vinculación con PV
  • Bound: Vinculado a PV
  • Lost: PV no disponible

Volúmenes Persistentes

Ejemplo Básico de PV

# pv-local.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  hostPath:
    path: /mnt/data
# Crear PV
kubectl apply -f pv-local.yaml

# Verificar estado del PV
kubectl get pv
kubectl describe pv local-pv

Volumen Persistente NFS

# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    server: nfs-server.example.com
    path: /exported/path
  mountOptions:
    - hard
    - nfsvers=4.1

Ejemplos de PV de Proveedores en la Nube

AWS EBS

# pv-aws-ebs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: aws-ebs-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: aws-ebs
  awsElasticBlockStore:
    volumeID: vol-0123456789abcdef0
    fsType: ext4

Google Persistent Disk

# pv-gce-pd.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gce-pd-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: gce-pd
  gcePersistentDisk:
    pdName: my-disk-name
    fsType: ext4

Azure Disk

# pv-azure-disk.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: azure-disk-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: azure-disk
  azureDisk:
    diskName: myAKSDisk
    diskURI: /subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Compute/disks/myAKSDisk
    kind: Managed

Políticas de Reclamación

spec:
  persistentVolumeReclaimPolicy: Retain  # Mantener datos después de eliminación de PVC
  # O
  persistentVolumeReclaimPolicy: Delete  # Eliminar volumen después de eliminación de PVC
  # O
  persistentVolumeReclaimPolicy: Recycle # Obsoleto - limpieza básica (rm -rf)

Reclamaciones de Volúmenes Persistentes

PVC Básico

# pvc-basic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: basic-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard
# Crear PVC
kubectl apply -f pvc-basic.yaml

# Verificar estado del PVC
kubectl get pvc
kubectl describe pvc basic-pvc

Usar PVC en Pod

# pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-storage
spec:
  containers:
  - name: app
    image: nginx:alpine
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: basic-pvc

Usar PVC en Deployment

# deployment-with-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 1  # Nota: volúmenes RWO limitan a 1 réplica
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        volumeMounts:
        - name: web-storage
          mountPath: /usr/share/nginx/html
      volumes:
      - name: web-storage
        persistentVolumeClaim:
          claimName: web-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard

Selector para PV Específico

# pvc-with-selector.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: selective-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: ""  # Vacío para vinculación manual
  selector:
    matchLabels:
      environment: production
      tier: database

Clases de Almacenamiento

Las StorageClasses permiten el aprovisionamiento dinámico de PersistentVolumes.

Ver Clases de Almacenamiento

# Listar clases de almacenamiento
kubectl get storageclass
kubectl get sc

# Describir clase de almacenamiento
kubectl describe sc standard

StorageClass Básica

# storageclass-basic.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
  replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete

StorageClasses de Proveedores en la Nube

AWS EBS

# sc-aws-ebs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-ebs-gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

Google Cloud

# sc-gce-pd.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gce-ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
  replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

Azure Disk

# sc-azure.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: azure-premium
provisioner: disk.csi.azure.com
parameters:
  skuName: Premium_LRS
  kind: Managed
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

StorageClass Predeterminada

# Establecer clase de almacenamiento predeterminada
kubectl patch storageclass standard \
  -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# Eliminar predeterminada
kubectl patch storageclass standard \
  -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

Aprovisionamiento Dinámico

El aprovisionamiento dinámico crea automáticamente PVs cuando se crean PVCs.

PVC con Aprovisionamiento Dinámico

# pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: fast-ssd  # Usa aprovisionador de StorageClass
# Crear PVC
kubectl apply -f pvc-dynamic.yaml

# PV se crea automáticamente
kubectl get pv
kubectl get pvc dynamic-pvc

Expansión de Volúmenes

# sc-expandable.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: expandable-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
allowVolumeExpansion: true  # Habilitar expansión
# Expandir PVC
kubectl patch pvc dynamic-pvc -p '{"spec":{"resources":{"requests":{"storage":"30Gi"}}}}'

# Verificar estado de expansión
kubectl get pvc dynamic-pvc
kubectl describe pvc dynamic-pvc

Modos de Volumen y Acceso

Modos de Acceso

  • ReadWriteOnce (RWO): Un solo nodo lectura-escritura
  • ReadOnlyMany (ROX): Múltiples nodos solo lectura
  • ReadWriteMany (RWX): Múltiples nodos lectura-escritura
  • ReadWriteOncePod (RWOP): Un solo pod lectura-escritura (1.22+)
# Diferentes modos de acceso
spec:
  accessModes:
    - ReadWriteOnce   # Almacenamiento de bloques (EBS, Azure Disk)
    # O
    - ReadWriteMany   # Almacenamiento compartido (NFS, EFS, Azure Files)
    # O
    - ReadOnlyMany    # Solo lectura compartido

Modos de Volumen

# Modo de bloque (dispositivo de bloque crudo)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block  # Modo de bloque
  resources:
    requests:
      storage: 10Gi
  storageClassName: fast-ssd
---
# Usar volumen de bloque en pod
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-block
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeDevices:  # volumeDevices en lugar de volumeMounts
    - name: data
      devicePath: /dev/xvda
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: block-pvc

StatefulSets con Almacenamiento Persistente

Los StatefulSets proporcionan identificadores de red únicos y estables y almacenamiento persistente.

StatefulSet con VolumeClaimTemplates

# statefulset-mysql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 20Gi
      storageClassName: fast-ssd
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None  # Servicio sin cabeza
  selector:
    app: mysql
  ports:
  - port: 3306

StatefulSet de PostgreSQL

# statefulset-postgres.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 50Gi
      storageClassName: standard

Patrones de Producción

Múltiples Montajes de Volumen

# pod-multi-volumes.yaml
apiVersion: v1
kind: Pod
metadata:
  name: multi-volume-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: app-data
      mountPath: /app/data
    - name: logs
      mountPath: /app/logs
    - name: config
      mountPath: /app/config
      readOnly: true
  volumes:
  - name: app-data
    persistentVolumeClaim:
      claimName: app-data-pvc
  - name: logs
    persistentVolumeClaim:
      claimName: logs-pvc
  - name: config
    configMap:
      name: app-config

Init Container para Configuración de Datos

# pod-with-init.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-init
spec:
  initContainers:
  - name: setup
    image: busybox:latest
    command: ['sh', '-c', 'echo "Configurando datos" > /data/setup.txt']
    volumeMounts:
    - name: data
      mountPath: /data
  containers:
  - name: app
    image: nginx:alpine
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: app-pvc

Límites de Recursos

# pvc-with-limits.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: limited-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
    limits:
      storage: 50Gi  # Expansión máxima
  storageClassName: expandable-storage

Respaldo y Recuperación ante Desastres

Instantáneas de Volúmenes

# volumesnapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: data-snapshot
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: app-data-pvc
# Crear instantánea
kubectl apply -f volumesnapshot.yaml

# Verificar instantánea
kubectl get volumesnapshot
kubectl describe volumesnapshot data-snapshot

Restaurar desde Instantánea

# pvc-from-snapshot.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restored-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard
  dataSource:
    name: data-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

Respaldo con Jobs

# backup-job.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-job
spec:
  schedule: "0 2 * * *"  # Diariamente a las 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: backup-tool:latest
            volumeMounts:
            - name: data
              mountPath: /data
              readOnly: true
            - name: backup
              mountPath: /backup
          restartPolicy: OnFailure
          volumes:
          - name: data
            persistentVolumeClaim:
              claimName: app-data-pvc
          - name: backup
            persistentVolumeClaim:
              claimName: backup-pvc

Solución de Problemas

Problemas Comunes

# PVC atascado en Pending
kubectl describe pvc <pvc-name>
# Verificar: StorageClass existe, recursos suficientes, afinidad de nodo

# Pod no puede montar volumen
kubectl describe pod <pod-name>
kubectl get events --sort-by='.lastTimestamp'
# Verificar: PVC está Bound, claimName correcto, modos de acceso coinciden

# Volumen no se expande
kubectl describe pvc <pvc-name>
# Verificar: allowVolumeExpansion=true, almacenamiento subyacente soporta expansión

# Verificar vinculación PV/PVC
kubectl get pv,pvc
kubectl describe pv <pv-name>
kubectl describe pvc <pvc-name>

Comandos de Depuración

# Verificar clase de almacenamiento
kubectl get sc
kubectl describe sc <sc-name>

# Verificar PV
kubectl get pv
kubectl describe pv <pv-name>

# Verificar PVC
kubectl get pvc -A
kubectl describe pvc <pvc-name>

# Verificar volúmenes de pod
kubectl describe pod <pod-name> | grep -A 5 Volumes

# Verificar almacenamiento del nodo
kubectl describe node <node-name> | grep -A 10 "Allocated resources"

# Eventos
kubectl get events --field-selector involvedObject.name=<pvc-name>

Limpiar Recursos Atascados

# Eliminar finalizadores de PVC (si está atascado eliminando)
kubectl patch pvc <pvc-name> -p '{"metadata":{"finalizers":null}}'

# Forzar eliminación de PV
kubectl delete pv <pv-name> --grace-period=0 --force

# Verificar volúmenes huérfanos
kubectl get pv | grep Released

Conclusión

Los Volúmenes Persistentes son esenciales para aplicaciones con estado en Kubernetes. Comprender PV, PVC, StorageClasses y aprovisionamiento dinámico permite una persistencia de datos confiable para cargas de trabajo de producción.

Conclusiones Clave

  • PV: Recurso de almacenamiento físico en el cluster
  • PVC: Solicitud de almacenamiento por pods
  • StorageClass: Plantilla de aprovisionamiento dinámico
  • Modos de Acceso: Controlan cómo se accede a los volúmenes
  • StatefulSets: Despliegue ordenado con almacenamiento estable
  • Respaldos: Instantáneas regulares y planes de recuperación ante desastres

Referencia Rápida

# PersistentVolume
kubectl get pv
kubectl describe pv <pv-name>
kubectl delete pv <pv-name>

# PersistentVolumeClaim
kubectl get pvc
kubectl describe pvc <pvc-name>
kubectl delete pvc <pvc-name>

# StorageClass
kubectl get sc
kubectl describe sc <sc-name>

# Instantáneas de Volumen
kubectl get volumesnapshot
kubectl describe volumesnapshot <name>

# Expandir PVC
kubectl patch pvc <name> -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

Lista de Verificación de Producción

  • Elegir StorageClass apropiada para la carga de trabajo
  • Establecer modos de acceso correctos (RWO, RWX, ROX)
  • Configurar capacidad de expansión de volumen
  • Implementar estrategia de respaldo (instantáneas, jobs)
  • Establecer política de reclamación apropiada
  • Monitorear uso y capacidad de volumen
  • Probar procedimientos de restauración
  • Documentar arquitectura de almacenamiento
  • Implementar cuotas de recursos
  • Planificar recuperación ante desastres

Próximos Pasos

  1. Planificar: Evaluar requisitos de almacenamiento
  2. Configurar: Establecer StorageClasses
  3. Desplegar: Implementar StatefulSets con almacenamiento
  4. Respaldar: Configurar estrategia de instantáneas
  5. Monitorear: Rastrear métricas de volumen
  6. Optimizar: Dimensionar correctamente asignaciones de almacenamiento
  7. Recuperación ante Desastres: Probar procedimientos de respaldo/restauración

¡Domina los Volúmenes Persistentes para ejecutar aplicaciones con estado de manera confiable en Kubernetes!