Alta disponibilidad de PostgreSQL con el operador Zalando Postgres: Guía de implementación de Kubernetes en múltiples nubes
Implemente PostgreSQL HA de nivel de producción con Zalando Operador en cualquier plataforma Kubernetes
: Por qué es importante la alta disponibilidad de PostgreSQL en Kubernetes
La ejecución de PostgreSQL en producción exige alta disponibilidad (HA). El tiempo de inactividad medido en minutos puede costar a las empresas millones de dólares en pérdida de ingresos, erosión de la confianza del cliente e incumplimiento de acuerdos de nivel de servicio. Kubernetes se ha convertido en la plataforma de facto para orquestar cargas de trabajo en contenedores, pero ejecutar servicios con estado como PostgreSQL en Kubernetes presenta desafíos únicos: administración de almacenamiento persistente, elección de líder, conmutación por error automática, orquestación de respaldo y agrupación de conexiones.
El operadorZalando Postgreses una solución de código abierto probada en batalla que Zalando, el minorista de moda en línea más grande de Europa, creó para gestionar cientos de clústeres PostgreSQL en producción. AprovechaPatronipara la elección de líderes basada en consenso,Spilocomo imagen de contenedor PostgreSQL,WAL-Gpara archivado continuo y recuperación en un momento dado, yPgBouncerpara agrupación de conexiones. Juntos, estos componentes ofrecen una implementación PostgreSQL totalmente automatizada y autorreparable que funciona en cualquier distribución Kubernetes, desde servicios de nube administrados como AWS EKS, Azure AKS y Google GKE hasta clústeres básicos que ejecutan k3s con Rancher.
En esta guía completa, exploraremos la arquitectura del operador Zalando Postgres, recorreremos la instalación y configuración en múltiples plataformas Kubernetes, profundizaremos en la replicación, las copias de seguridad, la recuperación ante desastres, el monitoreo y el ajuste de la producción. Al final, tendrá el conocimiento para implementar y operar clústeres PostgreSQL HA de nivel de producción en cualquier infraestructura Kubernetes.
Arquitectura del operador Zalando Postgres
Comprender la arquitectura es esencial antes de realizar la implementación. El operador Zalando Postgres sigue el patrón del operador Kubernetes: busca definiciones de recursos personalizados (CRD) de tipopostgresqly concilia el estado deseado con los recursos Kubernetes reales. Así es como encajan los componentes:
Spilo: La imagen del contenedor PostgreSQL
Spiloes la imagen Docker de Zalando que incluye PostgreSQL con Patroni, WAL-G y extensiones esenciales. Cada pod en StatefulSet ejecuta un contenedor Spilo. Mangos Spilo:
- Servidor PostgreSQL: el motor de base de datos en sí, compatible con las versiones 13 a 16
- Patroni: el agente HA que gestiona la elección, replicación y conmutación por error del líder
- WAL-G: herramienta de copia de seguridad básica y archivado WAL continuo
- pg_cron, pg_stat_statements, PostGIS: extensiones comúnmente necesarias preinstaladas
Patroni: Elección de líder y conmutación por error automática
Patroni es el corazón del mecanismo HA. Utiliza un almacén de configuración distribuida (DCS) para mantener el estado del clúster y realizar la elección del líder. En el contexto del operador de Zalando, Patroni utiliza el propioKubernetes APIcomo DCS (a través de Endpoints o ConfigMaps), eliminando la necesidad de un etcd externo o un clúster ZooKeeper.
Así es como funciona el proceso de conmutación por error de Patroni:
- Verificaciones de estado: cada agente Patroni monitorea continuamente su instancia PostgreSQL local e informa el estado al DCS.
- Bloqueo de líder: el principal tiene un bloqueo de líder en el DCS (un objeto de extremo Kubernetes). La cerradura tiene un TTL (por defecto 30 segundos).
- Detección de fallas: si el principal no puede renovar su bloqueo dentro del TTL, las réplicas detectan la ausencia.
- Elección: las réplicas elegibles compiten por el bloqueo de líder. Gana la réplica con el menor retraso de replicación.
- Promoción: la réplica ganadora se promociona a sí misma a primaria, actualiza el DCS y el punto final del servicio Kubernetes
masterse actualiza automáticamente. - Cercado: la antigua primaria está cercada (detenida o degradada a réplica) para evitar la división del cerebro.
Todo este proceso de conmutación por error normalmente se completa en15 a 30 segundos, lo que garantiza un tiempo de inactividad mínimo para sus aplicaciones.
WAL-G: Archivado y Backup Continuo
WAL-G es una herramienta de archivo de próxima generación para PostgreSQL que admite copias de seguridad en S3, Google Cloud Storage (GCS) y Azure Blob Storage. Proporciona:
- Copias de seguridad básicas: copias de seguridad físicas completas usando
pg_basebackup - Archivado WAL: envío de registros de escritura anticipada continua para recuperación en un momento dado
- Copias de seguridad delta: copias de seguridad incrementales que solo almacenan páginas modificadas
- Cifrado: cifrado AES-256 de copias de seguridad en reposo
- Compresión: compresión LZ4 o ZSTD para reducir los costos de almacenamiento
Kubernetes Jerarquía de recursos
Cuando crea un recurso personalizadopostgresql, el operador crea un conjunto completo de recursos Kubernetes para administrar el clúster. Comprender esta jerarquía es importante para la resolución de problemas y el monitoreo:
creados por el operador
- StatefulSet: gestiona los pods PostgreSQL con identidades de red estables e implementación ordenada
- Servicios: dos servicios ClusterIP:
<cluster-name>para el principal y<cluster-name>-replpara réplicas de lectura - Puntos finales: Patroni actualiza los puntos finales para que apunten al líder actual para una conmutación por error perfecta
- PodDisruptionBudgets (PDB): garantiza que al menos una instancia permanezca disponible durante interrupciones voluntarias
- Secrets: credenciales de superusuario, replicación y aplicación de PostgreSQL almacenadas como Kubernetes Secrets
- PersistentVolumeClaims (PVCs): un PVC por pod para PostgreSQL almacenamiento de datos
- Implementación de PgBouncer: agrupador de conexiones opcional implementado como una implementación separada con su propio servicio
en múltiples plataformas Kubernetes
Requisitos previos
Antes de instalar el operador Zalando Postgres, asegúrese de tener:
- Un clúster Kubernetes en ejecución (v1.25+)
kubectlconfigurado con acceso de administrador del clústerhelmv3 instalado- Un StorageClass predeterminado configurado
a través de Helm
El método de instalación recomendado utiliza Helm. Esto funciona de manera consistente en todas las plataformas Kubernetes:
# Add the Zalando Postgres Operator Helm repository
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm repo add postgres-operator-ui-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator-ui
helm repo update
# Create a dedicated namespace
kubectl create namespace postgres-operator
# Install the operator with custom values
helm install postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-operator \
--set configKubernetes.enable_pod_antiaffinity=true \
--set configKubernetes.pod_environment_configmap=postgres-pod-config \
--set configAwsOrGcp.aws_region=us-east-1 \
--set configLoadBalancer.db_hosted_zone=db.example.com \
--set configConnectionPooler.connection_pooler_default_cpu_request=500m \
--set configConnectionPooler.connection_pooler_default_memory_request=100Mi
# Optionally install the operator UI for visual management
helm install postgres-operator-ui postgres-operator-ui-charts/postgres-operator-ui \
--namespace postgres-operatorVerifique que el operador esté ejecutando:
kubectl get pods -n postgres-operator
# Expected output:
# NAME READY STATUS RESTARTS AGE
# postgres-operator-7f8b9c6d4-x2k9j 1/1 Running 0 2mAWS Especificaciones de implementación de EKS
Amazon EKS requiere una configuración específica para un rendimiento óptimo de PostgreSQL:
# EKS-specific Helm values (eks-values.yaml)
configAwsOrGcp:
aws_region: us-east-1
enable_ebs_gp3_migration: true
additional_secret_mount: "aws-iam-token"
configKubernetes:
enable_pod_antiaffinity: true
pod_environment_configmap: "postgres-pod-config"
spilo_privileged: false
storage_resize_mode: pvc
# Use EBS gp3 StorageClass for better performance
# Create the StorageClass first:
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-gp3-postgres
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "400"
encrypted: "true"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: truePara copias de seguridad WAL-G en EKS, configure roles de IAM para cuentas de servicio (IRSA):
# Create IAM policy for WAL-G S3 access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::my-pg-backups",
"arn:aws:s3:::my-pg-backups/*"
]
}
]
}Azure Implementación de AKS
Azure AKS utiliza el disco Azure para almacenamiento persistente y la identidad administrada para autenticación de respaldo:
# AKS-specific StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-premium-postgres
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
cachingMode: ReadOnly
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# WAL-G backup to Azure Blob Storage environment variables
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-pod-config
namespace: default
data:
AZURE_STORAGE_ACCOUNT: "pgbackupsstorage"
AZURE_STORAGE_ACCESS_KEY: "" # Use Managed Identity instead
WALG_AZ_PREFIX: "azure://pg-wal-backups/$(SCOPE)"
USE_WALG_BACKUP: "true"
USE_WALG_RESTORE: "true"
BACKUP_SCHEDULE: "0 2 * * *"
BACKUP_NUM_TO_RETAIN: "7"Implementación de Google GKE
El motor Google Kubernetes utiliza un disco persistente y una identidad de carga de trabajo para el acceso a la copia de seguridad de GCS:
# GKE StorageClass for SSD Persistent Disks
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-postgres
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# GCS backup configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-pod-config
namespace: default
data:
WALG_GS_PREFIX: "gs://my-pg-backups/$(SCOPE)"
USE_WALG_BACKUP: "true"
USE_WALG_RESTORE: "true"
GOOGLE_APPLICATION_CREDENTIALS: "/var/secrets/google/key.json"
BACKUP_SCHEDULE: "0 2 * * *"
BACKUP_NUM_TO_RETAIN: "7"Bare Metal k3s/Rancher con almacenamiento Longhorn
Para implementaciones locales, k3s proporciona una distribución Kubernetes liviana y Longhorn ofrece almacenamiento en bloques distribuido. Esta combinación es ideal cuando necesita control total sobre su infraestructura sin depender de un proveedor de nube.
# Install k3s on all nodes
# Master node:
curl -sfL https://get.k3s.io | sh -s - server \
--cluster-init \
--disable traefik \
--write-kubeconfig-mode 644
# Worker nodes:
curl -sfL https://get.k3s.io | sh -s - agent \
--server https://master-ip:6443 \
--token $(cat /var/lib/rancher/k3s/server/node-token)
# Install Longhorn for persistent storage
helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn \
--namespace longhorn-system \
--create-namespace \
--set defaultSettings.defaultReplicaCount=3 \
--set defaultSettings.defaultDataPath=/mnt/longhorn
# Install MetalLB for LoadBalancer services
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml
# Configure MetalLB IP pool
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: postgres-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.210PostgreSQL Clúster CRD Especificación
El núcleo de la implementación de un El clúster PostgreSQL con el operador Zalando es el recurso personalizadopostgresql. Este manifiesto YAML declara el estado deseado de su clúster y el operador lo concilia con la realidad. A continuación se muestra una especificación CRD completa y lista para producción:
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-production-cluster
namespace: databases
labels:
team: platform
environment: production
spec:
teamId: "platform"
volume:
size: 100Gi
storageClass: ebs-gp3-postgres
numberOfInstances: 3
enableConnectionPooler: true
enableReplicaConnectionPooler: true
connectionPooler:
numberOfInstances: 2
mode: transaction
schema: pooler
user: pooler
resources:
requests:
cpu: 500m
memory: 100Mi
limits:
cpu: "1"
memory: 256Mi
users:
app_user:
- superuser
- createdb
readonly_user: []
databases:
app_database: app_user
postgresql:
version: "16"
parameters:
shared_buffers: "2GB"
max_connections: "200"
work_mem: "64MB"
maintenance_work_mem: "512MB"
effective_cache_size: "6GB"
random_page_cost: "1.1"
effective_io_concurrency: "200"
wal_buffers: "64MB"
max_wal_size: "4GB"
min_wal_size: "1GB"
checkpoint_completion_target: "0.9"
default_statistics_target: "100"
log_statement: "ddl"
log_min_duration_statement: "1000"
idle_in_transaction_session_timeout: "600000"
lock_timeout: "30000"
statement_timeout: "60000"
patroni:
initdb:
encoding: "UTF8"
locale: "en_US.UTF-8"
data-checksums: "true"
pg_hba:
- hostssl all all 0.0.0.0/0 md5
- host all all 0.0.0.0/0 md5
ttl: 30
loop_wait: 10
retry_timeout: 10
synchronous_mode: false
synchronous_mode_strict: false
maximum_lag_on_failover: 33554432
resources:
requests:
cpu: "2"
memory: 8Gi
limits:
cpu: "4"
memory: 16Gi
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9187"
tolerations:
- key: "database"
operator: "Equal"
value: "postgres"
effect: "NoSchedule"
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload-type
operator: In
values:
- database
enableShmVolume: true
spiloRunAsUser: 101
spiloRunAsGroup: 103
spiloFSGroup: 103Clave CRD Campos explicados
- numberOfInstances: número total de pods. El operador designa automáticamente uno como principal y el resto como réplicas de streaming.
- enableConnectionPooler: implementa el sidecar PgBouncer para el servicio principal, lo que reduce la sobrecarga de conexión.
- enableReplicaConnectionPooler: implementa un PgBouncer independiente para el servicio de réplica, esencial para cargas de trabajo de lectura intensa.
- postgresql.parameters: parámetros de configuración directos de PostgreSQL pasados a
postgresql.conf. - patrones: configura el comportamiento de Patroni, incluido TTL, espera de bucle, tiempo de espera de reintento y modo de replicación sincrónica.
- volume.storageClass: se asigna a la StorageClass específica de la plataforma (EBS gp3 en AWS, Premium SSD en Azure, SSD PD en GCP, Longhorn en k3s).
- enableShmVolume: monta un
tmpfsen/dev/shmpara la memoria compartida PostgreSQL, fundamental para el rendimiento.
con PgBouncer
El modelo de proceso por conexión dePostgreSQL hace que sea costoso manejar una gran cantidad de conexiones de clientes. Cada conexión consume aproximadamente 10 MB de RAM. PgBouncer resuelve esto multiplexando miles de conexiones de clientes en un pequeño grupo de conexiones PostgreSQL reales.
El operador Zalando admite de forma nativa la implementación de PgBouncer. Cuando configuraenableConnectionPooler: trueen el CRD, el operador crea:
- Una implementación de PgBouncer con recuento de réplicas configurable
- Un servicio dedicado (
<cluster-name>-pooler) para conexiones agrupadas - Sincronización automática de credenciales con PostgreSQL
Modos de configuración de PgBouncer
# PgBouncer connection pooler modes:
#
# transaction (recommended for most workloads)
# - Connection returned to pool after each transaction
# - Best balance of efficiency and compatibility
# - Cannot use session-level features (prepared statements, temp tables)
#
# session
# - Connection held for entire client session
# - Full PostgreSQL compatibility
# - Lower pooling efficiency
#
# statement
# - Connection returned after each statement
# - Most efficient but most restrictive
# - Only works with autocommit queries
# Custom PgBouncer configuration via operator
spec:
connectionPooler:
numberOfInstances: 3
mode: transaction
schema: pooler
user: pooler
defaultPoolSize: 25
maxDBConnections: 100
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: "1"
memory: 256MiPara aplicaciones que necesitan declaraciones preparadas o funciones a nivel de sesión, conéctese directamente a los servicios PostgreSQL sin pasar por PgBouncer o utilice el modo de agrupaciónsessioncon la desventaja de una menor eficiencia de conexión.
Replicación PostgreSQL multirregional
Para aplicaciones globales que requieren lecturas de baja latencia desde múltiples ubicaciones geográficas o recuperación ante desastres entre regiones, la replicación multirregional es esencial. El operador de Zalando admite esto a través de clústeres en espera que se replican desde un clúster primario mediante replicación de transmisión o archivos WAL-G.
Configuración de replicación de transmisión por secuenciasLa replicación de streamingPostgreSQL es la base de HA en el operador Zalando. Funciona enviando registros de registro de escritura anticipada (WAL) desde el principal a las réplicas casi en tiempo real. El operador configura esto automáticamente, pero comprender los detalles ayuda con el ajuste y la resolución de problemas.
- Replicación síncrona: el principal espera a que al menos una réplica confirme la recepción de WAL antes de confirmar una transacción. Esto garantiza cero pérdida de datos (RPO=0) pero agrega latencia. Habilitar con
patroni.synchronous_mode: true. - Replicación asincrónica: el principal se confirma inmediatamente y envía WAL de forma asincrónica. Latencia ligeramente menor pero posible pérdida de datos durante la conmutación por error. Este es el valor predeterminado.
- Replicación en cascada: las réplicas se pueden replicar desde otras réplicas en lugar de desde la principal, lo que reduce la carga en la principal en clústeres grandes.
# Enable synchronous replication for zero data loss
spec:
patroni:
synchronous_mode: true
synchronous_mode_strict: false # Allow async if no sync replica available
synchronous_node_count: 1 # Number of sync replicas required
postgresql:
parameters:
synchronous_commit: "on" # Matches Patroni synchronous_mode
max_wal_senders: "10" # Maximum WAL sender processes
wal_keep_size: "1GB" # WAL retention for replica catch-up
hot_standby: "on" # Allow queries on replicas
hot_standby_feedback: "on" # Reduce query conflicts on replicasCopia de seguridad y recuperación decon WAL-G
Configuración de copia de seguridad WAL-G
La configuración adecuada de la copia de seguridad es fundamental para la recuperación ante desastres. El operador de Zalando integra WAL-G para realizar copias de seguridad continuas en el almacenamiento de objetos. Aquí hay una configuración completa para almacenamiento compatible con S3:
# ConfigMap for WAL-G backup configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-pod-config
namespace: databases
data:
# S3 backup configuration
AWS_ENDPOINT: "https://s3.us-east-1.amazonaws.com"
AWS_S3_FORCE_PATH_STYLE: "false"
AWS_REGION: "us-east-1"
WALG_S3_PREFIX: "s3://my-pg-backups/$(SCOPE)"
WALG_DISABLE_S3_SSE: "false"
WALG_S3_SSE: "aws:kms"
WALG_S3_SSE_KMS_ID: "arn:aws:kms:us-east-1:123456789:key/mrk-abcdef"
# Backup scheduling and retention
USE_WALG_BACKUP: "true"
USE_WALG_RESTORE: "true"
BACKUP_SCHEDULE: "0 1 * * *" # Daily at 1 AM UTC
BACKUP_NUM_TO_RETAIN: "14" # Keep 14 daily backups
# WAL archiving
WALG_COMPRESSION_METHOD: "zstd" # Better compression than lz4
WALG_DELTA_MAX_STEPS: "6" # Delta backups between full backups
WALG_UPLOAD_CONCURRENCY: "4" # Parallel upload streams
WALG_DOWNLOAD_CONCURRENCY: "4" # Parallel download for restore
WALG_UPLOAD_DISK_CONCURRENCY: "4" # Disk read concurrency
# Clone configuration
CLONE_AWS_ENDPOINT: "https://s3.us-east-1.amazonaws.com"
CLONE_AWS_REGION: "us-east-1"
CLONE_WALG_S3_PREFIX: "s3://my-pg-backups/$(CLONE_SCOPE)"
CLONE_METHOD: "CLONE_WITH_WALG"
CLONE_USE_WALG_RESTORE: "true"Recuperación de un punto en el tiempo (PITR)
PITR le permite restaurar su base de datos en cualquier momento específico, algo fundamental para recuperarse de una eliminación o corrupción accidental de datos. El operador de Zalando soporta PITR mediante el mecanismo de clonación:
# Clone a cluster with PITR
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-restored-cluster
namespace: databases
spec:
teamId: "platform"
volume:
size: 100Gi
storageClass: ebs-gp3-postgres
numberOfInstances: 3
postgresql:
version: "16"
clone:
cluster: "pg-production-cluster"
timestamp: "2026-04-11T14:30:00+00:00" # Restore to this exact moment
s3_wal_path: "s3://my-pg-backups/wal/16/pg-production-cluster"
s3_endpoint: "https://s3.us-east-1.amazonaws.com"
s3_access_key_id: "" # Use IAM role instead
s3_secret_access_key: "" # Use IAM role insteadCuando se aplica este CRD, el operador realiza los siguientes pasos:
- Encuentra la última copia de seguridad base antes de la marca de tiempo de destino
- Restaura la copia de seguridad base en el pod principal del nuevo StatefulSet
- Reproduce segmentos WAL hasta la marca de tiempo especificada
- Abre la base de datos para operaciones de lectura y escritura
- Configura la replicación de transmisión en los pods de réplica
Clúster en espera para recuperación ante desastres
Un clúster en espera se replica continuamente desde un clúster primario, lo que proporciona un respaldo activo que se puede promover durante un desastre. Esto es diferente de las réplicas dentro de un clúster: un clúster en espera es un recurso Kubernetes completamente independiente que puede ejecutarse en un espacio de nombres, clúster o incluso región diferente.
# Standby cluster in a different region
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-standby-eu
namespace: databases
spec:
teamId: "platform"
volume:
size: 100Gi
storageClass: managed-premium-postgres
numberOfInstances: 2
postgresql:
version: "16"
standby:
standby_host: "pg-production-cluster.databases.svc.cluster.local"
standby_port: "5432"
# Alternative: replicate from S3 WAL archive
# s3_wal_path: "s3://my-pg-backups/wal/16/pg-production-cluster"
enableConnectionPooler: true
enableReplicaConnectionPooler: truePara promover un clúster en espera a primario independiente (durante la recuperación ante desastres), simplemente elimine la secciónstandbydel CRD y aplique:
# Edit the standby cluster CRD to remove standby section
kubectl patch postgresql pg-standby-eu -n databases --type json \
-p '[{"op": "remove", "path": "/spec/standby"}]'
# The operator will promote the standby to primary
# Update your application DNS/service mesh to point to the new primaryMonitoreocon Prometheus y Grafana
El monitoreo integral no es negociable para implementaciones de producción PostgreSQL. El operador de Zalando admite la exportación de métricas Prometheus a través del sidecarpostgres_exporter. A continuación se explica cómo configurar una pila de monitoreo completa:
para Prometheus
# ServiceMonitor for PostgreSQL metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: postgres-monitor
namespace: databases
labels:
team: platform
release: prometheus
spec:
selector:
matchLabels:
team: platform
namespaceSelector:
matchNames:
- databases
endpoints:
- port: exporter
interval: 15s
scrapeTimeout: 10s
path: /metrics
relabelings:
- sourceLabels: [__meta_kubernetes_pod_label_spilo_role]
targetLabel: role
- sourceLabels: [__meta_kubernetes_pod_label_cluster_name]
targetLabel: cluster
---
# PodMonitor alternative (scrapes pods directly)
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: postgres-pod-monitor
namespace: databases
spec:
selector:
matchLabels:
application: spilo
podMetricsEndpoints:
- port: exporter
interval: 15s
path: /metricsMétricas clave depara monitorear
Estas son las métricas de PostgreSQL más importantes para realizar un seguimiento en sus paneles de Grafana:
- pg_stat_replication_lag: retraso de replicación en bytes y segundos. Alerta si el retraso excede su umbral de RPO.
- pg_stat_activity_count: conexiones activas por estado. Alerta sobre agotamiento del grupo de conexiones.
- pg_stat_database_tup_fetched/returned/inserted/updated/deleted: consultar métricas de rendimiento.
- pg_stat_bgwriter_buffers_checkpoint/clean/backend: eficiencia de la gestión del búfer.
- pg_database_size_bytes: crecimiento del tamaño de la base de datos a lo largo del tiempo para la planificación de capacidad.
- pg_locks_count— Contención de bloqueo. Alerta sobre espera excesiva de cerraduras.
- pg_stat_statements_calls/mean_time: consulta estadísticas de rendimiento para optimización.
- patroni_postgres_running: estado de salud de Patroni (1 = en ejecución, 0 = inactivo).
- patroni_master: qué pod es el principal actual (1 = maestro, 0 = réplica).
- pg_up: sonda de disponibilidad básica PostgreSQL.
Reglas de alerta
# PrometheusRule for PostgreSQL alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgres-alerts
namespace: databases
spec:
groups:
- name: postgresql.rules
rules:
- alert: PostgreSQLDown
expr: pg_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "PostgreSQL instance {{ $labels.instance }} is down"
- alert: PostgreSQLReplicationLag
expr: pg_stat_replication_pg_wal_lsn_diff > 100000000
for: 5m
labels:
severity: warning
annotations:
summary: "Replication lag is {{ $value }} bytes on {{ $labels.instance }}"
- alert: PostgreSQLHighConnections
expr: sum(pg_stat_activity_count) by (instance) > 180
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $value }} active connections on {{ $labels.instance }}"
- alert: PostgreSQLDeadlocks
expr: rate(pg_stat_database_deadlocks[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "Deadlocks detected on {{ $labels.datname }}"
- alert: PatroniFailover
expr: changes(patroni_master[5m]) > 0
labels:
severity: critical
annotations:
summary: "Patroni failover occurred in cluster {{ $labels.cluster }}"Guía de ajuste de producción deEl ajuste adecuado es esencial para extraer el máximo rendimiento de PostgreSQL en Kubernetes. Los siguientes parámetros deben ajustarse según los límites de recursos de su pod y las características de la carga de trabajo.
Configuración de memoria# For a pod with 16GB memory limit:
postgresql:
parameters:
# shared_buffers: 25% of total memory
shared_buffers: "4GB"
# effective_cache_size: 75% of total memory
# (tells planner how much OS cache to expect)
effective_cache_size: "12GB"
# work_mem: shared_buffers / (max_connections * 2)
# Conservative to prevent OOM
work_mem: "10MB"
# maintenance_work_mem: 5-10% of total memory
# Used for VACUUM, CREATE INDEX, ALTER TABLE
maintenance_work_mem: "1GB"
# temp_buffers: memory for temp tables per session
temp_buffers: "32MB"
# huge_pages: try to use huge pages (requires OS config)
huge_pages: "try"Configuración de WAL y punto de control
postgresql:
parameters:
# WAL settings
wal_buffers: "64MB" # 1/32 of shared_buffers, max 64MB
wal_compression: "zstd" # Compress WAL (PG 15+)
max_wal_size: "8GB" # Before forced checkpoint
min_wal_size: "2GB" # WAL disk reservation
wal_level: "replica" # Required for replication
# Checkpoint settings
checkpoint_completion_target: "0.9" # Spread I/O over 90% of interval
checkpoint_timeout: "15min" # Max time between checkpointsPlanificador de consultas y E/S
postgresql:
parameters:
# Cost parameters for SSD storage
random_page_cost: "1.1" # SSD: close to seq_page_cost
seq_page_cost: "1.0" # Sequential I/O baseline
effective_io_concurrency: "200" # Concurrent I/O for SSD
# Planner behavior
default_statistics_target: "200" # More accurate statistics
from_collapse_limit: 12 # JOIN planning threshold
join_collapse_limit: 12 # JOIN planning threshold
# Parallel queries
max_parallel_workers_per_gather: "4"
max_parallel_workers: "8"
max_parallel_maintenance_workers: "4"
parallel_tuple_cost: "0.01"
parallel_setup_cost: "1000"Conexión y registropostgresql:
parameters:
# Connection limits
max_connections: "200" # Keep low, use PgBouncer
superuser_reserved_connections: "5" # Reserve for admin access
# Logging for troubleshooting
log_statement: "ddl" # Log DDL statements
log_min_duration_statement: "500" # Log queries > 500ms
log_checkpoints: "on" # Log checkpoint activity
log_connections: "off" # Too noisy in production
log_disconnections: "off" # Too noisy in production
log_lock_waits: "on" # Log lock waits
log_temp_files: "0" # Log all temp file usage
log_autovacuum_min_duration: "1000" # Log slow autovacuum
# Statement timeouts
statement_timeout: "60000" # 60 second query timeout
lock_timeout: "30000" # 30 second lock timeout
idle_in_transaction_session_timeout: "600000" # 10 min idle txn timeoutActualizaciones continuas y actualizaciones de versión
Actualizaciones de la versión menor deEl operador maneja automáticamente las actualizaciones de versiones menores (por ejemplo, 16.2 a 16.3) cuando actualiza la etiqueta de imagen de Spilo. El operador realiza un reinicio continuo:
# Update the operator configuration to use a new Spilo image
helm upgrade postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-operator \
--set configGeneral.docker_image=ghcr.io/zalando/spilo-16:3.1-p1 \
--reuse-values
# The operator will perform rolling updates:
# 1. Restart replicas one at a time
# 2. Failover the primary to a freshly updated replica
# 3. Restart the old primary (now a replica)Actualizaciones de la versión principal deLas actualizaciones de versiones principales (por ejemplo, PostgreSQL 15 a 16) requieren una planificación más cuidadosa. El operador de Zalando admite actualizaciones importantes in situ mediantepg_upgrade:
# Step 1: Update the CRD to the new major version
spec:
postgresql:
version: "16" # Changed from "15"
# Step 2: The operator detects the version change and:
# a) Scales down the StatefulSet to 1 replica
# b) Runs pg_upgrade on the primary pod
# c) Scales back up to the desired numberOfInstances
# d) Replicas are rebuilt from the upgraded primary
# Step 3: Verify the upgrade
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "psql -c 'SELECT version()'"
# Step 4: Run ANALYZE to update statistics after upgrade
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "psql -c 'ANALYZE VERBOSE'"Consideraciones importantes para actualizaciones importantes:
- Realice siempre una copia de seguridad nueva antes de actualizar
- Pruebe primero la actualización en un clúster clonado
- Las actualizaciones importantes requieren tiempo de inactividad (normalmente entre 5 y 30 minutos, dependiendo del tamaño de la base de datos)
- Revise las notas de la versión PostgreSQL para conocer los cambios importantes
- Supervise el retraso de replicación de cerca después de la reconstrucción de la réplica
- Ejecute
ANALYZEen todas las bases de datos para regenerar las estadísticas del planificador de consultas
Patrones operativos avanzados
Copias de seguridad lógicas
Además de las copias de seguridad físicas WAL-G, el operador admite copias de seguridad lógicas utilizandopg_dump. Las copias de seguridad lógicas son útiles para migraciones entre versiones y restauraciones selectivas de tablas:
# Enable logical backups in the operator configuration
helm upgrade postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-operator \
--set configLogicalBackup.logical_backup_schedule="0 3 * * *" \
--set configLogicalBackup.logical_backup_s3_bucket="my-pg-logical-backups" \
--set configLogicalBackup.logical_backup_s3_region="us-east-1" \
--set configLogicalBackup.logical_backup_s3_sse="AES256" \
--reuse-values
# The operator creates a CronJob for each cluster that:
# 1. Connects to the primary PostgreSQL instance
# 2. Runs pg_dumpall (or pg_dump per database)
# 3. Compresses and uploads to S3Variables de entorno de pod personalizadas
Puede inyectar variables de entorno en pods de Spilo utilizando un ConfigMap. Esto es útil para configurar WAL-G, scripts personalizados o ajustar parámetros a nivel del sistema operativo:
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-pod-config
namespace: databases
data:
# Custom Spilo configurations
SPILO_CONFIGURATION: |
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 33554432
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
archive_mode: "on"
archive_timeout: 1800s
# Enable pg_stat_statements
POSTGRESQL_SHARED_PRELOAD_LIBRARIES: "bg_mon,pg_stat_statements,pgextwlist,pg_auth_mon,set_user,timescaledb,pg_cron,pg_stat_kcache"
# Cron jobs inside PostgreSQL
ENABLE_PG_CRON: "true"Políticas de red para seguridad
En producción, restrinja el acceso a la red a los pods PostgreSQL usando Kubernetes NetworkPolicies:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-network-policy
namespace: databases
spec:
podSelector:
matchLabels:
application: spilo
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: app-namespace
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
- protocol: TCP
port: 8008 # Patroni REST API
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9187 # Prometheus exporter
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443 # S3/GCS/Azure for backupsSolución de problemas problemas comunes
Incluso con la automatización, surgen problemas. A continuación se detallan los problemas más comunes y sus soluciones al ejecutar PostgreSQL con el operador Zalando:
1. Pod atascado en estado pendiente
# Check events for the pod
kubectl describe pod pg-production-cluster-0 -n databases
# Common causes:
# - No nodes with matching tolerations/affinity
# - Insufficient CPU or memory on nodes
# - PVC cannot be provisioned (check StorageClass)
# Fix: Check node resources and storage availability
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory
kubectl get pvc -n databases2. Retraso de replicación creciente
# Check replication status on the primary
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "psql -c 'SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag FROM pg_stat_replication'"
# Common causes:
# - Replica CPU/IO saturation
# - Network bandwidth limits
# - Long-running queries on replica blocking WAL replay
# - Insufficient wal_keep_size
# Fix: Check replica resources and cancel blocking queries
kubectl exec -it pg-production-cluster-1 -n databases -- \
su postgres -c "psql -c 'SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE state = \"active\" AND query_start < now() - interval \"5 minutes\"'"3. La conmutación por error no activa
# Check Patroni cluster status
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "patronictl list"
# Check Patroni logs
kubectl logs pg-production-cluster-0 -n databases -c postgres | grep -i patroni
# Manual failover (if auto-failover is stuck)
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "patronictl failover --candidate pg-production-cluster-1 --force"4. Fallos de copia de seguridad
# Check WAL-G backup status
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "envdir /run/etc/wal-e.d/env wal-g backup-list"
# Check backup CronJob logs
kubectl logs -l application=spilo,cluster-name=pg-production-cluster -n databases | grep -i wal-g
# Verify S3/GCS credentials
kubectl exec -it pg-production-cluster-0 -n databases -- \
su postgres -c "envdir /run/etc/wal-e.d/env aws s3 ls s3://my-pg-backups/"Mejores prácticas de seguridad
Proteger PostgreSQL en Kubernetes requiere un enfoque de defensa en profundidad:
- Cifrado TLS: habilite SSL para todas las conexiones de clientes. El operador puede proporcionar certificados automáticamente utilizando cert-manager.
- Gestión de secretos: utilice secretos Kubernetes (o administradores de secretos externos como Vault) para las credenciales de la base de datos. Nunca almacene contraseñas en ConfigMaps.
- RBAC: limita los permisos de la cuenta de servicio del operador. Utilice acceso con privilegios mínimos para los usuarios de la base de datos de la aplicación.
- NetworkPolicies: restrinja la comunicación entre pods como se muestra en la sección anterior.
- pg_hba.conf: configure la autenticación basada en host para restringir qué IP y usuarios pueden conectarse.
- Registro de auditoría: habilite la extensión
pgauditpara el registro de auditoría SQL en entornos regulados. - Almacenamiento cifrado: utilice StorageClasses cifrados (cifrado EBS, cifrado de disco Azure, etc.).
- Estándares de seguridad del pod: ejecute los pods de Spilo como no root con contextos de seguridad restringidos.
# Security-hardened CRD settings
spec:
spiloRunAsUser: 101
spiloRunAsGroup: 103
spiloFSGroup: 103
enableShmVolume: true
patroni:
pg_hba:
- hostssl all all 0.0.0.0/0 md5
- host replication standby all md5
- hostssl replication standby all md5
postgresql:
parameters:
ssl: "on"
ssl_min_protocol_version: "TLSv1.3"
password_encryption: "scram-sha-256"
additionalVolumes:
- name: postgres-tls
mountPath: /tls
secret:
secretName: pg-tls-cert
defaultMode: 0640Planificación de capacidad y dimensionamiento
El tamaño adecuado garantiza un rendimiento estable y rentabilidad. Utilice estas pautas como punto de partida y ajústelas según los datos de monitoreo de su carga de trabajo:
| CPU | MemoriaAlmacenamiento | Instancias|||
|---|---|---|---|---|
| 500m | 1Gi | 10Gi | 1 | |
| Pequeña producción | 2 núcleos | 8Gi | 50Gi SSD | 3 |
| Producción media | 4 núcleos | 16Gi | SSD200Gi | 3 |
| Gran producción | 8 núcleos | 32Gi | 500Gi SSD | 5 |
| Empresa/Análisis | 16+ núcleos | 64Gi+ | 1Ti+ SSD | 5+ |
Ejemplo completo de implementación de un extremo a otro
Juntemos todo con una implementación completa desde cero en un clúster Kubernetes nuevo:
# Step 1: Create namespace and configure storage
kubectl create namespace databases
kubectl create namespace postgres-operator
# Step 2: Install the Zalando Postgres Operator
helm repo add postgres-operator-charts \
https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm repo update
helm install postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-operator \
--set configKubernetes.enable_pod_antiaffinity=true \
--set configKubernetes.pod_environment_configmap=databases/postgres-pod-config
# Step 3: Create backup configuration
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-pod-config
namespace: databases
data:
USE_WALG_BACKUP: "true"
USE_WALG_RESTORE: "true"
WALG_S3_PREFIX: "s3://my-pg-backups/\$(SCOPE)"
BACKUP_SCHEDULE: "0 1 * * *"
BACKUP_NUM_TO_RETAIN: "14"
EOF
# Step 4: Deploy the PostgreSQL cluster
kubectl apply -f - <<EOF
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-app-cluster
namespace: databases
labels:
team: platform
spec:
teamId: "platform"
volume:
size: 50Gi
numberOfInstances: 3
enableConnectionPooler: true
enableReplicaConnectionPooler: true
users:
app_user:
- superuser
- createdb
databases:
app_db: app_user
postgresql:
version: "16"
parameters:
shared_buffers: "2GB"
work_mem: "64MB"
effective_cache_size: "6GB"
resources:
requests:
cpu: "2"
memory: 8Gi
limits:
cpu: "4"
memory: 16Gi
EOF
# Step 5: Wait for cluster readiness
kubectl wait --for=condition=Running postgresql/pg-app-cluster \
-n databases --timeout=300s
# Step 6: Verify cluster status
kubectl get postgresql -n databases
kubectl get pods -n databases -l cluster-name=pg-app-cluster
kubectl get svc -n databases -l cluster-name=pg-app-cluster
# Step 7: Get connection credentials
export PGPASSWORD=$(kubectl get secret app-user.pg-app-cluster.credentials.postgresql.acid.zalan.do \
-n databases -o jsonpath='{.data.password}' | base64 -d)
# Step 8: Connect and verify
kubectl run pg-client --rm -it --image=postgres:16 -n databases -- \
psql -h pg-app-cluster-pooler -U app_user -d app_db -c "SELECT version();"Conclusión
El operador Zalando Postgres transforma PostgreSQL en Kubernetes de un desafío operativo complejo a una implementación automatizada y manejable. Al aprovechar Patroni para una conmutación por error basada en consenso, Spilo para una imagen de contenedor con baterías incluidas, WAL-G para respaldo y recuperación continuos y PgBouncer para una agrupación de conexiones eficiente, obtiene una plataforma de base de datos de nivel de producción que funciona de manera consistente en los clústeres AWS EKS, Azure AKS, Google GKE y k3s bare-metal.
Las conclusiones clave de esta guía son:
- Automatice todo: permita que el operador se encargue de la administración de StatefulSet, la conmutación por error y la programación de copias de seguridad. La intervención manual debería ser la excepción.
- Monitoreo agresivo: implemente Prometheus y Grafana desde el primer día. El retraso en la replicación, el recuento de conexiones y la contención de bloqueos son sus primeras señales de advertencia.
- Planifique ante desastres: configure copias de seguridad WAL-G, pruebe PITR periódicamente y mantenga un clúster en espera para cargas de trabajo críticas.
- Ajuste para su carga de trabajo: los parámetros predeterminados de PostgreSQL son conservadores. Ajuste las configuraciones de Shared_buffers, work_mem y checkpoint según su asignación de recursos y patrones de consulta.
- Seguro de forma predeterminada: habilite TLS, utilice la autenticación SCRAM-SHA-256, restrinja el acceso a la red y cifre el almacenamiento en reposo.
- Pruebe las actualizaciones: clone siempre su clúster y pruebe las actualizaciones de versiones principales antes de aplicarlas a producción.
Con esta base integral, está bien equipado para implementar y operar clústeres PostgreSQL de alta disponibilidad en cualquier plataforma Kubernetes, sirviendo aplicaciones que exigen confiabilidad, rendimiento e integridad de datos a escala.