StatefulSets & Almacenamiento Permanente en Kubernetes

Implemente aplicaciones con estado con escalado ordenado y datos persistentes

Kubernetes StatefulSets son la solución ideal para gestionar aplicaciones con estado que requieren identidades estables, almacenamiento persistente y patrones de despliegue ordenados, esenciales para bases de datos, sistemas distribuidos y capas de caché.

Si eres nuevo en Kubernetes o estás configurando un clúster, considera explorar distribuciones de Kubernetes como k3s o MicroK8s para desarrollo, o instalar Kubernetes con Kubespray para clústeres de producción.

presentación en la cafetería Esta imagen agradable fue generada por modelo AI Flux 1 dev.

¿Qué son los StatefulSets?

Los StatefulSets son un objeto de API de carga de trabajo de Kubernetes diseñado específicamente para gestionar aplicaciones con estado. A diferencia de los Deployments, que tratan a todos los pods como intercambiables, los StatefulSets mantienen una identidad única para cada pod con garantías sobre el orden y la unicidad.

Funciones clave:

  • Identificadores de red estables: Cada pod recibe un nombre de host predecible que persiste incluso tras reinicios
  • Almacenamiento persistente: Solicitudes de volumen persistentes dedicadas que siguen a los pods incluso tras reschedules
  • Despliegue ordenado: Los pods se crean secuencialmente (0, 1, 2…) y se terminan en orden inverso
  • Actualizaciones ordenadas: Las actualizaciones progresivas se realizan en orden, asegurando la estabilidad de la aplicación

Los StatefulSets son esenciales para aplicaciones como PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis y etcd—cualquier carga de trabajo donde la identidad del pod y la persistencia de datos importen.

Entendiendo el almacenamiento persistente en Kubernetes

Kubernetes proporciona una capa de abstracción de almacenamiento sofisticada que desconecta la gestión del almacenamiento del ciclo de vida del pod:

Componentes de almacenamiento

PersistentVolume (PV): Un fragmento de almacenamiento en el clúster provisto por un administrador o creado dinámicamente mediante una StorageClass. Los PV existen independientemente de los pods.

PersistentVolumeClaim (PVC): Una solicitud de almacenamiento por parte de un pod. Los PVC se vinculan a PV disponibles que coincidan con sus requisitos (tamaño, modo de acceso, clase de almacenamiento).

StorageClass: Define diferentes “clases” de almacenamiento con diversos proveedores (AWS EBS, GCE PD, Azure Disk, NFS, etc.) y parámetros como replicación, niveles de rendimiento y políticas de respaldo.

Modos de acceso

  • ReadWriteOnce (RWO): Volumen montado como de lectura y escritura por un solo nodo
  • ReadOnlyMany (ROX): Volumen montado como de solo lectura por muchos nodos
  • ReadWriteMany (RWX): Volumen montado como de lectura y escritura por muchos nodos (requiere backends de almacenamiento especiales)

Arquitectura de almacenamiento de StatefulSets

Los StatefulSets utilizan volumeClaimTemplates para crear automáticamente PersistentVolumeClaims para cada réplica de pod. Esto es fundamentalmente diferente de los Deployments:

Cómo funcionan los volumeClaimTemplates

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Cuando creas este StatefulSet:

  1. Kubernetes crea el pod mysql-0 y el PVC data-mysql-0
  2. Luego crea el pod mysql-1 y el PVC data-mysql-1
  3. Finalmente crea el pod mysql-2 y el PVC data-mysql-2

Cada pod recibe su propio volumen persistente de 10 GB dedicado. Si mysql-1 se elimina o se reschedulea, Kubernetes lo recrea y vuelve a adjuntar el mismo PVC data-mysql-1, preservando todos los datos.

Crear un servicio sin dirección IP (headless) para StatefulSets

Los StatefulSets requieren un servicio headless para proporcionar identidades de red estables:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Esto lo convierte en un servicio headless
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Esto crea entradas DNS para cada pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Las aplicaciones pueden conectarse directamente a instancias específicas de pod usando estos nombres DNS estables.

Patrones de despliegue de StatefulSets

Para equipos que gestionan despliegues complejos de Kubernetes, Helm Charts ofrecen una forma poderosa de empaquetar y desplegar StatefulSets con plantillas, versionado y gestión de dependencias. Helm simplifica la gestión de configuraciones de StatefulSet en diferentes entornos.

Escalado ordenado

Al escalar de 3 a 5 réplicas:

kubectl scale statefulset mysql --replicas=5

Kubernetes crea los pods en orden: mysql-3 → espera a que esté listo → mysql-4

Al escalar de 5 a 3 réplicas:

kubectl scale statefulset mysql --replicas=3

Kubernetes termina en orden inverso: mysql-4 → espera a que termine → mysql-3

Actualizaciones progresivas

Los StatefulSets admiten dos estrategias de actualización:

OnDelete: Actualizaciones manuales—los pods solo se actualizan cuando los eliminas RollingUpdate: Actualizaciones automáticas secuenciales en orden inverso

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Solo actualiza pods con ordinal >= 2

El parámetro partition permite despliegues canarios—puedes actualizar primero los pods con números altos y probar antes de desplegar a todas las réplicas.

Mejores prácticas de almacenamiento

Provisionamiento dinámico

Siempre usa StorageClasses para el provisionamiento dinámico de volúmenes:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Habilita el redimensionamiento de PVC sin recrearlos reclaimPolicy: Retain mantiene los datos del PV después de eliminar el PVC, Delete los elimina automáticamente

Políticas de retención de PVC

Kubernetes 1.23+ admite persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Mantiene los PVC cuando se elimina el StatefulSet
    whenScaled: Delete     # Elimina los PVC al escalar hacia abajo

Opciones:

  • Retain: Mantiene los PVC (comportamiento predeterminado, más seguro)
  • Delete: Elimina automáticamente los PVC (útil para entornos de desarrollo)

Estrategias de respaldo

Snapshots de volumen: Usa recursos de VolumeSnapshot para crear copias de seguridad en punto de tiempo

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Respaldo a nivel de aplicación: Usa herramientas como mysqldump, pg_dump o Velero para respaldos específicos de bases de datos

Replicación distribuida: Configura la replicación a nivel de aplicación (replicación de MySQL, replicación en streaming de PostgreSQL) como primera línea de defensa

Casos de uso reales

Clúster de base de datos (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Cache distribuido (Redis)

Para clústeres de Redis, necesitas tanto un StatefulSet como una configuración cuidadosa:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Cola de mensajes (Kafka)

Kafka requiere tanto almacenamiento persistente para los registros como identidades de red estables para la coordinación de brokers:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Monitoreo y solución de problemas

Para una referencia completa de los comandos de Kubernetes utilizados en esta sección, consulta la Hoja de trucos de Kubernetes.

Verificar el estado de los StatefulSets

# Ver detalles del StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Ver el orden de creación y el estado de los pods
kubectl get pods -l app=mysql -w

# Ver el estado de los PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Problemas comunes

Pod atascado en Pendiente: Verificar el estado del PVC y la disponibilidad del almacenamiento

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Almacenamiento lleno: Ampliar el PVC si la StorageClass lo permite

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod que no se termina: Verificar bloqueos a nivel de aplicación o problemas de desmontaje de volúmenes

kubectl delete pod mysql-0 --grace-period=0 --force

Métricas para monitorear

  • Uso de almacenamiento: Monitorear la capacidad y el porcentaje de uso del PVC
  • Rendimiento de E/S: Registrar IOPS, throughput y latencia
  • Reinicios de pod: Reinicios frecuentes pueden indicar problemas de almacenamiento
  • Tiempo de vinculación de PVC: Tiempos lentos sugieren problemas de provisionamiento

Estrategias de migración

Al migrar a StatefulSets, asegúrate de que tu clúster de Kubernetes esté correctamente configurado. Para configuraciones de laboratorio o clústeres pequeños, revisa nuestra comparación completa de distribuciones de Kubernetes para elegir la plataforma adecuada para tus requisitos de carga de trabajo.

De Deployment a StatefulSet

  1. Crear StatefulSet con volumeClaimTemplates
  2. Escalar hacia abajo el Deployment de forma suave
  3. Restaurar datos desde respaldos a los pods de StatefulSet
  4. Actualizar referencias de DNS/Servicio
  5. Eliminar el Deployment antiguo y los PVCs

Respaldo antes de la migración

# Crear snapshots de los PVCs existentes
kubectl get pvc -o yaml > pvc-backup.yaml

# Crear snapshots de volumen
kubectl apply -f volume-snapshot.yaml

Consideraciones de seguridad

Encriptación de almacenamiento

Habilita la encriptación en reposo usando parámetros de StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Control de acceso

Usa RBAC para restringir quién puede crear/modificar StatefulSets y PVCs:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Políticas de red

Restringir la comunicación entre pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Optimización de rendimiento

Capas de rendimiento de almacenamiento

Elige StorageClasses adecuadas según la carga de trabajo:

  • Alta IOPS: Bases de datos con lectura/escritura aleatoria intensiva (gp3, io2)
  • Alto throughput: Agregación de logs, análisis (st1, sc1)
  • Equilibrada: Aplicaciones generales (gp3)

Distribución de pods

Usa anti-affinidad de pods para distribuir pods de StatefulSet entre zonas de disponibilidad:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Solicitudes y límites de recursos

Establece recursos adecuados para un rendimiento consistente:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Patrones avanzados

Aplicación con estado con contenedores de inicialización

Usa contenedores de inicialización para la inicialización de bases de datos:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Pods con múltiples contenedores para sidecars

Añade sidecars de respaldo o agentes de monitoreo:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... configuración de mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps para configuración dinámica

Separa la configuración de la definición de StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config

Enlaces útiles