Postgres Operator (CloudNativePG)
Como o CloudNativePG opera um Postgres por cliente via operator pattern
Operator pattern
Kubernetes só entende nativamente objetos genéricos (Pod, Service, Deployment). Ele não sabe o que é “um Postgres com replicação e backup automático” — não existe esse conceito embutido. Um operator ensina isso ao cluster em duas partes: uma CRD (Custom Resource Definition) que declara um tipo de objeto novo na API do cluster, e um controller que roda dentro do cluster e implementa o control loop (ver orchestration-fundamentals) daquele objeto — observa instâncias da CRD e cria/ajusta os objetos nativos (StatefulSet, Service, Secret, PVC) necessários para realizar o que foi declarado.
CloudNativePG é o operator de Postgres deste cluster. A CRD que ele expõe é Cluster (postgresql.cnpg.io/v1), e o controller dele é quem lê um Cluster e materializa um Postgres de verdade a partir disso.
O Cluster deste chart
# charts/app/templates/postgresql.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
spec:
instances: {{ .Values.postgresql.instances }}
storage:
size: {{ .Values.postgresql.storage.size }}
bootstrap:
initdb:
database: {{ .Values.db.database }}
owner: {{ .Values.db.username }}
secret:
name: {{ include "app.dbSecretName" . }}
backup:
retentionPolicy: {{ .Values.postgresql.backup.retentionPolicy }}
barmanObjectStore:
destinationPath: {{ printf "s3://%s/%s" .Values.postgresql.backup.bucket (include "app.fullname" .) | quote }}
wal:
compression: gzip
data:
compression: gzip
Declarar isso não sobe um Postgres sozinho — é o controller do CloudNativePG que lê esse Cluster, cria o StatefulSet com o container postgres de verdade, o Service que os outros pods usam para conectar, e o Secret de credenciais (referenciando db.username, gerado a partir do SealedSecret — ver secrets-encryption). instances: 1 neste chart significa sem réplica de leitura — cada cliente roda uma única instância de Postgres, não um cluster de alta disponibilidade com failover automático. Isso é intencional para o tamanho de carga por cliente aqui, não uma limitação do CloudNativePG (que suporta múltiplas instâncias com failover).
Backup: streaming de WAL + base diária
barmanObjectStore é a integração do CloudNativePG com Barman, a ferramenta de backup/restore de Postgres que ele usa por baixo. Duas coisas acontecem em paralelo, não uma ou outra:
- Streaming contínuo de WAL (Write-Ahead Log) — toda transação commitada gera entradas de WAL, e o CloudNativePG as envia para o MinIO (S3) continuamente, à medida que são geradas. Isso é o que permite restaurar para “agora” ou para qualquer ponto no tempo entre dois backups base, não só para o horário exato de um snapshot.
- Backup base diário, agendado via a CRD
ScheduledBackup(schedule: {{ .Values.postgresql.backup.schedule }},0 0 3 * * *por padrão — 03:00) — uma cópia completa e consistente do banco naquele momento, que serve de ponto de partida para uma restauração (aplicar WAL a partir dali é mais rápido que reconstruir tudo desde o início dos tempos).
Restaurar significa criar um Cluster novo com bootstrap.recovery apontando para o mesmo barmanObjectStore de origem — o controller do CloudNativePG reconstrói o banco a partir do backup base mais próximo e replay do WAL até o ponto pedido. Nunca testado neste cluster; ver a recomendação em topology para testar antes de depender.
helm.sh/resource-policy: keep na anotação do Cluster é o que impede o Helm de apagar o Postgres se o release for desinstalado — sem essa anotação, um helm uninstall acidental apagaria dados de cliente junto com o resto do release.