Introducción
Los Deployments tratan las réplicas como elementos intercambiables. Sin embargo, las aplicaciones con estado suelen necesitar nombres de Pod estables, un orden de inicio predecible y almacenamiento que sobreviva a la sustitución de un Pod. Los StatefulSets combinan estas identidades estables con solicitudes de almacenamiento independientes para cada Pod.
Inspeccionarás el almacenamiento dinámico, desplegarás un StatefulSet con dos réplicas, escribirás datos, reemplazarás un Pod y comprobarás que tanto la identidad como los datos se conservan.
Distingue por separado tres identidades: el StatefulSet controla el número deseado de réplicas, cada Pod recibe un ordinal estable como -0 y cada ordinal recibe su propio PersistentVolumeClaim. El Pod puede reemplazarse mientras la solicitud de almacenamiento y los datos guardados permanecen.
Inspeccionar el almacenamiento dinámico
En este paso, inspecciona la StorageClass del clúster antes de solicitar almacenamiento.
Un PersistentVolumeClaim describe lo que necesita una carga de trabajo; una StorageClass describe cómo puede aprovisionarlo el clúster. Comprobar primero las clases disponibles evita crear una solicitud que nunca pueda vincularse.
cd /home/labex/project/stateful-storage
kubectl get storageclasses
kubectl get storageclass -o custom-columns='NAME:.metadata.name,PROVISIONER:.provisioner,DEFAULT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class'
La StorageClass predeterminada crea dinámicamente un PersistentVolume cuando una solicitud omite storageClassName. Esto evita tener que configurar un hostPath específico de cada nodo en los manifiestos del laboratorio.
Busca (default) en la tabla normal o true en la columna personalizada DEFAULT. El nombre del aprovisionador es un detalle de implementación de este entorno; lo importante es que exista una clase predeterminada.
En este entorno, el resultado es:
NAME PROVISIONER ...
standard (default) k8s.io/minikube-hostpath ...
NAME PROVISIONER DEFAULT
standard k8s.io/minikube-hostpath true
Por tanto, standard es la clase predeterminada. Como el StatefulSet omitirá storageClassName, Kubernetes seleccionará esta clase automáticamente.
Crear un StatefulSet y un Service sin clúster
En este paso, crearás un Service sin clúster para proporcionar DNS estable y un StatefulSet con una solicitud de almacenamiento por réplica.
Este manifiesto presenta varios campos relacionados entre sí. Léelos antes de aplicarlo:
clusterIP: Noneconvierte el Service en un Service sin clúster, de modo que el DNS pueda representar Pods individuales.serviceName: stateful-webvincula el StatefulSet con ese Service.volumeClaimTemplatescrea una solicitud cuyo nombre se forma a partir de la plantilla y el ordinal del Pod.volumeMountsmonta la solicitud propia de cada Pod en el directorio de documentos de NGINX.
Las etiquetas siguen utilizando la familiar selección de Pods mediante Service aprendida en el primer curso.
cd /home/labex/project/stateful-storage
cat <<'EOF' > stateful-web.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-stateful}
---
apiVersion: v1
kind: Service
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
clusterIP: None
selector: {app: stateful-web}
ports: [{name: http, port: 80, targetPort: http}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: stateful-web, namespace: practice-stateful}
spec:
serviceName: stateful-web
replicas: 2
selector: {matchLabels: {app: stateful-web}}
template:
metadata: {labels: {app: stateful-web}}
spec:
containers:
- name: web
image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
ports: [{name: http, containerPort: 80}]
volumeMounts: [{name: web-data, mountPath: /usr/share/nginx/html}]
volumeClaimTemplates:
- metadata: {name: web-data}
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 100Mi}}
EOF
Aplica el manifiesto y espera a que finalice el despliegue ordenado. Normalmente, los StatefulSets crean primero el ordinal 0 y después el ordinal 1, por lo que el segundo Pod puede aparecer un poco más tarde:
kubectl apply -f stateful-web.yaml
kubectl rollout status statefulset/stateful-web -n practice-stateful --timeout=90s
kubectl get pods,pvc -n practice-stateful
Los nombres de los Pods utilizan ordinales estables: stateful-web-0 y stateful-web-1. Cada solicitud incluye el nombre de la plantilla, el nombre del Pod y el ordinal.
Deberías ver las solicitudes web-data-stateful-web-0 y web-data-stateful-web-1, ambas en estado Bound. Esto significa que Kubernetes encontró o aprovisionó dinámicamente almacenamiento que satisface cada solicitud.
Los mensajes del despliegue muestran el comportamiento ordenado, seguidos de las dos solicitudes:
Waiting for 2 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...
pod/stateful-web-0 1/1 Running ...
pod/stateful-web-1 1/1 Running ...
persistentvolumeclaim/web-data-stateful-web-0 Bound ... 100Mi RWO standard
persistentvolumeclaim/web-data-stateful-web-1 Bound ... 100Mi RWO standard
Los identificadores de volumen generados cambian en cada ejecución. Concéntrate en los nombres estables de las solicitudes, el estado Bound, la capacidad solicitada y la StorageClass.
Escribir datos persistentes específicos de cada Pod
En este paso, escribirás contenido diferente en la solicitud montada de cada Pod.
Se utiliza kubectl exec ... -- sh -c porque la redirección de salida (>) debe ejecutarse dentro del contenedor. Sin sh -c, el shell local intentaría redirigir la salida en la máquina virtual en lugar de hacerlo dentro del Pod.
cd /home/labex/project/stateful-storage
kubectl exec stateful-web-0 -n practice-stateful -- sh -c 'echo persistent-zero > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-1 -n practice-stateful -- sh -c 'echo persistent-one > /usr/share/nginx/html/index.html'
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl exec stateful-web-1 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pods -n practice-stateful -o custom-columns='POD:.metadata.name,HOSTNAME:.spec.hostname,CLAIM:.spec.volumes[0].persistentVolumeClaim.claimName'
Los Pods ejecutan la misma imagen, pero montan solicitudes diferentes, por lo que sus datos no son intercambiables.
Los dos comandos cat deberían devolver cadenas distintas. Después, la tabla personalizada relaciona cada nombre estable de Pod con su solicitud, haciendo visible esta relación de almacenamiento.
persistent-zero
persistent-one
POD HOSTNAME CLAIM
stateful-web-0 stateful-web-0 web-data-stateful-web-0
stateful-web-1 stateful-web-1 web-data-stateful-web-1
El ordinal aparece en las tres columnas. Ese 0 o 1 repetido es la identidad que conecta un Pod con su propio almacenamiento.
Reemplazar un Pod y recuperar sus datos
En este paso, eliminarás el ordinal 0 y dejarás que el StatefulSet recree la misma identidad.
Eliminar un Pod administrado no equivale a reducir el número deseado de réplicas. El StatefulSet sigue necesitando dos Pods, así que crea un reemplazo llamado stateful-web-0 y vuelve a asociarle la solicitud reservada para ese ordinal.
cd /home/labex/project/stateful-storage
kubectl delete pod stateful-web-0 -n practice-stateful --wait=true
kubectl wait --for=condition=Ready pod/stateful-web-0 -n practice-stateful --timeout=90s
kubectl exec stateful-web-0 -n practice-stateful -- cat /usr/share/nginx/html/index.html
kubectl get pod stateful-web-0 -n practice-stateful -o jsonpath='pod={.metadata.name} claim={.spec.volumes[0].persistentVolumeClaim.claimName}{"\n"}'
El objeto Pod es nuevo, pero su nombre y su asociación con la solicitud permanecen estables. El archivo recuperado demuestra que los datos pertenecían al PersistentVolume y no al sistema de archivos del contenedor eliminado.
pod "stateful-web-0" deleted from practice-stateful namespace
pod/stateful-web-0 condition met
persistent-zero
pod=stateful-web-0 claim=web-data-stateful-web-0
La línea importante es persistent-zero: se escribió antes de la eliminación y se leyó desde el Pod de reemplazo.
Si el archivo se hubiera escrito fuera del directorio montado, habría desaparecido junto con el sistema de archivos del contenedor anterior. La persistencia depende de la ruta de montaje, no únicamente del nombre del StatefulSet.
Resumen
Has conectado la StorageClass predeterminada, el aprovisionamiento dinámico de PVC, un Service sin clúster, los ordinales estables de un StatefulSet y el almacenamiento independiente de cada Pod. Al reemplazar un Pod, se conservaron tanto su identidad como sus datos.


