Docker & Kubernetes

Guia Completo de StatefulSets em Kubernetes: Bancos de Dados e Workloads com Estado

11 min de leitura

Guia Completo de StatefulSets em Kubernetes: Bancos de Dados e Workloads com Estado

O que é StatefulSet? 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. 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 , e — 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)

<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&#039;-&#039; -f1 | rev)

echo &quot;Pod ordinal: $ordinal&quot;

if [ &quot;$ordinal&quot; -eq &quot;0&quot; ]; then

echo &quot;Master node detected&quot;

fi

volumeClaimTemplates:

  • metadata:

name: data

spec:

accessModes: [ &quot;ReadWriteOnce&quot; ]

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, &quot;mysql-2&quot; → 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: [ &quot;ReadWriteOnce&quot; ]

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: [&#039;sh&#039;, &#039;-c&#039;, &#039;until nslookup rabbitmq-service; do echo waiting for DNS; sleep 2; done&#039;]

volumeClaimTemplates:

  • metadata:

name: rabbitmq-data

spec:

accessModes: [ &quot;ReadWriteOnce&quot; ]

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 &quot;SHOW SLAVE STATUS\G&quot;</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção
Como Usar DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote em Produção

DaemonSets e Jobs em Kubernetes: Agentes e Tarefas em Lote Quando você começa...

GKE no GCP: Autopilot, Workload Identity e Cloud SQL Proxy: Do Básico ao Avançado
GKE no GCP: Autopilot, Workload Identity e Cloud SQL Proxy: Do Básico ao Avançado

GKE Autopilot: Gerenciamento Automático de Clusters Kubernetes O Google Kuber...

Network Policies em Kubernetes: Isolamento de Rede entre Pods: Do Básico ao Avançado
Network Policies em Kubernetes: Isolamento de Rede entre Pods: Do Básico ao Avançado

Network Policies em Kubernetes: Isolamento de Rede entre Pods Network Policie...