<h2>O que é StatefulSet?</h2>
<p>Um StatefulSet é um objeto do Kubernetes projetado para gerenciar aplicações com estado (stateful), onde cada réplica precisa manter uma identidade única e estável. Diferentemente de um Deployment, que trata todas as réplicas de forma intercambiável, um StatefulSet garante que cada pod tenha um nome persistente, um identificador ordinal e, se configurado, armazenamento dedicado que persiste mesmo após recriações. Isso é fundamental para aplicações como bancos de dados, sistemas de fila distribuídos e qualquer workload que mantenha dados locais ou dependa de descoberta de peers.</p>
<p>Quando você cria um StatefulSet com 3 réplicas, não obtém pods com nomes aleatórios como em um Deployment. Em vez disso, você recebe <code>meu-app-0</code>, <code>meu-app-1</code> e <code>meu-app-2</code> — sempre nesta ordem, sempre com estes nomes. Isso permite que outras aplicações ou componentes do próprio cluster façam descoberta de serviço previsível e confiável. A ordem de criação e exclusão também é garantida: os pods são criados e iniciados sequencialmente (0 → 1 → 2) e removidos em ordem reversa (2 → 1 → 0).</p>
<h2>Diferenças Fundamentais: StatefulSet vs Deployment</h2>
<h3>O Ciclo de Vida e Ordenação</h3>
<p>Um Deployment cria e destrói pods sem ordem específica e reatribui nomes sempre que um pod falha. Um StatefulSet, pelo contrário, respeita uma ordem rigorosa e mantém identidade estável. Se o pod <code>meu-banco-1</code> falha, o StatefulSet garante que a próxima instância também será chamada <code>meu-banco-1</code> e, se houver PersistentVolumeClaim associado, terá acesso aos mesmos dados.</p>
<h3>Armazenamento e PersistentVolumes</h3>
<p>StatefulSets suportam nativamente <code>volumeClaimTemplates</code>, que criam automaticamente PersistentVolumeClaims (PVCs) únicos para cada réplica. Um Deployment pode usar volumes compartilhados, mas cada pod não tem garantia de dados persistentes entre recriações. Com StatefulSet e volumeClaimTemplates, cada réplica <code>meu-banco-0</code>, <code>meu-banco-1</code> e <code>meu-banco-2</code> possui seu próprio volume que persiste independentemente.</p>
<h3>Descoberta de Serviço</h3>
<p>StatefulSets exigem um Serviço Headless (sem IP de cluster), que não faz balanceamento de carga e expõe cada pod individualmente via DNS. Um Deployment típico usa um Serviço regular que balanceia requisições. Isso permite que aplicações cliente conheçam e se conectem a instâncias específicas de um StatefulSet pelo nome: <code>meu-banco-0.meu-servico.default.svc.cluster.local</code>.</p>
<h2>Anatomia de um StatefulSet: Componentes Essenciais</h2>
<h3>Serviço Headless</h3>
<p>Toda aplicação stateful no Kubernetes exige um Serviço Headless para comunicação entre réplicas e descoberta DNS. Um Serviço Headless define <code>clusterIP: None</code>, o que faz com que o Kubernetes não aloque um IP de cluster virtual. Em vez disso, requisições DNS retornam os IPs individuais de cada pod.</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
name: mysql-service
labels:
app: mysql
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
name: mysql</code></pre>
<h3>StatefulSet com volumeClaimTemplates</h3>
<p>O StatefulSet abaixo demonstra uma configuração real para MySQL, incluindo persistência, descoberta ordenada e um init container que aguarda o serviço estar pronto. O campo <code>serviceName</code> vincula este StatefulSet ao Serviço Headless, e <code>volumeClaimTemplates</code> cria um PVC único para cada réplica.</p>
<pre><code class="language-yaml">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
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
livenessProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}
initialDelaySeconds: 30
periodSeconds: 10
initContainers:
- name: init-mysql
image: busybox
command:
- sh
- -c
- |
set -ex
ordinal=$(hostname | rev | cut -d'-' -f1 | rev)
echo "Pod ordinal: $ordinal"
if [ "$ordinal" -eq "0" ]; then
echo "Master node detected"
fi
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi</code></pre>
<h3>Explicação Detalhada dos Campos-Chave</h3>
<p>O campo <code>serviceName: mysql-service</code> liga este StatefulSet ao Serviço Headless, permitindo descoberta DNS por nome. O <code>volumeClaimTemplates</code> funciona como um template que gera um PVC para cada réplica: <code>data-mysql-0</code>, <code>data-mysql-1</code>, <code>data-mysql-2</code>. Cada pod automaticamente recebe um volume montado em <code>/var/lib/mysql</code> que persiste entre restarts. O <code>initContainer</code> usa um truque comum: extrai o ordinal do hostname (por exemplo, "mysql-2" → ordinal 2) para permitir configuração baseada em função.</p>
<h2>Casos de Uso Reais e Padrões de Implementação</h2>
<h3>Banco de Dados com Replicação</h3>
<p>Bancos de dados como MySQL, PostgreSQL e MongoDB exigem topologia conhecida e armazenamento persistente. Um StatefulSet permite que você configure um nó primário (ordinal 0) e réplicas secundárias que sabem para onde replicar dados. Cada réplica mantém seu próprio estado sem conflito.</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-service
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
name: postgres
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U postgres
initialDelaySeconds: 10
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 20Gi</code></pre>
<h3>Aplicações de Fila Distribuída (RabbitMQ, Kafka)</h3>
<p>Sistemas de fila como RabbitMQ e Kafka dependem de um cluster onde cada node precisa de identidade estável e dados persistentes. Um StatefulSet garante que quando um nó falha, sua identidade é preservada e pode se reintegrar ao cluster com seu estado anterior.</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rabbitmq
spec:
serviceName: rabbitmq-service
replicas: 3
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:3.12-management
ports:
- containerPort: 5672
name: amqp
- containerPort: 15672
name: management
env:
- name: RABBITMQ_DEFAULT_USER
value: rabbitmq
- name: RABBITMQ_DEFAULT_PASS
valueFrom:
secretKeyRef:
name: rabbitmq-secret
key: password
- name: RABBITMQ_NODENAME
value: rabbit@$(HOSTNAME).rabbitmq-service.default.svc.cluster.local
volumeMounts:
- name: rabbitmq-data
mountPath: /var/lib/rabbitmq
initContainers:
- name: wait-for-service
image: busybox
command: ['sh', '-c', 'until nslookup rabbitmq-service; do echo waiting for DNS; sleep 2; done']
volumeClaimTemplates:
- metadata:
name: rabbitmq-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi</code></pre>
<h3>Descoberta de Peers em um Cluster</h3>
<p>Em aplicações como Elasticsearch ou Consul, cada nó precisa descobrir e se comunicar com os outros. Com StatefulSet e Serviço Headless, cada pod conhece o DNS de seus peers: <code>elasticsearch-0.elasticsearch-service</code>, <code>elasticsearch-1.elasticsearch-service</code>, etc. Isso simplifica a configuração inicial do cluster.</p>
<h2>Monitoramento, Troubleshooting e Boas Práticas</h2>
<h3>Verificando o Status de um StatefulSet</h3>
<p>Para diagnosticar problemas, inicie obtendo informações sobre o StatefulSet e seus pods:</p>
<pre><code class="language-bash"># Listar StatefulSets e suas réplicas prontas
kubectl get statefulset
kubectl describe statefulset mysql
Verificar os pods criados com seus ordinais
kubectl get pods -l app=mysql
kubectl describe pod mysql-0
Verificar PVCs associados
kubectl get pvc
kubectl describe pvc data-mysql-0
Verificar descoberta DNS do Serviço Headless
kubectl exec -it mysql-0 -- nslookup mysql-service</code></pre>
<h3>Logs e Diagnostics</h3>
<p>Logs são essenciais para entender comportamento de replicação e erros de inicialização:</p>
<pre><code class="language-bash"># Ver logs do container principal
kubectl logs mysql-0
Ver logs do init container
kubectl logs mysql-0 -c init-mysql
Ver eventos recentes associados ao pod
kubectl describe pod mysql-0 | grep -A 10 Events
Executar comandos dentro do pod para debug
kubectl exec -it mysql-0 -- mysql -u root -p${MYSQL_ROOT_PASSWORD} -e "SHOW SLAVE STATUS\G"</code></pre>
<h3>Políticas de Escalamento Seguro</h3>
<p>StatefulSets não devem ser escalados agressivamente em aplicações de banco de dados. Escale sempre manualmente e com cuidado:</p>
<pre><code class="language-bash"># Escalar para 5 réplicas (cria mysql-3 e mysql-4 sequencialmente)
kubectl scale statefulset mysql --replicas=5
Reduzir para 2 réplicas (remove mysql-4, mysql-3, mysql-2 nesta ordem)
kubectl scale statefulset mysql --replicas=2
Atualizar a imagem (rolling update respeitando ordem)
kubectl set image statefulset/mysql mysql=mysql:8.1 --record</code></pre>
<h3>Dicas Práticas</h3>
<blockquote><p>Sempre use <code>partition</code> em estratégias de atualização para testar mudanças em um subset de réplicas antes de aplicar a todos os pods. Defina <code>podManagementPolicy: Parallel</code> apenas se sua aplicação tolera inicializações simultâneas, caso contrário deixe como <code>OrderedReady</code>.</p></blockquote>
<p>Use <code>PodDisruptionBudgets</code> para proteger a disponibilidade durante manutenção:</p>
<pre><code class="language-yaml">apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mysql-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: mysql</code></pre>
<p>Implemente health checks robustos (liveness e readiness probes) específicos para sua aplicação. Um StatefulSet com pods que falham nas verificações de saúde pode levar a um estado degradado que requer intervenção manual.</p>
<h2>Conclusão</h2>
<p>StatefulSets resolvem um problema fundamental do Kubernetes: como executar aplicações que precisam de identidade estável, ordem de inicialização garantida e armazenamento persistente. A combinação de um Serviço Headless, volumeClaimTemplates e nomes de pods previsíveis permite que bancos de dados, sistemas de fila e outros workloads com estado funcionem de forma confiável. A chave para dominar StatefulSets é entender que você não está apenas replicando containers — está orquestrando componentes de um sistema distribuído onde cada instância importa e tem um papel específico. Por fim, lembre-se que escalabilidade em aplicações stateful é diferente de stateless: procure sempre pelos documentos de sua aplicação específica para entender topologias de replicação, failover e backup antes de decidir pelo StatefulSet no Kubernetes.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" target="_blank" rel="noopener noreferrer">Kubernetes Official Documentation: StatefulSets</a></li>
<li><a href="https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-app/" target="_blank" rel="noopener noreferrer">Kubernetes Documentation: Running a Single-Instance Stateful Application</a></li>
<li><a href="https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-introduction.html" target="_blank" rel="noopener noreferrer">MySQL on Kubernetes Guide</a></li>
<li><a href="https://www.kubernetesbook.com/" target="_blank" rel="noopener noreferrer">StatefulSets Best Practices - The Kubernetes Book</a></li>
<li><a href="https://www.postgresql.org/about/news/postgresql-on-kubernetes-1676/" target="_blank" rel="noopener noreferrer">Deploying PostgreSQL on Kubernetes</a></li>
</ul>
<p><!-- FIM --></p>