StatefulSets & Armazenamento Persistente no Kubernetes

Implante aplicativos stateful com dimensionamento ordenado e dados persistentes

Kubernetes StatefulSets são a solução ideal para gerenciar aplicações stateful que exigem identidades estáveis, armazenamento persistente e padrões de implantação ordenados — essenciais para bancos de dados, sistemas distribuídos e camadas de cache.

Se você é novo no Kubernetes ou está configurando um cluster, considere explorar distribuições do Kubernetes como k3s ou MicroK8s para desenvolvimento, ou instalar Kubernetes com Kubespray para clusters de produção.

apresentação no café Esta imagem agradável foi gerada pelo modelo AI Flux 1 dev.

O que são StatefulSets?

StatefulSets são um objeto de API de carga de trabalho do Kubernetes projetado especificamente para gerenciar aplicações stateful. Ao contrário de Deployments, que tratam todos os pods como intercambiáveis, StatefulSets mantêm uma identidade única para cada pod com garantias sobre a ordem e a unicidade.

Funcionalidades Principais:

  • Identificadores de Rede Estáveis: Cada pod recebe um hostname previsível que persiste após reinícios
  • Armazenamento Persistente: Reivindicações de Volume Permanente dedicadas que acompanham os pods durante reschedules
  • Implantação Ordenada: Os pods são criados sequencialmente (0, 1, 2…) e terminam na ordem inversa
  • Atualizações Ordenadas: Atualizações em rolagem prosseguem na ordem, garantindo a estabilidade da aplicação

StatefulSets são fundamentais para aplicações como PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis e etcd — qualquer carga de trabalho onde a identidade do pod e a persistência dos dados importam.

Entendendo o Armazenamento Persistente no Kubernetes

O Kubernetes fornece uma camada abstrata de armazenamento sofisticada que desconecta o gerenciamento de armazenamento do ciclo de vida do pod:

Componentes de Armazenamento

PersistentVolume (PV): Um pedaço de armazenamento no cluster provisionado por um administrador ou criado dinamicamente via StorageClass. Os PVs existem independentemente dos pods.

PersistentVolumeClaim (PVC): Uma solicitação de armazenamento por um pod. Os PVCs se vinculam a PVs disponíveis que correspondem a seus requisitos (tamanho, modo de acesso, classe de armazenamento).

StorageClass: Define diferentes “classes” de armazenamento com diversos provisionadores (AWS EBS, GCE PD, Azure Disk, NFS, etc.) e parâmetros como replicação, camadas de desempenho e políticas de backup.

Modos de Acesso

  • ReadWriteOnce (RWO): Volume montado como leitura-escrita por um único nó
  • ReadOnlyMany (ROX): Volume montado como somente leitura por muitos nós
  • ReadWriteMany (RWX): Volume montado como leitura-escrita por muitos nós (requer backends de armazenamento especiais)

Arquitetura de Armazenamento do StatefulSet

StatefulSets usam volumeClaimTemplates para criar automaticamente PersistentVolumeClaims para cada réplica do pod. Isso é fundamentalmente diferente de Deployments:

Como volumeClaimTemplates Funcionam

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
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Quando você cria este StatefulSet:

  1. O Kubernetes cria o pod mysql-0 e o PVC data-mysql-0
  2. Em seguida, cria o pod mysql-1 e o PVC data-mysql-1
  3. Finalmente, cria o pod mysql-2 e o PVC data-mysql-2

Cada pod recebe seu próprio volume persistente de 10 GB dedicado. Se o mysql-1 for excluído ou rescheduleado, o Kubernetes o recria e reanexa o mesmo PVC data-mysql-1, preservando todos os dados.

Criando um Serviço Headless para StatefulSets

StatefulSets exigem um Serviço Headless para fornecer identidades de rede estáveis:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Isso torna-o um serviço headless
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Isso cria entradas DNS para cada pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Aplicações podem se conectar diretamente a instâncias específicas de pod usando esses nomes DNS estáveis.

Padrões de Implantação do StatefulSet

Para equipes que gerenciam implantações complexas no Kubernetes, Helm Charts oferecem uma maneira poderosa de empacotar e implantar StatefulSets com modelagem, versionamento e gerenciamento de dependências. O Helm simplifica o gerenciamento de configurações de StatefulSet em diferentes ambientes.

Escalando em Ordem

Ao escalar de 3 para 5 réplicas:

kubectl scale statefulset mysql --replicas=5

O Kubernetes cria os pods em ordem: mysql-3 → aguarda o Ready → mysql-4

Ao escalar de 5 para 3 réplicas:

kubectl scale statefulset mysql --replicas=3

O Kubernetes termina em ordem inversa: mysql-4 → aguarda a terminação → mysql-3

Atualizações em Rolagem

StatefulSets suportam duas estratégias de atualização:

OnDelete: Atualizações manuais — os pods só são atualizados quando você os exclui RollingUpdate: Atualizações automáticas sequenciais em ordem inversa

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Apenas atualize pods com ordinal >= 2

O parâmetro partition permite implantações canárias — você pode atualizar primeiro os pods com números mais altos e testar antes de implantar em todas as réplicas.

Boas Práticas de Armazenamento

Provisionamento Dinâmico

Sempre use StorageClasses para provisionamento dinâmico de volumes:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Habilita o redimensionamento de PVCs sem recriá-los reclaimPolicy: Retain mantém os dados do PV após a exclusão do PVC, Delete remove-os automaticamente

Políticas de Retenção de PVC

O Kubernetes 1.23+ suporta persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Mantenha os PVCs quando o StatefulSet for excluído
    whenScaled: Delete     # Exclua os PVCs ao escalar para baixo

Opções:

  • Retain: Mantenha os PVCs (comportamento padrão, mais seguro)
  • Delete: Exclua automaticamente os PVCs (útil para ambientes de desenvolvimento)

Estratégias de Backup

Snapshots de Volume: Use recursos de VolumeSnapshot para criar backups em pontos de tempo

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Backups no Nível da Aplicação: Use ferramentas como mysqldump, pg_dump ou Velero para backups específicos de banco de dados

Replicação Distribuída: Configure replicação no nível da aplicação (replicação do MySQL, replicação de streaming do PostgreSQL) como primeira linha de defesa

Casos de Uso Reais

Cluster de Banco de Dados (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Cache Distribuído (Redis)

Para clusters Redis, você precisa de ambos StatefulSet e configuração cuidadosa:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Fila de Mensagens (Kafka)

Kafka requer armazenamento persistente para logs e identidades de rede estáveis para coordenação de brokers:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Monitoramento e Solução de Problemas

Para uma referência abrangente dos comandos do Kubernetes usados nesta seção, veja o Kubernetes Cheatsheet.

Verificar o Status do StatefulSet

# Verificar detalhes do StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Verificar a ordem de criação e o status dos pods
kubectl get pods -l app=mysql -w

# Verificar o status do PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Problemas Comuns

Pod preso em Pendente: Verifique o status do PVC e a disponibilidade de armazenamento

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Armazenamento cheio: Expanda o PVC se a StorageClass permitir

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod não termina: Verifique bloqueios no nível da aplicação ou problemas de desmontagem de volume

kubectl delete pod mysql-0 --grace-period=0 --force

Métricas para Monitorar

  • Uso de Armazenamento: Monitore a capacidade e a porcentagem de uso do PVC
  • Desempenho de I/O: Rastreie IOPS, throughput e latência
  • Reinícios de Pod: Reinícios frequentes podem indicar problemas de armazenamento
  • Tempo de Vinculação do PVC: Vinculação lenta sugere problemas de provisionamento

Estratégias de Migração

Ao migrar para StatefulSets, certifique-se de que seu cluster Kubernetes esteja configurado corretamente. Para configurações de laboratório doméstico ou clusters pequenos, revise nossa comparação abrangente das distribuições do Kubernetes para escolher a plataforma certa para suas necessidades de carga de trabalho.

De Deployment para StatefulSet

  1. Crie o StatefulSet com volumeClaimTemplates
  2. Reduza o Deployment de forma suave
  3. Restaure os dados dos backups para os pods do StatefulSet
  4. Atualize as referências de DNS/Serviço
  5. Exclua o antigo Deployment e PVCs

Backup antes da migração

# Faça snapshot dos PVCs existentes
kubectl get pvc -o yaml > pvc-backup.yaml

# Crie snapshots de volume
kubectl apply -f volume-snapshot.yaml

Considerações de Segurança

Criptografia de Armazenamento

Ative criptografia em repouso usando parâmetros da StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Controle de Acesso

Use RBAC para restringir quem pode criar/modificar StatefulSets e PVCs:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Políticas de Rede

Restrinja a comunicação entre pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Otimização de Desempenho

Camadas de Desempenho de Armazenamento

Escolha as classes de StorageClass apropriadas com base na carga de trabalho:

  • Alta IOPS: Bancos de dados com leitura/escrita aleatória intensa (gp3, io2)
  • Alta Throughput: Agregação de logs, análise (st1, sc1)
  • Equilibrada: Aplicações gerais (gp3)

Distribuição de Pods

Use anti-affinity de pod para distribuir pods de StatefulSet entre zonas de disponibilidade:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Solicitações e Limites de Recursos

Defina recursos apropriados para desempenho consistente:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Padrões Avançados

Aplicação Stateful com Containers Iniciais

Use containers iniciais para inicialização de banco de dados:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Pods com Múltiplos Containers para Sidecars

Adicione sidecars de backup ou agentes de monitoramento:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... configuração do mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps para Configuração Dinâmica

Separe a configuração da definição do StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config