Entendendo Armazenamento em Kubernetes
Quando você inicia um contêiner em Kubernetes, o sistema de arquivos é efêmero. Isso significa que qualquer dado escrito durante a execução do Pod é perdido quando o contêiner termina ou é reiniciado. Para aplicações que precisam persistir dados—como bancos de dados, aplicações stateful ou logs—precisamos de uma solução de armazenamento persistente que viva além do ciclo de vida do Pod.
Kubernetes resolve esse problema através de dois conceitos fundamentais: Persistent Volumes (PV) e Persistent Volume Claims (PVC). Um Persistent Volume é um recurso de armazenamento no cluster que existe independentemente de qualquer Pod. Um Persistent Volume Claim é uma solicitação por esse armazenamento, feita por um Pod ou usuário. Pense em um PV como um imóvel disponível para aluguel e um PVC como um contrato de aluguel.
Storage Classes e Provisionamento Dinâmico
O Papel das Storage Classes
Uma Storage Class automatiza o provisionamento de Persistent Volumes. Em vez de um administrador criar manualmente cada PV, você define uma Storage Class que especifica como volumes devem ser criados: qual provedor de armazenamento usar, tipo de disco, replicação, e outras políticas. Quando um PVC solicita uma Storage Class específica, Kubernetes cria automaticamente um PV correspondente.
Diferentes ambientes requerem diferentes estratégias de armazenamento. Em um cluster on-premises, você pode usar iSCSI ou NFS. Na AWS, você usaria EBS ou EFS. No Google Cloud, seria PersistentDisk. A Storage Class abstrai esses detalhes, permitindo que aplicações sejam portáveis entre ambientes.
Exemplo Prático: Criando uma Storage Class
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
Este exemplo cria uma Storage Class chamada fast-storage que provisiona automaticamente volumes EBS na AWS. O campo provisioner indica qual plugin é responsável pela criação. Os parameters são específicos do provisionador—aqui estamos pedindo um volume gp3 com 3000 IOPS criptografado. O allowVolumeExpansion: true permite que o volume cresça sem perder dados. reclaimPolicy: Delete significa que quando o PVC for deletado, o volume será removido. volumeBindingMode: WaitForFirstConsumer otimiza a alocação aguardando que um Pod realmente use o volume antes de vinculá-lo, melhorando a performance em clusters com múltiplas zonas de disponibilidade.
Persistent Volumes e Claims na Prática
Fluxo de Funcionamento: PV e PVC
O workflow padrão funciona assim: primeiro, você cria um PVC especificando o tamanho necessário e a Storage Class desejada. Kubernetes detecta essa solicitação e, através da Storage Class especificada, provisiona automaticamente um PV (ou reclama um existente não vinculado). O PVC e PV são então ligados um ao outro. Por fim, você monta esse volume em um Pod especificando o nome do PVC.
Esse design de duas camadas oferece flexibilidade. Desenvolvedores crisp PVCs sem conhecer detalhes de infraestrutura. Administradores gerenciam Storage Classes e a infraestrutura subjacente. Ambos trabalham com abstrações apropriadas ao seu papel.
Exemplo Completo: PVC, Pod e Dados Persistentes
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-database-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-storage
resources:
requests:
storage: 50Gi
---
apiVersion: v1
kind: Pod
metadata:
name: postgres-pod
namespace: default
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
volumeMounts:
- name: db-storage
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_PASSWORD
value: "securepassword"
volumes:
- name: db-storage
persistentVolumeClaim:
claimName: app-database-pvc
Este manifesto cria um PVC solicitando 50Gi de armazenamento usando a Storage Class fast-storage que definimos anteriormente. O accessMode ReadWriteOnce indica que apenas um nó pode montar este volume simultaneamente em modo leitura-escrita (apropriado para um banco de dados). O Pod monta esse PVC no caminho /var/lib/postgresql/data, onde PostgreSQL armazenará dados. Mesmo que o Pod seja deletado, o volume e os dados perseveram.
Access Modes Explicados
Existem três modos de acesso em Kubernetes:
- ReadWriteOnce (RWO): Apenas um nó pode montar em leitura-escrita. Ideal para aplicações stateful como bancos de dados.
- ReadOnlyMany (ROX): Múltiplos nós podem montar em leitura. Útil para compartilhar dados estáticos entre várias réplicas.
- ReadWriteMany (RWX): Múltiplos nós podem montar em leitura-escrita. Requer provisionador que suporte isso (NFS, EFS, etc).
Nem todo provisionador suporta todos os modos. Um volume EBS na AWS, por exemplo, suporta apenas RWO.
Ciclo de Vida e Gestão de Volumes
Estados de um PersistentVolume
Um PV passa por vários estados durante sua vida. Inicialmente está Available (disponível para ser reclamado). Quando um PVC o vincula, passa para Bound (vinculado). Se o PVC que o vinculava for deletado, o comportamento depende da reclaimPolicy. Com Delete, o PV é imediatamente deletado. Com Retain, o PV permanece no cluster mas sai do estado Bound, permitindo que seja reivindicado manualmente. Com Recycle (deprecated), o PV é limpo e retorna ao estado Available.
Compreender esses estados é crítico para diagnosticar problemas. Se um PVC fica pendente indefinidamente, provavelmente não há PV disponível ou nenhuma Storage Class pode provisionar um. Se dados não são mais acessíveis após deletar um Pod, é provável que a reclaimPolicy tenha removido o volume.
Expandindo Volumes sem Perda de Dados
Kubernetes permite expandir PVCs on-the-fly, desde que a Storage Class tenha allowVolumeExpansion: true e o sistema de arquivos suporte redimensionamento (ext4, XFS, etc).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-database-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-storage
resources:
requests:
storage: 100Gi # Aumentado de 50Gi para 100Gi
Basta editar o PVC e aumentar o campo storage sob resources.requests. Kubernetes detecta a mudança, expande o volume no provisionador subjacente, e o sistema de arquivos no Pod é redimensionado automaticamente. A aplicação não precisa ser reiniciada.
Exemplo de Monitoramento e Troubleshooting
# Listar todos os PVs no cluster
kubectl get pv
# Obter informações detalhadas sobre um PV específico
kubectl describe pv nome-do-pv
# Listar PVCs em um namespace
kubectl get pvc -n seu-namespace
# Verificar por quais Pods um PVC está sendo usado
kubectl get pods -n seu-namespace --field-selector spec.volumes[*].persistentVolumeClaim.claimName=app-database-pvc
# Monitorar o status de um PVC durante provisionamento
kubectl describe pvc app-database-pvc -n seu-namespace
# Editar e expandir um PVC
kubectl edit pvc app-database-pvc -n seu-namespace
Se um PVC ficar em estado Pending, verifique: (1) se a Storage Class existe e está correta, (2) se há recursos disponíveis no cluster, (3) os logs do controlador de provisionamento com kubectl logs -n kube-system -l app=storage-provisioner.
Casos de Uso Reais e Padrões Avançados
StatefulSets com Storage Persistente
StatefulSets são ideais para aplicações que precisam de identidade estável e armazenamento persistente. Cada réplica recebe seu próprio PVC, nomeado previsivamente, garantindo que quando um Pod é recriado, ele recupera seu volume anterior.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-cluster
namespace: default
spec:
serviceName: mysql-headless
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: mysql-data
mountPath: /var/lib/mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-storage
resources:
requests:
storage: 20Gi
O volumeClaimTemplates é a chave aqui. Para cada réplica do StatefulSet, Kubernetes cria um PVC separado com nome mysql-data-mysql-cluster-0, mysql-data-mysql-cluster-1, etc. Se o Pod mysql-cluster-0 falhar e for recriado, ele automaticamente reclama mysql-data-mysql-cluster-0, recuperando todos os dados.
Snapshot e Backup de Volumes
Para proteção contra perda de dados, Kubernetes suporta snapshots de volumes (requer suporte do provisionador e CRD instalado).
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-snapshot-20240115
namespace: default
spec:
volumeSnapshotClassName: csi-snapshot-class
source:
persistentVolumeClaimName: app-database-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-restored-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-storage
dataSource:
name: mysql-snapshot-20240115
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
resources:
requests:
storage: 50Gi
O primeiro objeto cria um snapshot do PVC existente. O segundo cria um novo PVC restaurando a partir desse snapshot. Isso é invaluável para testes, desenvolvimento e recuperação de desastres.
Conclusão
Três pontos essenciais aprendidos: Primeiro, Persistent Volumes e Claims separam armazenamento da computação, permitindo que dados sobrevivam ao reinício de Pods enquanto abstraem detalhes de infraestrutura. Storage Classes automatizam esse provisionamento, tornando seu cluster escalável e portável entre ambientes diferentes. Segundo, entender access modes, reclaim policies e ciclo de vida é fundamental para operar Kubernetes em produção com confiança—erros nessa área resultam em perda de dados irrecuperável. Terceiro, StatefulSets combinados com volumeClaimTemplates e snapshots oferecem uma base sólida para aplicações stateful em Kubernetes, mas exigem planejamento cuidadoso de estratégia de backup e recuperação.