Introdução
Os Deployments tratam as réplicas como intercambiáveis. Aplicações stateful geralmente precisam de nomes estáveis para os Pods, uma ordem de inicialização previsível e armazenamento que sobreviva à substituição de Pods. Os StatefulSets combinam essas identidades com claims individuais para cada Pod.
Você examinará o armazenamento dinâmico, implantará um StatefulSet com duas réplicas, gravará dados, substituirá um Pod e comprovará que tanto a identidade quanto os dados permanecem disponíveis.
Acompanhe separadamente três identidades: o StatefulSet controla o número desejado de réplicas, cada Pod recebe um ordinal estável, como -0, e cada ordinal recebe seu próprio PersistentVolumeClaim. O Pod pode ser substituído enquanto o claim e os dados armazenados permanecem intactos.
Inspecionar o Armazenamento Dinâmico
Nesta etapa, examine a StorageClass do cluster antes de solicitar armazenamento.
Um PersistentVolumeClaim descreve o que uma aplicação precisa; uma StorageClass descreve como o cluster pode provisionar esse armazenamento. Verificar primeiro as classes disponíveis evita criar um claim que nunca consiga ser associado a um volume.
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'
A StorageClass padrão cria dinamicamente um PersistentVolume quando um claim não especifica storageClassName. Isso evita a necessidade de configurar um hostPath específico de um nó nos manifestos do laboratório.
Procure por (default) na tabela normal ou por true na coluna personalizada DEFAULT. O nome do provisionador é um detalhe de implementação deste ambiente; o importante é que exista uma classe padrão.
Neste ambiente, o resultado é:
NAME PROVISIONER ...
standard (default) k8s.io/minikube-hostpath ...
NAME PROVISIONER DEFAULT
standard k8s.io/minikube-hostpath true
Portanto, standard é a classe padrão. Como o StatefulSet não especificará storageClassName, o Kubernetes escolherá essa classe automaticamente.
Criar um StatefulSet e um Service Headless
Nesta etapa, crie um Service headless para fornecer DNS estável e um StatefulSet com um claim para cada réplica.
Esse manifesto apresenta vários campos relacionados entre si. Leia-os antes de aplicá-lo:
clusterIP: Nonetorna o Service headless, permitindo que o DNS represente cada Pod individualmente.serviceName: stateful-webassocia o StatefulSet a esse Service.volumeClaimTemplatescria um claim cujo nome é formado a partir do template e do ordinal do Pod.volumeMountsmonta o claim exclusivo de cada Pod no diretório de documentos do NGINX.
Os labels continuam realizando a conhecida seleção entre Service e Pods, apresentada no primeiro 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
Aplique o manifesto e aguarde a implantação ordenada. Normalmente, os StatefulSets criam primeiro o ordinal 0 e depois o ordinal 1, portanto o segundo Pod pode aparecer um pouco mais 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
Os nomes dos Pods usam ordinais estáveis: stateful-web-0 e stateful-web-1. Cada claim inclui o nome do template, o nome do Pod e o ordinal.
Você deverá ver os claims web-data-stateful-web-0 e web-data-stateful-web-1, ambos com status Bound. Isso significa que o Kubernetes encontrou ou provisionou dinamicamente um armazenamento capaz de atender a cada solicitação.
As mensagens da implantação tornam o comportamento ordenado visível, seguidas pelos dois claims:
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
Os IDs dos volumes gerados são diferentes a cada execução. Concentre-se nos nomes estáveis dos claims, no status Bound, na capacidade solicitada e na classe de armazenamento.
Gravar Dados Persistentes Específicos de Cada Pod
Nesta etapa, grave conteúdos diferentes no claim montado de cada Pod.
Usamos kubectl exec ... -- sh -c porque o redirecionamento de saída (>) precisa ser executado dentro do contêiner. Sem sh -c, o shell local tentaria redirecionar a saída na VM, e não dentro do 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'
Os Pods executam a mesma imagem, mas montam claims diferentes; por isso, seus dados não são compartilhados nem intercambiáveis.
Os dois comandos cat deverão retornar strings diferentes. Em seguida, a tabela personalizada relacionará cada nome estável de Pod ao respectivo claim, tornando visível essa relação de armazenamento.
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
O ordinal aparece nas três colunas. Esse 0 ou 1 repetido representa a identidade que associa um Pod ao seu próprio armazenamento.
Substituir um Pod e Recuperar seus Dados
Nesta etapa, exclua o ordinal 0 e deixe o StatefulSet recriar a mesma identidade.
Excluir um Pod gerenciado não é o mesmo que reduzir o número desejado de réplicas. O StatefulSet ainda exige dois Pods, portanto criará um substituto chamado stateful-web-0 e remontará o claim reservado para esse 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"}'
O objeto Pod é novo, mas seu nome e sua associação com o claim permanecem estáveis. O arquivo recuperado comprova que os dados pertenciam ao PersistentVolume, e não ao sistema de arquivos do contêiner excluído.
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
A linha mais importante é persistent-zero: ela foi gravada antes da exclusão e lida pelo Pod substituto.
Se o arquivo tivesse sido gravado fora do diretório montado, ele teria desaparecido junto com o sistema de arquivos do contêiner antigo. A persistência depende do caminho de montagem, e não apenas do nome do StatefulSet.
Resumo
Você relacionou a StorageClass padrão, o provisionamento dinâmico de PVCs, um Service headless, os ordinais estáveis de um StatefulSet e o armazenamento individual de cada Pod. Ao substituir um Pod, tanto sua identidade quanto seus dados foram preservados.


