通过 Zalando Postgres Operator 实现 PostgreSQL 高可用性:多云 Kubernetes 部署指南
使用 Zalando Operator 在任何 Kubernetes 平台上部署生产级 PostgreSQL HA
简介:为什么 Kubernetes 上的 PostgreSQL 高可用性很重要
在生产中运行 PostgreSQL 需要高可用性 (HA)。以分钟为单位的停机时间可能会给企业造成数百万美元的收入损失、削弱客户信任并违反服务级别协议。 Kubernetes 已成为编排容器化工作负载的事实平台,但在 Kubernetes 上运行 PostgreSQL 等有状态服务会带来独特的挑战:持久存储管理、领导者选举、自动故障转移、备份编排和连接池。
Zalando Postgres Operator是欧洲最大的在线时装零售商 Zalando 构建的一款开源、经过实战考验的解决方案,用于管理生产中的数百个 PostgreSQL 集群。它利用Patroni进行基于共识的领导者选举,利用Spilo作为 PostgreSQL 容器映像,利用WAL-G进行持续归档和时间点恢复,利用PgBouncer进行连接池。这些组件共同提供了完全自动化、自我修复的 PostgreSQL 部署,适用于任何 Kubernetes 发行版 — 从 AWS EKS、Azure AKS 和 Google GKE 等托管云服务到使用 Rancher 运行 k3s 的裸机集群。
在本综合指南中,我们将探索 Zalando Postgres Operator 的架构,逐步介绍多个 Kubernetes 平台上的安装和配置,深入探讨复制、备份、灾难恢复、监控和生产调整。最后,您将掌握在任何 Kubernetes 基础设施上部署和操作生产级 PostgreSQL HA 集群的知识。
Zalando Postgres 操作架构
在部署之前了解架构至关重要。 Zalando Postgres Operator 遵循 Kubernetes 运算符模式:它监视postgresql类型的自定义资源定义 (CRD) 并将所需状态协调为实际 Kubernetes 资源。 以下是组件的组合方式:
Spilo:PostgreSQL 容器映像
Spilo是 Zalando 的 Docker 映像,它将 PostgreSQL 与 Patroni、WAL-G 和基本扩展捆绑在一起。 StatefulSet 中的每个 Pod 都运行一个 Spilo 容器。 Spilo 手柄:
- PostgreSQL 服务器— 数据库引擎本身,支持版本 13 至 16
- Patroni— 管理领导者选举、复制和故障转移的 HA 代理
- WAL-G— 连续 WAL 归档和基础备份工具
- pg_cron、pg_stat_statements、PostGIS— 预装的常用扩展
Patroni:领导者选举和自动故障转移
Patroni 是 HA 机制的核心。它使用分布式配置存储(DCS)来维护集群状态并执行领导者选举。在 Zalando 运营商环境中,Patroni 使用Kubernetes API本身作为 DCS(通过端点或 ConfigMap),从而无需外部 etcd 或 ZooKeeper 集群。
以下是 Patroni 故障转移过程的工作原理:
- 运行状况检查— 每个 Patroni 代理持续监控其本地 PostgreSQL 实例并向 DCS 报告运行状况。
- 领导者锁— 主设备在 DCS(Kubernetes 端点对象)中持有领导者锁。锁有一个 TTL(默认 30 秒)。
- 故障检测— 如果主节点未能在 TTL 内更新其锁定,副本将检测到这种情况。
- 选举— 合格的副本竞争领导者锁。复制延迟最小的副本获胜。
- 升级— 获胜副本将自身升级为主副本,更新 DCS,并且 Kubernetes
master服务端点自动更新。 - 防护— 旧的主节点被防护(停止或降级为副本)以防止脑裂。
整个故障转移过程通常会在15-30 秒内完成,从而确保最短的应用程序停机时间。
WAL-G:连续归档和备份
WAL-G 是 PostgreSQL 的下一代归档工具,支持备份到 S3、Google Cloud Storage (GCS) 和 Azure Blob Storage。它提供:
- 基本备份— 使用
pg_basebackup进行完整物理备份 - WAL 归档— 用于时间点恢复的连续预写日志传送
- 增量备份— 仅存储更改页面的增量备份
- 加密— 静态备份的 AES-256 加密
- 压缩— LZ4 或 ZSTD 压缩可降低存储成本
Kubernetes 资源层次结构
当您创建postgresql自定义资源时,操作员将创建一组全面的 Kubernetes 资源来管理集群。了解此层次结构对于故障排除和监控非常重要:
资源- StatefulSet— 管理具有稳定网络身份和有序部署的 PostgreSQL Pod
- 服务— 两个 ClusterIP 服务:用于主数据库的
<cluster-name>和用于只读副本的<cluster-name>-repl - 端点— Patroni 更新端点以指向当前领导者以实现无缝故障转移
- PodDisruptionBudgets (PDB)— 确保至少一个实例在自愿中断期间保持可用
- Secrets— PostgreSQL 超级用户、复制和应用程序凭证存储为 Kubernetes Secrets
- PersistentVolumeClaims (PVCs)— 每个 Pod 一个 PVC,用于 PostgreSQL 数据存储
- PgBouncer 部署— 可选连接池,作为单独的部署部署,具有自己的服务
在多个 Kubernetes 平台上的安装
先决条件
<cluster-name>和用于只读副本的<cluster-name>-repl在安装 Zalando Postgres Operator 之前,请确保您拥有:
- 正在运行的 Kubernetes 集群 (v1.25+)
kubectl配置有集群管理员访问权限helmv3 安装- 默认 StorageClass 配置的
操作员安装
推荐安装方式使用Helm。这在所有 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-operator验证操作员是否正在运行:
kubectl get pods -n postgres-operator
# Expected output:
# NAME READY STATUS RESTARTS AGE
# postgres-operator-7f8b9c6d4-x2k9j 1/1 Running 0 2mAWS EKS 部署细节
Amazon EKS 需要特定配置才能实现最佳 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: true对于 EKS 上的 WAL-G 备份,为服务账户 (IRSA) 配置 IAM 角色:
# 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 AKS 部署
Azure AKS 使用 Azure 磁盘进行持久存储,使用托管身份进行备份身份验证:
# 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"Google GKE 部署
Google Kubernetes 引擎使用持久磁盘和工作负载身份进行 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"裸机 k3s/带 Longhorn 存储的 Rancher
对于本地部署,k3s 提供轻量级 Kubernetes 发行版,Longhorn 提供分布式块存储。当您需要完全控制基础设施而不被云供应商锁定时,这种组合是理想的选择。
# 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 集群 CRD 规格
部署核心 具有 Zalando 运营商的 PostgreSQL 集群是postgresql自定义资源。此 YAML 清单声明了集群的所需状态,操作员将其调整为现实。以下是全面的、可投入生产的 CRD 规格:
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: 103CRD 关键字段说明
- numberOfInstances— Pod 总数。操作员会自动将其中一个指定为主副本,将其余副本指定为流副本。
- enableConnectionPooler— 为主要服务部署 PgBouncer sidecar,从而减少连接开销。
- enableReplicaConnectionPooler— 为副本服务部署单独的 PgBouncer,这对于读取繁重的工作负载至关重要。
- postgresql.parameters— 直接传递到
postgresql.conf的 PostgreSQL 配置参数。 - patroni— 配置 Patroni 行为,包括 TTL、循环等待、重试超时和同步复制模式。
- volume.storageClass— 映射到特定于平台的 StorageClass(AWS 上的 EBS gp3、Azure 上的 Premium SSD、GCP 上的 SSD PD、k3s 上的 Longhorn)。
- enableShmVolume— 在
/dev/shm上安装tmpfs,用于 PostgreSQL 共享内存,这对性能至关重要。
与 PgBouncer
的连接池PostgreSQL 的按连接处理模型使得处理大量客户端连接的成本很高。每个连接消耗大约 10MB 的 RAM。 PgBouncer 通过在一小部分实际 PostgreSQL 连接上复用数千个客户端连接来解决这个问题。
Zalando 运营商原生支持 PgBouncer 部署。当您在CRD中设置enableConnectionPooler: true时,操作员创建:
- 具有可配置副本数量的 PgBouncer 部署
- 用于池连接的专用服务 (
<cluster-name>-pooler) - 与 PostgreSQL 自动凭证同步
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: 256Mi对于需要准备好的语句或会话级功能的应用程序,可以绕过 PgBouncer 直接连接到 PostgreSQL 服务,或者使用session池模式,但会降低连接效率。
多区域 PostgreSQL 复制
对于需要从多个地理位置低延迟读取或跨区域灾难恢复的全球应用程序,多区域复制至关重要。 Zalando 操作员通过备用集群支持这一点,这些备用集群通过流复制或 WAL-G 存档从主集群进行复制。
流复制配置
PostgreSQL 流复制是 Zalando 运营商中 HA 的基础。它的工作原理是近乎实时地将预写日志 (WAL) 记录从主服务器传送到副本。操作员会自动进行配置,但了解详细信息有助于调整和故障排除。
- 同步复制— 主节点在提交事务之前等待至少一个副本确认 WAL 接收。这保证了零数据丢失 (RPO=0),但增加了延迟。使用
patroni.synchronous_mode: true启用。 - 异步复制— 主节点立即提交并异步传送 WAL。延迟稍低,但故障转移期间可能会丢失数据。这是默认设置。
- 级联复制— 副本可以从其他副本而不是主副本进行复制,从而减少大型集群中主副本的负载。
# 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 replicas使用 WAL-G
进行备份和恢复WAL-G 备份配置
正确的备份配置对于灾难恢复至关重要。 Zalando 操作员集成了 WAL-G 以连续备份到对象存储。以下是 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"时间点恢复 (PITR)
PITR 允许您将数据库恢复到任何特定时刻,这对于从意外数据删除或损坏中恢复至关重要。 Zalando运营商通过克隆机制支持PITR:
# 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 instead应用 CRD 时,操作员执行以下步骤:
- 查找目标时间戳之前的最新基础备份
- 将基础备份恢复到新 StatefulSet 的主 Pod
- 重放 WAL 段直至指定时间戳
- 打开数据库进行读写操作
- 设置到副本 Pod 的流式复制
用于灾难恢复的备用集群
备用集群持续从主集群进行复制,提供可在灾难期间升级的热备用。这与集群内的副本不同 - 备用集群是完全独立的 Kubernetes 资源,可以在不同的命名空间、集群甚至区域中运行。
# 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: true要将备用集群升级为独立主集群(在灾难恢复期间),只需从 CRD 中删除standby部分并应用:
# 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 primary使用 Prometheus 和 Grafana 进行监控
全面监控对于生产 PostgreSQL 部署来说是不容忽视的。 Zalando 运营商支持通过postgres_exportersidecar 导出 Prometheus 指标。以下是如何设置完整的监控堆栈:
ServiceMonitor
# 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: /metrics监控
的关键指标以下是需要在 Grafana 仪表板中跟踪的最关键的 PostgreSQL 指标:
- pg_stat_replication_lag— 复制延迟(以字节和秒为单位)。如果滞后超过 RPO 阈值,则发出警报。
- pg_stat_activity_count— 按状态划分的活动连接。连接池耗尽时发出警报。
- pg_stat_database_tup_fetched/returned/inserted/updated/deleted— 查询吞吐量指标。
- pg_stat_bgwriter_buffers_checkpoint/clean/backend— 缓冲区管理效率。
- pg_database_size_bytes— 容量规划的数据库大小随着时间的推移而增长。
- pg_locks_count— 锁争用。对过多的等待锁发出警报。
- pg_stat_statements_calls/mean_time— 查询性能统计信息以进行优化。
- patroni_postgres_running— Patroni 运行状况(1 = 正在运行,0 = 关闭)。
- patoni_master— 哪个 pod 是当前主节点(1 = 主节点,0 = 副本节点)。
- pg_up— 基本 PostgreSQL 可用性探测器。
警报规则
# 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 }}"生产调整指南
正确的调整对于在 Kubernetes 上从 PostgreSQL 获得最大性能至关重要。应根据您的 Pod 资源限制和工作负载特征调整以下参数。
存储器配置
# 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"WAL 和检查点配置
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 checkpoints查询规划器和 I/O
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"连接和记录
postgresql:
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 timeout滚动更新和版本升级
小版本升级
当您更新 Spilo 映像标签时,操作员会自动处理次要版本升级(例如,16.2 至 16.3)。 操作员执行滚动重启:
# 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)主要版本升级
主要版本升级(例如 PostgreSQL 15 至 16)需要更仔细的规划。 Zalando运营商支持使用pg_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'"重大升级的重要注意事项:
- 在升级 之前始终进行全新备份
- 首先在克隆集群上测试升级
- 主要升级需要停机(通常为 5-30 分钟,具体取决于数据库大小)
- 查看 PostgreSQL 发行说明以了解重大更改
- 副本重建后密切监控复制延迟
- 在所有数据库上运行
ANALYZE以重新生成查询规划器统计信息
高级操作模式
逻辑备份
除了物理 WAL-G 备份外,运营商还支持使用pg_dump进行逻辑备份。逻辑备份对于跨版本迁移和选择性表恢复非常有用:
# 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 S3自定义 Pod 环境变量
您可以使用 ConfigMap 将环境变量注入 Spilo pod。这对于配置 WAL-G、自定义脚本或调整操作系统级参数非常有用:
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"安全网络策略
在生产中,使用 Kubernetes 网络策略限制对 PostgreSQL Pod 的网络访问:
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 backups常见问题故障排除
即使实现自动化,也会出现问题。以下是使用 Zalando 操作器运行 PostgreSQL 时最常见的问题及其解决方案:
1. Pod 卡在待处理状态
# 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. 复制延迟不断增长
# 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. 故障转移不触发
# 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. 备份失败
# 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/"安全最佳实践
在 Kubernetes 上保护 PostgreSQL 需要采用深度防御方法:
- TLS 加密— 为所有客户端连接启用 SSL。操作员可以使用 cert-manager 自动提供证书。
- 机密管理— 使用 Kubernetes 机密(或外部机密管理器,如 Vault)作为数据库凭证。切勿将密码存储在 ConfigMap 中。
- RBAC— 限制操作员 ServiceAccount 权限。对应用程序数据库用户使用最低权限访问。
- NetworkPolicies— 限制 Pod 到 Pod 的通信,如上一节所示。
- pg_hba.conf— 配置基于主机的身份验证以限制哪些 IP 和用户可以连接。
- 审核日志记录— 启用
pgaudit扩展,以在受监管的环境中进行 SQL 审核日志记录。 - 加密存储— 使用加密存储类(EBS 加密、Azure 磁盘加密等)。
- Pod 安全标准— 在安全上下文受限的情况下以非 root 身份运行 Spilo Pod。
# 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: 0640容量规划和规模调整
适当的尺寸可确保稳定的性能和成本效率。 使用这些指南作为起点,并根据您的工作负载监控数据进行调整:
| 工作负载 | CPU | 内存 | 存储 | 实例 |
|---|---|---|---|---|
| 开发 | 500m | 1Gi | 10Gi | 1 |
| 小量产 | 2 核 | 8Gi | 50Gi SSD | 3 |
| 中等量产 | 4 核 | 16Gi | 200Gi SSD | 3 |
| 大批量生产 | 8 核 | 32Gi | 500Gi SSD | 5 |
| 企业/分析 | 16+ 内核 | 64Gi+ | 1Ti+ SSD | 5+ |
完整的端到端部署示例
让我们将所有内容放在一起,在新的 Kubernetes 集群上从头开始进行完整部署:
# 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();"结论
Zalando Postgres Operator 将 Kubernetes 上的 PostgreSQL 从复杂的操作挑战转变为可管理的自动化部署。通过利用 Patroni 进行基于共识的故障转移、利用 Spilo 实现包含电池的容器映像、利用 WAL-G 进行持续备份和恢复以及利用 PgBouncer 实现高效连接池,您将获得一个跨 AWS EKS、Azure AKS、Google GKE 和裸机 k3s 集群一致工作的生产级数据库平台。
本指南的主要要点是:
- 自动化一切— 让操作员处理 StatefulSet 管理、故障转移和备份调度。手动干预应该是例外。
- 积极监控— 从第一天开始部署 Prometheus 和 Grafana。复制滞后、连接计数和锁争用是您的早期预警信号。
- 灾难规划— 配置 WAL-G 备份、定期测试 PITR,并为关键工作负载维护备用集群。
- 根据您的工作负载进行调整— 默认 PostgreSQL 参数比较保守。根据您的资源分配和查询模式调整shared_buffers、work_mem和检查点设置。
- 默认安全— 启用 TLS、使用 SCRAM-SHA-256 身份验证、限制网络访问并加密静态存储。
- 测试升级— 在将其应用于生产之前,始终克隆集群并测试主要版本升级。
凭借这一全面的基础,您可以在任何 Kubernetes 平台上部署和操作高度可用的 PostgreSQL 集群,为需要大规模可靠性、性能和数据完整性的应用程序提供服务。