Alta disponibilidad de PostgreSQL con PGO de datos crujientes: implementación empresarial de Kubernetes
Enterprise PostgreSQL HA con Crunchy Data PGO en Kubernetes
La ejecución de PostgreSQL en producción en Kubernetes exige más que un StatefulSet con un volumen persistente. Necesita conmutación por error automatizada, copia de seguridad continua y recuperación en un punto en el tiempo, agrupación de conexiones, cifrado TLS, monitoreo y la capacidad de implementar de manera consistente entre proveedores de nube y bare metal. Crunchy Data PGO (Postgres Operador) v5 ofrece todo esto a través de un único recurso personalizado nativo de Kubernetes, elPostgresClusterCRD, respaldado por componentes probados en batalla: Patroni para HA basada en consenso, pgBackRest para respaldo empresarial, PgBouncer para agrupación de conexiones y pgMonitor para observabilidad compatible con Prometheus.
Esta guía cubre todo lo necesario para llevar un clúster PostgreSQL administrado por PGO desde la implementación inicial hasta la operación de nivel de producción en AWS EKS, Azure AKS, GCP GKE y k3s bare metal con Rancher. Cada sección incluye valores concretos de YAML, Helm y procedimientos operativos que puede adaptar a su entorno.
Datos crujientes Arquitectura PGO v5
PGO v5 es una reescritura completa del operador Crunchy Postgres. Reemplaza los anteriores pgcluster/pgreplica/pgpolicy CRD con un único recursoPostgresClusterque describe de forma declarativa cada aspecto de una implementación PostgreSQL. El operador observa los cambios en este recurso y concilia los objetos Kubernetes subyacentes (StatefulSets, Services, ConfigMaps, Secrets, Jobs) para que coincidan con el estado deseado.
La arquitectura se basa en cuatro pilares.Patronise ejecuta como sidecar en cada módulo PostgreSQL y gestiona la elección del líder, la topología de replicación y la conmutación por error automática mediante el consenso distribuido nativo de Kubernetes.pgBackRestmaneja copias de seguridad completas, diferenciales e incrementales, además de archivado WAL continuo en almacenamiento de objetos (S3, GCS, Azure Blob) o PVC locales.PgBouncerproporciona una agrupación de conexiones liviana que protege a PostgreSQL de tormentas de conexiones.pgMonitorexpone métricas PostgreSQL a través de un sidecar exportador Prometheus para la integración con su pila de observabilidad existente.
El operador en sí no tiene estado: todo el estado persistente reside en el clúster PostgreSQL y su repositorio de respaldo. Esto significa que puede actualizar o reiniciar el operador sin afectar las bases de datos en ejecución. El bucle de reconciliación del operador es idempotente: aplicar la misma especificaciónPostgresClustervarias veces produce el mismo conjunto de objetos Kubernetes.
Instalación de PGO v5
PGO v5 se puede instalar a través de Helm o manifiestos kubectl directos. Se prefiere el enfoque Helm para la producción porque se integra claramente con los flujos de trabajo de GitOps y proporciona actualizaciones controladas por versiones.
# Add the Crunchy Data Helm repository
helm repo add crunchydata https://charts.crunchydata.com
helm repo update
# Install PGO into its own namespace
helm install pgo crunchydata/pgo \
--namespace postgres-operator \
--create-namespace \
--set controllerImages.cluster=registry.crunchydata.com/crunchydata/postgres-operator:ubi8-5.7.2-0 \
--set relatedImages.postgres_16=registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.6-1 \
--set relatedImages.pgbackrest=registry.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.54.1-0 \
--set relatedImages.pgbouncer=registry.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.23.1-0 \
--set relatedImages.pgexporter=registry.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-0.16.0-0Para entornos aislados, refleje las imágenes requeridas en su registro interno y actualice los valores derelatedImagesen consecuencia. PGO solo extraerá imágenes especificadas en los valores Helm o la especificaciónPostgresCluster; nunca accede a registros externos en tiempo de ejecución a menos que esté configurado explícitamente para hacerlo.
# Verify the operator is running
kubectl get pods -n postgres-operator
NAME READY STATUS RESTARTS AGE
pgo-controller-manager-7d8f9b6c4-xk2pt 1/1 Running 0 45sPostgresCluster CRD Especificación
ElPostgresClusterCRD es la superficie declarativa única para toda su implementación PostgreSQL. A continuación se muestra una especificación lista para producción que demuestra las secciones clave. Cada sección se analiza en detalle en partes posteriores de esta guía.
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: production-db
namespace: databases
spec:
postgresVersion: 16
image: registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.6-1
instances:
- name: pgha1
replicas: 3
resources:
requests:
cpu: "2"
memory: 8Gi
limits:
cpu: "4"
memory: 16Gi
dataVolumeClaimSpec:
storageClassName: gp3-csi
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
walVolumeClaimSpec:
storageClassName: gp3-csi
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
postgres-operator.crunchydata.com/cluster: production-db
tolerations:
- key: "workload"
operator: "Equal"
value: "database"
effect: "NoSchedule"
backups:
pgbackrest:
image: registry.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.54.1-0
configuration:
- secret:
name: pgbackrest-s3-creds
global:
repo1-retention-full: "7"
repo1-retention-diff: "14"
repo1-path: /pgbackrest/production-db/repo1
repo1-s3-uri-style: path
repos:
- name: repo1
schedules:
full: "0 2 * * 0" # Sunday 2am
differential: "0 2 * * 1-6" # Mon-Sat 2am
incremental: "0 */4 * * *" # Every 4 hours
s3:
bucket: mycompany-pg-backups
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1
proxy:
pgBouncer:
image: registry.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.23.1-0
replicas: 2
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
config:
global:
pool_mode: transaction
max_client_conn: "1000"
default_pool_size: "25"
min_pool_size: "5"
reserve_pool_size: "5"
reserve_pool_timeout: "3"
monitoring:
pgmonitor:
exporter:
image: registry.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-0.16.0-0
resources:
requests:
cpu: 100m
memory: 128Mi
patroni:
dynamicConfiguration:
synchronous_mode: true
postgresql:
parameters:
shared_buffers: 2GB
effective_cache_size: 6GB
work_mem: 32MB
maintenance_work_mem: 512MB
max_connections: 200
wal_buffers: 64MB
checkpoint_completion_target: 0.9
random_page_cost: 1.1
effective_io_concurrency: 200
min_wal_size: 1GB
max_wal_size: 4GB
max_worker_processes: 8
max_parallel_workers_per_gather: 4
max_parallel_workers: 8
log_min_duration_statement: 500
log_checkpoints: "on"
log_connections: "on"
log_disconnections: "on"
log_lock_waits: "on"
users:
- name: appuser
databases: ["appdb"]
options: "NOSUPERUSER"
- name: readonly
databases: ["appdb"]
options: "NOSUPERUSER"
databaseInitSQL:
name: init-sql-configmap
key: init.sqlEsta especificación crea un clúster PostgreSQL 16 de tres instancias con datos y volúmenes WAL separados, copias de seguridad respaldadas por S3 en un cronograma completo/diferencial/incremental, agrupación de conexiones PgBouncer en modo de transacción, exportación de métricas Prometheus y replicación sincrónica de Patroni. La regla antiafinidad del pod garantiza que las tres instancias aterricen en diferentes nodos Kubernetes.
HA basado en Patroni y conmutación por error automática
Patroni es el marco HA integrado en cada pod PostgreSQL administrado por PGO. Utiliza Kubernetes API como su almacén de configuración distribuida (DCS); no se requiere un etcd o un clúster ZooKeeper por separado. Patroni monitorea continuamente el estado del PostgreSQL primario y de las réplicas. Cuando la principal deja de responder, Patroni inicia una conmutación por error automática: promueve la réplica más actualizada a principal y reconfigura las réplicas restantes para seguir al nuevo líder.
El proceso de conmutación por error en PGO funciona de la siguiente manera. Patroni en cada pod tiene un bloqueo de líder en Kubernetes (a través de objetos Endpoints u ConfigMap). El primario actual debe renovar este bloqueo en un intervalo configurable (TTL predeterminado de 10 segundos, espera de bucle de 3 segundos). Si el primario no se renueva (porque falló, el nodo murió o la red lo dividió), una réplica que esté más cercana a la posición WAL del primario adquirirá el bloqueo y se promocionará a sí misma. Todo el proceso normalmente se completa en 10 a 30 segundos.
El modo de replicación síncrona, habilitado a través desynchronous_mode: trueen la configuración Patroni, garantiza cero pérdida de datos (RPO = 0) a costa de una latencia de escritura ligeramente mayor. En modo síncrono, una transacción no se reconoce al cliente hasta que al menos una réplica haya confirmado la recepción del WAL. Si no hay una réplica sincrónica disponible, Patroni desactiva temporalmente el modo sincrónico para mantener la disponibilidad; puede anular esto consynchronous_mode_strict: truesi prefiere sacrificar la disponibilidad por coherencia.
# Check Patroni cluster status
kubectl exec -it production-db-pgha1-0 -n databases -- patronictl list
+----------------------------+-------------------+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+----------------------------+-------------------+---------+---------+----+-----------+
| production-db-pgha1-0 | 10.244.0.15 | Leader | running | 3 | |
| production-db-pgha1-1 | 10.244.1.22 | Replica | running | 3 | 0 |
| production-db-pgha1-2 | 10.244.2.18 | Replica | running | 3 | 0 |
+----------------------------+-------------------+---------+---------+----+-----------+
# Manual switchover (planned, zero downtime)
kubectl exec -it production-db-pgha1-0 -n databases -- \
patronictl switchover --master production-db-pgha1-0 --candidate production-db-pgha1-1 --force
# Manual failover (forced, for emergencies)
kubectl exec -it production-db-pgha1-1 -n databases -- \
patronictl failover --candidate production-db-pgha1-1 --forcePGO configura automáticamente los servicios Kubernetes para rastrear al líder Patroni. El servicioproduction-db-primarysiempre apunta al pod que actualmente tiene el bloqueo líder, por lo que las aplicaciones que se conectan a través de este servicio experimentan una conmutación por error perfecta con solo un breve restablecimiento de la conexión.
pgBackRest Copia de seguridad y restauración
pgBackRest es el motor de respaldo integrado en PGO. Admite tres tipos de copia de seguridad (completa, diferencial e incremental), además de archivado WAL continuo para recuperación en un momento dado (PITR). Comprender cómo encajan es esencial para diseñar una estrategia de respaldo que equilibre el costo de almacenamiento, la velocidad del respaldo y el tiempo de recuperación.
Una copia de seguridad completa decopia todo el directorio de datos de PostgreSQL y es la base para todos los demás tipos de copias de seguridad. Una copia de seguridad diferencialcopia solo las páginas que cambiaron desde la última copia de seguridad completa. Una copia de seguridad incrementalcopia solo las páginas que cambiaron desde la última copia de seguridad de cualquier tipo. Durante la restauración, pgBackRest encadena automáticamente las copias de seguridad requeridas; por ejemplo, la restauración desde una incremental requiere la incremental más la diferencial anterior (o completa) más la copia de seguridad completa, más cualquier segmento WAL necesario para alcanzar el punto de recuperación de destino.
Configuración del programa de respaldo
La programación de la copia de seguridad se define en la secciónreposde la configuración de pgBackRest dentro de la especificaciónPostgresCluster. Un programa de producción sólido normalmente ejecuta copias de seguridad semanales completas, diferenciales diarias y incrementales más frecuentes.
backups:
pgbackrest:
global:
repo1-retention-full: "4" # Keep 4 full backups
repo1-retention-full-type: count
repo1-retention-diff: "14" # Keep 14 differentials
repos:
- name: repo1
schedules:
full: "0 2 * * 0" # Weekly full: Sunday 2am
differential: "0 2 * * 1-6" # Daily diff: Mon-Sat 2am
incremental: "0 */6 * * *" # Every 6 hours
s3:
bucket: mycompany-pg-backups
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1Copia de seguridad y restauración manual
# Trigger a manual full backup
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/pgbackrest-backup="$(date +%s)" \
--overwrite
# Check backup status
kubectl exec -it production-db-pgha1-0 -n databases -- \
pgbackrest info --stanza=db
# Restore to a specific point in time
# First, update the PostgresCluster spec:
spec:
backups:
pgbackrest:
restore:
enabled: true
repoName: repo1
options:
- --type=time
- --target="2026-04-12 08:30:00+00"
- --target-action=promote
# Apply the restore annotation
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/pgbackrest-restore="$(date +%s)" \
--overwriteDurante una restauración PITR, PGO apaga todas las instancias PostgreSQL, restaura desde la copia de seguridad diferencial o completa más cercana, reproduce segmentos WAL hasta el tiempo objetivo y luego inicia el clúster. Toda la operación la organiza el operador; no se necesita intervención manual en módulos individuales.
Agrupación de conexiones PgBouncer
PostgreSQL crea un nuevo proceso para cada conexión de cliente. A escala (cientos o miles de módulos de aplicaciones, cada uno de los cuales mantiene grupos de conexiones), este modelo falla. PgBouncer se ubica entre su aplicación y PostgreSQL, multiplexando muchas conexiones de clientes a través de una cantidad menor de conexiones de servidor.
PGO implementa PgBouncer como una implementación separada con su propio servicio. El servicioproduction-db-pgbounceres a lo que deben conectarse sus aplicaciones, no directamente al servicio principal PostgreSQL. PgBouncer admite tres modos de grupo:
- Agrupación de sesiones: se asigna una conexión de servidor a un cliente durante la vida útil de la conexión del cliente. Más seguro, pero menos eficiente.
- Agrupación de transacciones: se asigna una conexión de servidor solo durante la duración de una transacción. Más eficiente para cargas de trabajo web. Este es el valor predeterminado recomendado.
- Agrupación de declaraciones: se asigna una conexión de servidor para una sola declaración. Solo funciona para cargas de trabajo simples y no transaccionales.
proxy:
pgBouncer:
replicas: 3
config:
global:
pool_mode: transaction
max_client_conn: "2000"
default_pool_size: "30"
min_pool_size: "10"
reserve_pool_size: "10"
reserve_pool_timeout: "3"
server_idle_timeout: "300"
server_lifetime: "3600"
client_idle_timeout: "600"
tcp_keepalive: "1"
tcp_keepidle: "30"
tcp_keepintvl: "10"
tcp_keepcnt: "3"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
postgres-operator.crunchydata.com/role: pgbouncerTenga en cuenta la configuración detcp_keepalive: son fundamentales cuando PgBouncer se ejecuta detrás de un equilibrador de carga en la nube o el servicio Kubernetes. Sin medidas de mantenimiento agresivas, los componentes intermedios de la red pueden interrumpir silenciosamente las conexiones inactivas, lo que provoca errores de aplicación en el siguiente intento de consulta.
pgMonitor y Prometheus/Grafana Monitoreo
La integración de monitoreo dePGO implementa un sidecarcrunchy-postgres-exporteren cada módulo PostgreSQL. Este exportador extrae las vistas de estadísticas internas de PostgreSQL y las expone como métricas Prometheus en el puerto 9187. El exportador cubre más de 150 métricas listas para usar, incluidas conexiones, retraso de replicación, tasas de transacciones, tasas de aciertos de caché, estadísticas de tablas e índices, contención de bloqueo y tasas de generación de WAL.
# ServiceMonitor for Prometheus Operator auto-discovery
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: postgres-monitoring
namespace: databases
labels:
release: kube-prometheus-stack
spec:
selector:
matchLabels:
postgres-operator.crunchydata.com/cluster: production-db
postgres-operator.crunchydata.com/crunchy-postgres-exporter: "true"
endpoints:
- port: exporter
interval: 15s
scrapeTimeout: 10sCrunchy Data proporciona un conjunto de paneles Grafana prediseñados que puede importar directamente. Estos cubren la descripción general de PostgreSQL, el estado de replicación, el estado de la copia de seguridad de pgBackRest, las estadísticas de PgBouncer y el uso de recursos a nivel de pod. Importarlos a través del aprovisionamiento del panel de Grafana o manualmente desde el repositorio de ejemplos de Crunchy Data.
# Install Grafana dashboards as ConfigMaps
kubectl create configmap grafana-pg-overview \
--from-file=pg-overview.json=dashboards/pg-overview.json \
-n monitoring
kubectl label configmap grafana-pg-overview grafana_dashboard=1 -n monitoringReglas de alerta críticapara PostgreSQL
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgres-alerts
namespace: databases
labels:
release: kube-prometheus-stack
spec:
groups:
- name: postgresql-health
rules:
- alert: PostgresReplicationLagHigh
expr: ccp_replication_lag_size_bytes > 104857600
for: 5m
labels:
severity: warning
annotations:
summary: "Replica {{ $labels.pod }} lag exceeds 100MB"
- alert: PostgresConnectionsExhausted
expr: ccp_connection_stats_active / ccp_connection_stats_max_connections > 0.85
for: 2m
labels:
severity: critical
annotations:
summary: "PostgreSQL connections above 85% capacity"
- alert: PostgresDeadlockDetected
expr: rate(ccp_stat_database_deadlocks[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "Deadlocks detected in {{ $labels.datname }}"
- alert: PostgresCacheHitRatioLow
expr: ccp_stat_database_blks_hit / (ccp_stat_database_blks_hit + ccp_stat_database_blks_read) < 0.95
for: 10m
labels:
severity: warning
annotations:
summary: "Cache hit ratio below 95% for {{ $labels.datname }}"
- alert: PgBackRestStaleBackup
expr: time() - ccp_backrest_last_full_backup_time_since_completion_seconds > 604800
for: 1h
labels:
severity: critical
annotations:
summary: "No full backup in the last 7 days"AWS Implementación de EKS
La implementación de PGO en AWS EKS requiere configurar el controlador CSI de EBS para volúmenes persistentes y funciones de IAM para cuentas de servicio (IRSA) para el acceso a la copia de seguridad de S3. Este enfoque evita almacenar credenciales AWS de larga duración en secretos Kubernetes.
# Install the EBS CSI driver (required for gp3 volumes)
eksctl create addon --name aws-ebs-csi-driver --cluster my-cluster --region eu-west-1 \
--service-account-role-arn arn:aws:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole
# Create a gp3 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-csi
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true# IAM policy for pgBackRest S3 access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mycompany-pg-backups",
"arn:aws:s3:::mycompany-pg-backups/*"
]
}
]
}
# Create an IRSA-enabled service account
eksctl create iamserviceaccount \
--name pgbackrest-sa \
--namespace databases \
--cluster my-cluster \
--attach-policy-arn arn:aws:iam::111122223333:policy/PGBackRestS3Policy \
--approveEn la especificaciónPostgresCluster, haga referencia al depósito y la región de S3. PGO utilizará las credenciales de la cuenta de servicio del pod (a través de IRSA) automáticamente; no se necesitan claves de acceso explícitas.
backups:
pgbackrest:
repos:
- name: repo1
s3:
bucket: mycompany-pg-backups
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1Para lograr resiliencia multi-AZ, asegúrese de que sus grupos de nodos EKS abarquen al menos tres zonas de disponibilidad y utilice las restricciones de distribución de topología que se mostraron anteriormente para distribuir los pods PostgreSQL entre ellos.
Azure Implementación de AKS
Azure AKS utiliza discos administrados para almacenamiento persistente y Azure Blob Storage para copias de seguridad de pgBackRest. La clase de almacenamiento recomendada utiliza Premium SSD v2 o Premium LRS para cargas de trabajo de bases de datos.
# StorageClass for Azure Premium SSD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: azure-premium-ssd
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
kind: Managed
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# pgBackRest with Azure Blob Storage
backups:
pgbackrest:
configuration:
- secret:
name: pgbackrest-azure-creds
global:
repo1-azure-account: mystorageaccount
repo1-retention-full: "7"
repos:
- name: repo1
schedules:
full: "0 2 * * 0"
differential: "0 2 * * 1-6"
azure:
container: pg-backups
---
# Secret with Azure credentials
apiVersion: v1
kind: Secret
metadata:
name: pgbackrest-azure-creds
namespace: databases
stringData:
azure.conf: |
[global]
repo1-azure-key=BASE64_STORAGE_ACCOUNT_KEYPara Workload Identity (el equivalente Azure de AWS IRSA), configure el clúster de AKS con el emisor OIDC y cree una credencial de identidad federada para la cuenta de servicio pgBackRest. Esto elimina la necesidad de almacenar las claves de las cuentas en secreto.
Implementación deGCP GKE
GKE utiliza disco persistente (pd-ssd) para almacenamiento y GCS para copias de seguridad de pgBackRest. GKE Workload Identity asigna cuentas de servicio Kubernetes a cuentas de servicio de Google Cloud para una autenticación segura y sin clave.
# StorageClass for GKE SSD Persistent Disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: pd-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# pgBackRest with GCS
backups:
pgbackrest:
configuration:
- secret:
name: pgbackrest-gcs-creds
repos:
- name: repo1
gcs:
bucket: mycompany-pg-backups
---
# GCS service account key secret
apiVersion: v1
kind: Secret
metadata:
name: pgbackrest-gcs-creds
namespace: databases
stringData:
gcs-key.json: |
{
"type": "service_account",
"project_id": "my-project",
...
}# Enable Workload Identity on GKE
gcloud container clusters update my-cluster \
--workload-pool=my-project.svc.id.goog
# Create and bind IAM service account
gcloud iam service-accounts create pgbackrest-gcs \
--display-name="pgBackRest GCS Access"
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:pgbackrest-gcs@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
gcloud iam service-accounts add-iam-policy-binding \
pgbackrest-gcs@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:my-project.svc.id.goog[databases/production-db-pgbackrest]"Recuperación ante desastres en varias regiones
PGO admite clústeres en espera para la recuperación ante desastres entre regiones. Un clúster en espera reproduce continuamente WAL desde el repositorio pgBackRest del clúster primario, manteniendo una copia activa que puede promoverse a primario independiente en caso de una falla regional.
# Standby cluster in Region B
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: production-db-standby
namespace: databases
spec:
postgresVersion: 16
image: registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.6-1
standby:
enabled: true
repoName: repo1
instances:
- name: pgha1
replicas: 2
dataVolumeClaimSpec:
storageClassName: gp3-csi
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
backups:
pgbackrest:
image: registry.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.54.1-0
configuration:
- secret:
name: pgbackrest-s3-creds
repos:
- name: repo1
s3:
bucket: mycompany-pg-backups
endpoint: s3.us-east-1.amazonaws.com
region: us-east-1Para promover el clúster en espera durante un desastre, configurestandby.enabled: falseen la especificación y aplique el cambio. PGO promueve el standby a un grupo primario independiente. Esta promoción es irreversible: deberá reconstruir la relación de replicación desde cero después de que se recupere la región primaria original.
Despliegue de metal desnudo k3s/Rancher
La ejecución de PGO en k3s sin sistema operativo con administración Rancher requiere atención cuidadosa al almacenamiento y las redes, ya que no tiene discos administrados por el proveedor de la nube ni balanceadores de carga.
Almacenamiento de cuerno largo
Longhorn es un sistema de almacenamiento en bloques distribuido y liviano para Kubernetes que es ideal para entornos bare metal. Replica volúmenes en múltiples nodos para mayor durabilidad y admite instantáneas, copias de seguridad y expansión de volúmenes.
# Install Longhorn via Helm
helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn \
--namespace longhorn-system \
--create-namespace \
--set defaultSettings.defaultReplicaCount=3 \
--set defaultSettings.storageOverProvisioningPercentage=100 \
--set defaultSettings.storageMinimalAvailablePercentage=15
---
# Longhorn StorageClass for PostgreSQL
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-pg
provisioner: driver.longhorn.io
parameters:
numberOfReplicas: "3"
staleReplicaTimeout: "2880"
fsType: ext4
dataLocality: best-effort
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: RetainMetalLB para equilibrio de carga
# Install MetalLB
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb --namespace metallb-system --create-namespace
---
# Configure IP address pool
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: pg-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.210
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: pg-l2
namespace: metallb-system
spec:
ipAddressPools:
- pg-poolCon MetalLB configurado, los servicios de PGO de tipoLoadBalancerrecibirán direcciones IP de su grupo, lo que hará que los puntos finales primarios PostgreSQL y PgBouncer sean directamente accesibles desde su red sin reenvío manual de puertos.
pgBackRest con PVC local o NFS para Bare Metal
# For environments without S3/GCS/Azure Blob, use a PVC-based repo
backups:
pgbackrest:
repos:
- name: repo1
schedules:
full: "0 2 * * 0"
differential: "0 2 * * 1-6"
volume:
volumeClaimSpec:
storageClassName: longhorn-pg
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 200GiPara entornos aislados, también puede configurar un repositorio pgBackRest secundario que envía copias de seguridad a un recurso compartido NFS o una instancia MinIO que se ejecuta localmente, proporcionando una copia de seguridad fuera del clúster sin dependencias de la nube.
TLS/SSL Configuración de cifrado
PGO genera certificados TLS autofirmados para todas las comunicaciones internas de forma predeterminada: las instancias PostgreSQL, la replicación, pgBackRest y PgBouncer se comunican a través de canales cifrados sin ninguna gestión manual de certificados. Sin embargo, para entornos de producción normalmente querrá utilizar certificados firmados por la CA de su organización.
# Create a TLS secret with your CA-signed certificates
kubectl create secret tls production-db-tls \
--cert=server.crt \
--key=server.key \
-n databases
kubectl create secret generic production-db-ca \
--from-file=ca.crt=ca.crt \
-n databases
---
# Reference in PostgresCluster spec
spec:
customTLSSecret:
name: production-db-tls
customReplicationTLSSecret:
name: production-db-replication-tlsPGO configura PostgreSQL conssl = ony establece los parámetrosssl_cert_file,ssl_key_fileyssl_ca_fileapropiados. PgBouncer está configurado de manera similar para requerir TLS para conexiones de clientes y para usar TLS cuando se conecta a backends PostgreSQL.
# Enforce TLS for all client connections via pg_hba.conf
spec:
patroni:
dynamicConfiguration:
postgresql:
pg_hba:
- hostssl all all 0.0.0.0/0 scram-sha-256
- hostssl replication all 0.0.0.0/0 scram-sha-256Configuración personalizada de PostgreSQL
PGO expone la configuración de PostgreSQL a través de la sección de configuración dinámica de Patroni. Este es el enfoque recomendado porque Patroni garantiza que todas las instancias mantengan una configuración consistente y maneja los reinicios cuando sea necesario.
patroni:
dynamicConfiguration:
postgresql:
parameters:
# Memory
shared_buffers: 4GB
effective_cache_size: 12GB
work_mem: 64MB
maintenance_work_mem: 1GB
huge_pages: "try"
# WAL
wal_buffers: 128MB
min_wal_size: 2GB
max_wal_size: 8GB
checkpoint_completion_target: 0.9
wal_compression: lz4
# Query Planning
random_page_cost: 1.1
effective_io_concurrency: 200
default_statistics_target: 200
# Parallelism
max_worker_processes: 16
max_parallel_workers_per_gather: 4
max_parallel_workers: 8
max_parallel_maintenance_workers: 4
# Connections
max_connections: 200
idle_in_transaction_session_timeout: 60000
statement_timeout: 300000
# Logging
log_min_duration_statement: 250
log_checkpoints: "on"
log_connections: "on"
log_disconnections: "on"
log_lock_waits: "on"
log_temp_files: 0
log_autovacuum_min_duration: 0
# Autovacuum Tuning
autovacuum_max_workers: 5
autovacuum_naptime: 30
autovacuum_vacuum_cost_limit: 800
autovacuum_vacuum_scale_factor: 0.02
autovacuum_analyze_scale_factor: 0.01Estos parámetros suponen un nodo con 16 núcleos CPU y 32 GB de RAM dedicados a PostgreSQL. Ajusteshared_buffersa aproximadamente el 25% de la RAM disponible yeffective_cache_sizea aproximadamente el 75%. La configuración de WAL y de puntos de control está optimizada para cargas de trabajo con mucha escritura: reduzcamax_wal_sizepara sistemas con mucha lectura donde la frecuencia de los puntos de control importa menos.
Gestión de usuarios y bases de datos
PGO administra usuarios y bases de datos PostgreSQL de forma declarativa a través de la secciónusersde la especificaciónPostgresCluster. Cuando agrega un usuario, PGO crea la función en PostgreSQL, genera una contraseña aleatoria y almacena las credenciales en un secreto Kubernetes.
users:
- name: appuser
databases: ["appdb"]
options: "NOSUPERUSER NOCREATEDB NOCREATEROLE"
- name: readonly
databases: ["appdb"]
options: "NOSUPERUSER NOCREATEDB NOCREATEROLE"
- name: migrations
databases: ["appdb"]
options: "NOSUPERUSER CREATEDB"
- name: monitoring
databases: ["postgres"]
options: "NOSUPERUSER LOGIN"
---
# Access credentials from the generated secret
kubectl get secret production-db-pguser-appuser -n databases -o jsonpath='{.data.password}' | base64 -d
# The secret also contains a full connection URI
kubectl get secret production-db-pguser-appuser -n databases -o jsonpath='{.data.uri}' | base64 -d
# postgresql://appuser:PASSWORD@production-db-primary.databases.svc:5432/appdbPara otorgar acceso de solo lectura, use la funcióndatabaseInitSQLpara ejecutar SQL en la creación del clúster que crea una función de solo lectura y le otorga SELECT en todas las tablas.
# init.sql ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: init-sql-configmap
namespace: databases
data:
init.sql: |
-- Read-only role for reporting users
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;
GRANT CONNECT ON DATABASE appdb TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
-- Extensions
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pg_trgm;Actualizaciones continuas y actualizaciones de versiones principales
PGO maneja actualizaciones de versiones menores mediante actualizaciones continuas. Cuando cambia la etiqueta de imagen en la especificaciónPostgresClustera una versión de parche más reciente, PGO actualiza las instancias una a la vez, comenzando con las réplicas y terminando con la primaria (lo que activa un cambio de Patroni para minimizar el tiempo de inactividad).
# Minor version upgrade: change the image tag
spec:
image: registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.7-0Las actualizaciones de la versión principal (por ejemplo, PostgreSQL 15 a 16) requierenpg_upgrade, que PGO organiza a través de unPGUpgradeCRD separado.
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PGUpgrade
metadata:
name: production-db-upgrade
namespace: databases
spec:
postgresClusterName: production-db
fromPostgresVersion: 15
toPostgresVersion: 16
image: registry.crunchydata.com/crunchydata/crunchy-upgrade:ubi8-5.7.2-0El proceso de actualización cierra el clúster existente, ejecutapg_upgrade --linkpara realizar una actualización local utilizando enlaces físicos (minimizando la copia de datos), verifica la actualización e inicia el clúster en la nueva versión. Realice siempre una copia de seguridad completa antes de iniciar una actualización de versión principal y pruebe primero el procedimiento en un clúster que no sea de producción.
# Pre-upgrade checklist
# 1. Take a full backup
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/pgbackrest-backup="$(date +%s)" \
--overwrite
# 2. Verify backup completed
kubectl exec -it production-db-pgha1-0 -n databases -- pgbackrest info --stanza=db
# 3. Shutdown the cluster (set replicas to 0 or use PGO shutdown annotation)
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/shutdown="$(date +%s)" \
--overwrite
# 4. Apply the PGUpgrade resource
kubectl apply -f pg-upgrade.yaml
# 5. Update the PostgresCluster spec with the new version and image
# 6. Remove the shutdown annotation to start the upgraded clusterRecomendaciones de ajuste de producción deLa ejecución de PGO en producción requiere atención a varias áreas operativas más allá de la implementación inicial.
Rendimiento de almacenamiento
- Datos separados y volúmenes WAL: Utilice siempre
walVolumeClaimSpecpara colocar WAL en un PVC dedicado. Esto evita que la actividad WAL con mucha escritura compita con la E/S de datos. - Utilice almacenamiento con alto IOPS: gp3 (AWS), Premium SSD v2 (Azure), pd-ssd (GCP) o Longhorn con respaldo NVMe en metal desnudo. Los patrones de E/S aleatorios de PostgreSQL exigen almacenamiento de baja latencia.
- Expansión de volumen: Asegúrese de que su StorageClass tenga
allowVolumeExpansion: true. PGO puede ampliar los PVC de forma no disruptiva en proveedores de almacenamiento compatibles.
PGO crea automáticamente PDB para sus instancias PostgreSQL, pero verifique que sean apropiados para sus requisitos de HA.
# Check PDBs
kubectl get pdb -n databases
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
production-db-pgha1 1 N/A 2 7dSolicitudes y límites de recursos
- Establezca las solicitudes de memoria iguales a los límites para los pods de bases de datos para evitar muertes de OOM y garantizar una clase de QoS garantizada.
- Configure las solicitudes CPU de manera conservadora y limite más alto para permitir la explosión durante las operaciones de vacío o mantenimiento.
- Monitoree el uso real a través de métricas de pgMonitor y ajuste trimestralmente.
Gestión de conexión
- Conéctese siempre a través de PgBouncer, no directamente a PostgreSQL.
- Configure
max_connectionsen PostgreSQL en un valor que tenga en cuenta el tamaño del grupo de PgBouncer más las conexiones del sistema (replicación, monitoreo, superusuario). - Utilice el modo de agrupación de transacciones para aplicaciones web. Cambie a la agrupación de sesiones solo si su aplicación utiliza declaraciones preparadas o estado de nivel de sesión (por ejemplo, comandos
SET, tablas temporales).
Validación de copia de seguridad
- Pruebe las restauraciones con regularidad creando un clúster de clonación a partir de la copia de seguridad y ejecutando pruebas de humo a nivel de aplicación.
- Supervise la alerta
PgBackRestStaleBackuppara garantizar que las copias de seguridad se completen según lo programado. - Valide PITR restaurando marcas de tiempo específicas y verificando la coherencia de los datos.
# Clone cluster for backup validation
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: backup-validation
namespace: databases
spec:
postgresVersion: 16
image: registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.6-1
dataSource:
postgresCluster:
clusterName: production-db
repoName: repo1
options:
- --type=time
- --target="2026-04-12 06:00:00+00"
instances:
- name: pgha1
replicas: 1
dataVolumeClaimSpec:
storageClassName: gp3-csi
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
backups:
pgbackrest:
repos:
- name: repo1
volume:
volumeClaimSpec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50GiPolíticas de red
# Restrict PostgreSQL traffic to known namespaces
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-network-policy
namespace: databases
spec:
podSelector:
matchLabels:
postgres-operator.crunchydata.com/cluster: production-db
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
app-access: database
- podSelector:
matchLabels:
postgres-operator.crunchydata.com/cluster: production-db
ports:
- port: 5432
protocol: TCP
- port: 9187
protocol: TCPLista de verificación de monitoreo- Retraso de replicación: Alerta cuando alguna réplica supera los 100 MB o 60 segundos de retraso.
- Saturación de conexión: Alerta cuando las conexiones activas superan el 80% de
max_connections. - Anomalías en la tasa de transacciones: Base de referencia de su TPS y alerta sobre desviaciones significativas.
- Uso del disco: alerta en umbrales del 70 % y 85 % con runbooks de expansión de volumen.
- Actualización de la copia de seguridad: alerta cuando la última copia de seguridad completa es anterior a su ventana de RPO.
- Contención de bloqueo: alerta sobre bloqueos prolongados (> 30 segundos) que pueden indicar errores en la aplicación.
- Estado del autovacuum: alerta cuando las mesas no se han aspirado en más de 24 horas.
Un plan de recuperación ante desastres sólo es útil si ha sido probado. El siguiente runbook describe los procedimientos clave para recuperarse de escenarios de fallas comunes.
Fallo de módulo único
Patroni y Kubernetes manejan esto automáticamente. Si el módulo principal falla, Patroni promueve una réplica en un plazo de 10 a 30 segundos. Kubernetes reinicia el pod fallido, que se vuelve a unir como una réplica.
Fallo de nodo único
Si un nodo que ejecuta un pod PostgreSQL muere, Kubernetes reprograma el pod en un nodo en buen estado. El pod se conecta a su PVC existente (si el almacenamiento está conectado a la red) o se restaura desde la copia de seguridad (si se utilizó almacenamiento local). Las reglas antiafinidad de pods garantizan que las instancias restantes continúen atendiendo tráfico.
Pérdida completa del clúster
Si se pierde todo el clúster Kubernetes, implemente un nuevo clúster, instale PGO y cree un nuevoPostgresClustercon undataSourceapuntando al repositorio de respaldo. PGO restaura la última copia de seguridad y reproduce WAL en el punto disponible más reciente.
Conmutación por error regional
Si se pierde la región principal, promueva el clúster en espera configurandostandby.enabled: false. Actualice su DNS o balanceador de carga para que apunte a la nueva región principal. Una vez que se recupere la región original, puede reconstruir la relación de espera a la inversa.
# Promote standby cluster
kubectl patch postgrescluster production-db-standby -n databases --type merge \
-p '{"spec":{"standby":{"enabled":false}}}'
# Verify the cluster is now an independent primary
kubectl exec -it production-db-standby-pgha1-0 -n databases -- patronictl listReferencia rápida de comandos operativos de# Cluster status
kubectl get postgrescluster -n databases
kubectl describe postgrescluster production-db -n databases
# Patroni status
kubectl exec -it production-db-pgha1-0 -n databases -- patronictl list
kubectl exec -it production-db-pgha1-0 -n databases -- patronictl history
# Connect to PostgreSQL
kubectl exec -it production-db-pgha1-0 -n databases -- psql -U postgres
# Backup info
kubectl exec -it production-db-pgha1-0 -n databases -- pgbackrest info --stanza=db
# Manual backup
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/pgbackrest-backup="$(date +%s)" --overwrite
# Restart PostgreSQL (rolling)
kubectl annotate postgrescluster production-db -n databases \
postgres-operator.crunchydata.com/restart="$(date +%s)" --overwrite
# Logs
kubectl logs production-db-pgha1-0 -n databases -c database
kubectl logs production-db-pgha1-0 -n databases -c pgbackrest
kubectl logs production-db-pgha1-0 -n databases -c exporter
# PgBouncer status
kubectl exec -it $(kubectl get pod -l postgres-operator.crunchydata.com/role=pgbouncer -n databases -o name | head -1) -n databases -- psql -U pgbouncer pgbouncer -c "SHOW POOLS;"
# Scale replicas
kubectl patch postgrescluster production-db -n databases --type merge \
-p '{"spec":{"instances":[{"name":"pgha1","replicas":5}]}}'Conclusión
Crunchy Data PGO transforma PostgreSQL en Kubernetes de una carga operativa a un sistema declarativo manejable. ElPostgresClusterCRD captura toda la implementación (instancias, replicación, respaldo, agrupación, monitoreo, TLS) en un único recurso controlado por versión. Patroni proporciona conmutación por error automática probada en batalla. pgBackRest ofrece respaldo de nivel empresarial con recuperación en un momento dado a cualquier almacén de objetos en la nube. PgBouncer maneja la agrupación de conexiones que exige el modelo de proceso por conexión de PostgreSQL a escala. Y pgMonitor introduce las métricas que necesita en Prometheus y Grafana para obtener visibilidad operativa.
Los patrones de implementación en AWS EKS, Azure AKS, GCP GKE y k3s bare metal comparten la misma especificación centralPostgresCluster: lo que cambia es la clase de almacenamiento, la configuración del repositorio de respaldo y la capa de red. Esta coherencia es el valor real de un enfoque basado en operadores: su equipo aprende una herramienta, un modelo operativo y un conjunto de runbooks que funcionan en todas partes.
Comience con un clúster de tres instancias, PgBouncer en modo de agrupación de transacciones, un programa de respaldo diferencial completo semanal y diario y las alertas principales Prometheus. Valide su procedimiento de restauración de copias de seguridad el primer día, no cuando lo necesite por primera vez. Amplíe a clústeres en espera de múltiples regiones, replicación sincrónica y ajuste avanzado a medida que crecen sus requisitos de disponibilidad y madurez operativa. El operador maneja la mecánica; su responsabilidad es comprender la arquitectura lo suficientemente bien como para realizar las compensaciones adecuadas para su carga de trabajo.