Kubernetes ofrece varios tipos de controladores para gestionar cargas de trabajo, siendo Deployment el estándar para servicios sin estado. Sin embargo, las aplicaciones que requieren identidad estable y persistencia de datos necesitan mecanismos específicos, como los StatefulSets.
En un entorno sin estado, los contenedores son intercambiables; si uno falla o se reinicia, otro toma su lugar sin afectar la lógica de negocio, ya que no hay dependencia entre ellos ni almacenamiento local vinculado. En contraste, las aplicaciones con estado suelen tener relaciones jerárquicas (como maestro-esclavo) y dependen fuertemente de recursos externos.
El desafío principal al escalar o migrar pods con estado radica en mantener la correspondencia entre la instancia del pod y los recursos de datos asociados. StatefulSet resuelve esto asegurando identificadores únicos por pod y gestionando el ciclo de vida de los volúmenes de almacenamiento.
Gestión de la Identidad de Red y Topología
Una característica distintiva es la asignación de nombres estables. A diferencia de los pods creados por Deployments, cuyo nombre puede variar tras un reinicio, StatefulSet genera hostnames derivados del nombre del recurso y un índice secuencial (ej. pod-name-0, pod-name-1).
Para acceder a estos pods de manera fiable, Kubernetes utiliza servicios DNS internos. La clave es utilizar un servicio Headless. Al configurar la propiedad clusterIP como None, el servicio no asigna una IP virtual, sino que expone directamente los registros DNS para cada endpoint activo.
# Definición de Servicio Cabeza Sin Carga (Headless Service)
apiVersion: v1
kind: Service
metadata:
name: db-service-headless
spec:
clusterIP: None
ports:
- name: mysql-port
port: 3306
targetPort: 3306
selector:
component: database
Una vez aplicado este manifiesto, cada pod asociado obtendrá un registro DNS completo siguiendo el patrón:
db-service-headless.{namespace}.svc.cluster.local.{pod-index}
Ejemplo de despliegue de StatefulSet conectado a este servicio:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database-cluster
spec:
serviceName: db-service-headless
replicas: 2
selector:
matchLabels:
component: database
template:
metadata:
labels:
component: database
spec:
containers:
- name: mariadb
image: mariadb:10.5
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
Al verificar los pods con kubectl get pods, se observa que mantienen sus nombres fijos. Incluso si se eliminan manualmente, el controlador recreará exactamente el mismo pod con el mismo nombre, permitiendo que las conexiones DNS existentes sigan funcionando.
Control de Persistencia de Datos (PVC y PV)
La segunda pieza fundamental es el almacenamiento persistente. En Kubernetes, esto se logra mediante Volúmenes Persistentes (PV) y Reclamaciones de Volumen Persistentes (PVC).
Cuando un StatefulSet requiere acceso a disco, se define la esrtuctura de volúmenes dentro del especificador del pod. Aunque existe un volumen único por pod (mediante patrones de variables dinámicas), a menudo se utilizan clases de almacenamiento específicas para garantizar la ubicación física correcta.
A continuación, creamos un volúmen físico local simulando almacenamiento directo:
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-storage-backup
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
storageClassName: fast-local
local:
path: /mnt/data/persistent
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker-node-01
Posteriormente, generamos la solicitud de almacenamiento (PVC) que permite a los pods consumir ese espacio:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-disk-claim
spec:
storageClassName: fast-local
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
Para integrar esto con el StatefulSet, modificamos el manifiesto anterior añadiendo la sección volumeMounts:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database-cluster
spec:
serviceName: db-service-headless
replicas: 2
selector:
matchLabels:
component: database
template:
metadata:
labels:
component: database
spec:
volumes:
- name: storage-volume
persistentVolumeClaim:
claimName: app-disk-claim
containers:
- name: mariadb
image: mariadb:10.5
volumeMounts:
- mountPath: /var/lib/mysql
name: storage-volume
ports:
- containerPort: 3306
Al ejecutar el despliegue, podemos observar cómo el volumen se monta correctamente dentro del contenedor. Si ingresamos a la terminal del pod y guardamos un archivo de configuración en el directorio montado:
echo "config_updated" > /var/lib/mysql/settings.dat
Siguiente paso crítico: eliminar el pod. Dado el orden estricto de StatefulSet, el sistema detiene el pod antes de iniciar el siguiente. Al borrar el pod específico y verificar después del reinicio:
$ kubectl delete pod database-cluster-0
$ kubectl exec -it database-cluster-0 -- cat /var/lib/mysql/settings.dat
El contenido persistirá. Esto confirma que aunque el contenedor (el pod) sea efímero y se destruya, la capa de almacenamiento (PVC/PV) sobrevive y mantiene la integridad de los datos, restableciendo la conexión automáticamente al nuevo pod.
Análisis Comparativo de Comportamiento
StatefulSet introduce semánticas operativas distintas al modelo de replica set estándar:
- Ordenación: Los pods se crean y eliminan de forma secuencial (de 0 a N, y viceversa).
- Identidad Única: El hostname no cambia, facilitando la descubrimiento de services en clústeres distribuidos.
- Escalabilidad Controlada: Permite crecer o reducir instancias manteniendo la integridad de los nodos de datos.
Este enfoque es esencial para bases de datos relacionales, sistemas de colas y aplicaciones que requieren coordinación compleja, donde la pérdida de contexto o datos durante una reprogramación no es aceptable.