
Un StatefulSet administra Pods que necesitan una identidad estable, almacenamiento persistente y un orden controlado de creación o actualización. Estas propiedades lo vuelven adecuado para bases de datos, pero no convierten automáticamente varias réplicas en un clúster de alta disponibilidad.

En esta guía desplegarás una instancia de PostgreSQL orientada a laboratorio. El objetivo es comprender la relación entre StatefulSet, Service headless, Pod, PersistentVolumeClaim y PersistentVolume antes de evaluar una topología productiva.
Advertencia: no utilices este ejemplo como arquitectura de producción sin agregar backups, restauración probada, monitorización y automatización específica de PostgreSQL. Aumentar replicas no configura replicación, failover ni elección de primario.Qué se va a construir
La solución tendrá:
- Un StatefulSet con una réplica llamada
postgres-0. - Un Service headless para proporcionar identidad DNS estable.
- Un Service convencional para las conexiones de aplicaciones.
- Un PVC creado mediante
volumeClaimTemplates. - Un Secret para la contraseña.
- Probes de arranque y disponibilidad.
- Una prueba de persistencia después de recrear el Pod.
Los StatefulSets proporcionan nombres ordinales, almacenamiento estable y despliegue ordenado. Cada entrada de volumeClaimTemplates genera un PVC por Pod. Documentación oficial de StatefulSet.
Qué no resuelve un StatefulSet
Un StatefulSet no configura por sí solo:
- Replicación entre instancias.
- Consenso o elección de primario.
- Promoción automática de réplicas.
- Backups y restauración.
- Migraciones de esquema.
- Recuperación ante corrupción lógica.
- Distribución segura de lecturas y escrituras.
Para producción suele utilizarse un operador específico del motor. Los operadores pueden automatizar backups, restauraciones, actualizaciones y tareas que Kubernetes no conoce de forma nativa. Patrón Operator en Kubernetes.
Requisitos previos
Necesitas:
- Un clúster Kubernetes operativo.
kubectlconfigurado.- Permisos para crear StatefulSets, Services, Secrets y PVC.
- Un StorageClass con aprovisionamiento dinámico o un PV preparado manualmente.
- Capacidad suficiente en los nodos y en el backend de almacenamiento.
- Un entorno de prueba sin datos productivos.
Comprueba el contexto y la comunicación con el clúster:
kubectl config current-context
kubectl version
kubectl cluster-info
kubectl get nodesVerifica tus permisos:
kubectl auth can-i create statefulsets.apps -n donweb-lab
kubectl auth can-i create persistentvolumeclaims -n donweb-lab
kubectl auth can-i create secrets -n donweb-labPaso 1: comprobar el almacenamiento
Lista los StorageClass disponibles:
kubectl get storageclassIdentifica cuál está marcado como predeterminado y revisa sus propiedades:
kubectl describe storageclass NOMBRE_STORAGECLASSComprueba especialmente:
Provisioner.ReclaimPolicy.VolumeBindingMode.AllowVolumeExpansion.
Los volúmenes creados dinámicamente heredan la política de recuperación del StorageClass. Si no se especifica otra, normalmente es Delete: eliminar el PVC puede provocar también la eliminación del volumen subyacente. StorageClass y políticas de recuperación.
Paso 2: crear el namespace
kubectl create namespace donweb-lab \
--dry-run=client \
-o yaml |
kubectl apply -f -Comprueba el resultado:
kubectl get namespace donweb-labPaso 3: crear el Secret
Crea un archivo temporal protegido y escribe una contraseña ficticia o de laboratorio:
install -m 600 /dev/null postgres-password.txt
${EDITOR:-vi} postgres-password.txtCrea el Secret sin imprimir su contenido:
kubectl create secret generic postgres-auth \
--namespace donweb-lab \
--from-file=POSTGRES_PASSWORD=./postgres-password.txtElimina la copia local:
rm -f postgres-password.txtComprueba únicamente los metadatos:
kubectl get secret postgres-auth -n donweb-labNo utilices kubectl get secret -o yaml durante comprobaciones rutinarias. Los valores están codificados en Base64, pero eso no equivale a cifrado. Kubernetes recomienda cifrado en reposo, RBAC de mínimo privilegio y, cuando corresponda, un almacén externo. Buenas prácticas para Secrets.
Paso 4: preparar el manifiesto
Guarda lo siguiente como postgres-statefulset.yaml:
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
labels:
app.kubernetes.io/name: postgres
spec:
clusterIP: None
selector:
app.kubernetes.io/name: postgres
ports:
- name: postgres
port: 5432
targetPort: postgres
---
apiVersion: v1
kind: Service
metadata:
name: postgres-rw
labels:
app.kubernetes.io/name: postgres
spec:
selector:
app.kubernetes.io/name: postgres
ports:
- name: postgres
port: 5432
targetPort: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
labels:
app.kubernetes.io/name: postgres
spec:
serviceName: postgres-headless
replicas: 1
podManagementPolicy: OrderedReady
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: Retain
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/name: postgres
template:
metadata:
labels:
app.kubernetes.io/name: postgres
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 60
containers:
- name: postgres
image: postgres:17-alpine
imagePullPolicy: IfNotPresent
ports:
- name: postgres
containerPort: 5432
env:
- name: POSTGRES_DB
value: appdb
- name: POSTGRES_USER
value: app
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-auth
key: POSTGRES_PASSWORD
startupProbe:
exec:
command:
- sh
- -ec
- 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
periodSeconds: 5
failureThreshold: 30
readinessProbe:
exec:
command:
- sh
- -ec
- 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
labels:
app.kubernetes.io/name: postgres
spec:
accessModes:
- ReadWriteOncePod
resources:
requests:
storage: 10GiReadWriteOncePod restringe el volumen a un único Pod en todo el clúster y es la opción recomendada para producción cuando el controlador CSI la soporta. Si el provisioner no es compatible, evalúa ReadWriteOnce y documenta sus implicaciones.
En producción, sustituye la etiqueta de la imagen por un digest verificado. Ajusta CPU, memoria, almacenamiento y probes utilizando mediciones reales.
No se incluye una livenessProbe en el ejemplo: una comprobación mal diseñada puede reiniciar una base de datos sana pero temporalmente saturada. Kubernetes advierte que probes de vida incorrectas pueden causar fallos en cascada. Guía oficial de probes.
Paso 5: validar antes de aplicar
Consulta el esquema correcto. El borrador utilizaba deployment.spec, aunque el recurso es un StatefulSet:
kubectl explain statefulset.spec
kubectl explain statefulset.spec.volumeClaimTemplates
kubectl explain statefulset.spec.persistentVolumeClaimRetentionPolicyRealiza la validación en el servidor sin persistir cambios:
kubectl apply \
--namespace donweb-lab \
--dry-run=server \
-f postgres-statefulset.yamlRevisa las diferencias:
kubectl diff \
--namespace donweb-lab \
-f postgres-statefulset.yamlkubectl diff devuelve código 1 cuando existen diferencias y un código mayor cuando ocurre un error. No añadas || true indiscriminadamente, porque ocultaría fallos reales de autenticación, validación o conexión.
Paso 6: aplicar y observar
kubectl apply \
--namespace donweb-lab \
-f postgres-statefulset.yamlEspera a que finalice el despliegue:
kubectl rollout status \
statefulset/postgres \
--namespace donweb-lab \
--timeout=5mObserva los recursos:
kubectl get statefulset,pod,service,pvc -n donweb-lab
kubectl get persistentvolume
kubectl get events -n donweb-lab --sort-by=.lastTimestampEl PVC esperado se llamará:
data-postgres-0El nombre combina el template data con el Pod postgres-0. Si el PVC permanece Pending, no continúes: revisa el StorageClass, el modo de acceso, la capacidad, la topología de nodos y los eventos.
Paso 7: comprobar DNS y disponibilidad
Comprueba PostgreSQL desde el propio Pod:
kubectl exec -n donweb-lab postgres-0 -- \
pg_isready -U app -d appdbConsulta el nombre asignado:
kubectl exec -n donweb-lab postgres-0 -- hostname -fEl DNS estable dentro del namespace será:
postgres-0.postgres-headlessSu forma completa suele ser:
postgres-0.postgres-headless.donweb-lab.svc.cluster.localLas aplicaciones deben conectarse normalmente a postgres-rw:5432. El DNS individual resulta útil cuando la topología del motor necesita identificar una instancia concreta.
Paso 8: probar la persistencia
Ejecuta esta prueba únicamente en el laboratorio:
kubectl exec -n donweb-lab postgres-0 -- \
psql -U app -d appdb -c \
"CREATE TABLE IF NOT EXISTS validacion (mensaje text); INSERT INTO validacion VALUES ('persistente');"Elimina el Pod, pero no el PVC:
kubectl delete pod postgres-0 -n donweb-labStatefulSet creará otro Pod llamado postgres-0 y volverá a asociarlo con data-postgres-0.
Espera a que esté disponible:
kubectl rollout status \
statefulset/postgres \
-n donweb-lab \
--timeout=5mComprueba que el dato continúa:
kubectl exec -n donweb-lab postgres-0 -- \
psql -U app -d appdb -c \
"SELECT * FROM validacion;"La prueba demuestra persistencia ante la recreación del Pod. No demuestra backup, alta disponibilidad ni recuperación ante corrupción del volumen.
Actualizaciones y rollback
Consulta las revisiones:
kubectl rollout history statefulset/postgres -n donweb-labDespués de modificar la imagen, observa el rollout:
kubectl rollout status statefulset/postgres -n donweb-labPara volver a la plantilla anterior:
kubectl rollout undo statefulset/postgres -n donweb-labStatefulSet es un tipo admitido por kubectl rollout. Referencia oficial de rollout.
El rollback solo revierte la plantilla de los Pods. No restaura:
- Datos eliminados o corruptos.
- Cambios de esquema.
- Credenciales rotadas.
- Contenido de los PVC.
- Configuración externa al StatefulSet.
Si un rollout queda bloqueado por un Pod que nunca alcanza Ready, revierte primero la plantilla y revisa cuidadosamente si es necesario eliminar el Pod defectuoso para que sea recreado.
Escalado y alta disponibilidad
No ejecutes simplemente:
kubectl scale statefulset postgres --replicas=3Cada Pod recibiría identidad y volumen propios, pero PostgreSQL no comenzaría a replicar datos por ese motivo. Tampoco existirían promoción de primario, quorum ni enrutamiento de escrituras.
Para producción, utiliza una arquitectura validada para PostgreSQL, preferiblemente mediante un operador mantenido, que gestione:
- Inicialización de réplicas.
- Elección y promoción de primario.
- Backups consistentes.
- Restauración y recuperación a un punto temporal.
- Actualizaciones compatibles.
- Certificados y credenciales.
- Métricas de replicación y alertas.
La propia guía oficial de MySQL replicado aclara que su ejemplo didáctico no es una configuración de producción. Aplicación stateful replicada.
Eliminación y retención
La política del manifiesto es:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: RetainPor ello, eliminar o reducir el StatefulSet no debería eliminar automáticamente sus PVC. Compruébalo siempre:
kubectl get pvc -n donweb-labNo elimines un PVC hasta confirmar:
- Que existe un backup utilizable.
- Que se probó su restauración.
- Que el volumen ya no contiene datos necesarios.
- Qué
reclaimPolicytiene el PV.
Eliminar el PVC puede activar la eliminación del volumen subyacente. Kubernetes recomienda no asumir que será posible acceder al volumen después de borrar el claim.

Problemas frecuentes
El PVC permanece en Pending
Revisa:
kubectl describe pvc data-postgres-0 -n donweb-lab
kubectl get storageclass
kubectl get events -n donweb-lab --sort-by=.lastTimestampLas causas habituales son ausencia de StorageClass predeterminado, modo de acceso incompatible, falta de capacidad o restricciones de zona.
El Pod entra en CrashLoopBackOff
Consulta el contenedor actual y el anterior:
kubectl logs postgres-0 -n donweb-lab
kubectl logs postgres-0 -n donweb-lab --previous
kubectl describe pod postgres-0 -n donweb-labRevisa permisos del volumen, contraseña, memoria, compatibilidad de la imagen y archivos de datos creados por otra versión mayor.
El Service no tiene endpoints disponibles
kubectl get endpointslice -n donweb-lab \
-l kubernetes.io/service-name=postgres-rwComprueba que las etiquetas coincidan y que la readiness probe sea correcta.
El volumen no puede montarse
Busca eventos FailedAttachVolume, FailedMount o Multi-Attach. No fuerces la eliminación del Pod o del PVC hasta entender si el volumen continúa conectado a otro nodo.
El rollout queda detenido
Revisa el Pod ordinal que no alcanza Ready, sus logs y eventos. Un StatefulSet con OrderedReady no continúa hasta que la instancia precedente esté disponible.
Buenas prácticas para producción
- Usa un operador específico y mantenido para el motor.
- Conserva backups fuera del clúster y prueba restauraciones periódicamente.
- Define RPO y RTO antes de elegir la arquitectura.
- Separa credenciales, configuración y datos.
- Habilita cifrado en tránsito y en reposo.
- Aplica RBAC y NetworkPolicy de mínimo privilegio.
- Monitoriza capacidad, latencia, conexiones y replicación.
- Distribuye réplicas entre nodos y zonas solo cuando el motor esté configurado para ello.
- Fija imágenes por digest y planifica las actualizaciones de versión mayor.
- Trata snapshots de volumen y backups lógicos como mecanismos diferentes.

Conclusión
StatefulSet proporciona la base de identidad y almacenamiento que necesita una aplicación con estado. Su valor está en mantener nombres, DNS, PVC y orden de operación estables cuando los Pods se recrean.
La disponibilidad real de una base de datos depende además del motor, su estrategia de replicación, los backups y la capacidad de restaurarlos. Kubernetes organiza los recursos; no reemplaza la administración especializada de los datos.