PostgreSQL 高可用性和关键数据 PGO:企业 Kubernetes 部署
企业 PostgreSQL HA 与 Kubernetes 上的 Crunchy Data PGO
在 Kubernetes 上运行 PostgreSQL 需要的不仅仅是具有持久卷的 StatefulSet。您需要自动故障转移、持续备份和时间点恢复、连接池、TLS 加密、监控以及跨云提供商和裸机一致部署的能力。 Crunchy Data PGO (Postgres Operator) v5 通过单个 Kubernetes 原生自定义资源 —PostgresClusterCRD — 提供所有这些功能,并由经过考验的组件提供支持:用于基于共识的 HA 的 Patroni、用于企业备份的 pgBackRest、用于连接池的 PgBouncer 以及用于与 Prometheus 兼容的可观察性的 pgMonitor。
本指南涵盖了将 PGO 管理的 PostgreSQL 集群从初始部署到跨 AWS EKS、Azure AKS、GCP GKE 和裸机 k3s 与 Rancher 进行生产级操作所需的一切。每个部分都包含具体的 YAML、Helm 值以及您可以适应您的环境的操作程序。
脆脆数据 PGO v5 架构
PGO v5 是对 Crunchy Postgres Operator 的完全重写。它用单个PostgresCluster资源取代了以前的 pgcluster/pgreplica/pgpolicy CRD,该资源以声明方式描述了 PostgreSQL 部署的各个方面。操作员监视此资源的更改,并协调底层 Kubernetes 对象(StatefulSet、服务、ConfigMap、Secret、作业)以匹配所需的状态。
该架构建立在四个支柱之上。Patroni在每个 PostgreSQL Pod 中作为 sidecar 运行,并使用 Kubernetes 原生分布式共识来管理领导者选举、复制拓扑和自动故障转移。pgBackRest处理完整备份、差异备份和增量备份以及到对象存储(S3、GCS、Azure Blob)或本地 PVC 的连续 WAL 归档。PgBouncer提供轻量级连接池,可保护 PostgreSQL 免受连接风暴的影响。pgMonitor通过 Prometheus 导出器 sidecar 公开 PostgreSQL 指标,以便与现有的可观测性堆栈集成。
操作器本身是无状态的 - 所有持久状态都存在于 PostgreSQL 集群及其备份存储库中。 这意味着您可以升级或重新启动 Operator,而不会影响正在运行的数据库。运算符协调循环是幂等的:多次应用相同的PostgresCluster规范会生成相同的 Kubernetes 对象集。
安装 PGO v5
PGO v5 可以通过 Helm 或直接 kubectl 清单安装。 Helm 方法是生产的首选方法,因为它与 GitOps 工作流程干净地集成并提供版本控制的升级。
# 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-0对于气隙环境,将所需的映像镜像到内部注册表并相应地更新relatedImages值。 PGO 只会提取 Helm 值或PostgresCluster规范中指定的映像 - 它永远不会在运行时访问外部注册表,除非明确配置这样做。
# 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 规格
PostgresClusterCRD 是整个 PostgreSQL 部署的单一声明性界面。下面是一个生产就绪的规范,演示了关键部分。本指南的后续部分将详细讨论每个部分。
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.sql该规范创建了一个三实例 PostgreSQL 16 集群,具有独立的数据和 WAL 卷、完整/差异/增量计划上的 S3 支持的备份、事务模式下的 PgBouncer 连接池、Prometheus 指标导出和 Patroni 同步复制。 Pod 反关联性规则确保所有三个实例都落在不同的 Kubernetes 节点上。
基于 Patroni 的 HA 和自动故障转移
Patroni 是嵌入在每个 PGO 管理的 PostgreSQL Pod 中的 HA 框架。它使用 Kubernetes API 作为其分布式配置存储 (DCS) — 不需要单独的 etcd 或 ZooKeeper 集群。 Patroni 持续监控 PostgreSQL 主服务器和副本服务器的运行状况。当主副本无响应时,Patroni 会启动自动故障转移:它将最新的副本提升为主副本,并重新配置其余副本以跟随新的领导者。
PGO 中的故障转移过程的工作原理如下。每个 pod 上的 Patroni 都持有 Kubernetes 中的领导者锁(通过 Endpoints 或 ConfigMap 对象)。当前主节点必须以可配置的时间间隔(默认 10 秒 TTL,3 秒循环等待)更新此锁。如果主节点无法更新(因为它崩溃、节点死亡或网络对其进行分区),最接近主节点 WAL 位置的副本将获取锁并提升自身。整个过程通常在 10 到 30 秒内完成。
同步复制模式通过 Patroni 配置中的synchronous_mode: true启用,可保证零数据丢失 (RPO = 0),但写入延迟稍高。在同步模式下,直到至少一个副本确认接收到 WAL 后,客户端才会确认事务。如果没有可用的同步副本,Patroni 会暂时禁用同步模式以维持可用性 - 如果您愿意牺牲可用性以保持一致性,则可以使用synchronous_mode_strict: true覆盖此模式。
# 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 自动配置 Kubernetes 服务来跟踪 Patroni 领导者。production-db-primary服务始终指向当前持有领导者锁的 Pod,因此通过此服务连接的应用程序只需短暂的连接重置即可体验无缝故障转移。
pgBackRest 备份和恢复
pgBackRest 是集成到 PGO 中的备份引擎。它支持三种备份类型——完整备份、差异备份和增量备份——以及用于时间点恢复 (PITR) 的连续 WAL 归档。 了解它们如何组合在一起对于设计平衡存储成本、备份速度和恢复时间的备份策略至关重要。
完整备份复制整个 PostgreSQL 数据目录,并且是所有其他备份类型的基准。差异备份仅复制自上次完整备份以来更改的页面。增量备份仅复制自上次任何类型备份以来更改的页面。在恢复期间,pgBackRest 自动将所需的备份链接在一起 - 例如,从增量恢复需要增量加上先前的差异(或完整)加上完整备份,加上到达目标恢复点所需的任何 WAL 段。
备份计划配置
备份计划在PostgresCluster规范内 pgBackRest 配置的repos部分中定义。可靠的生产计划通常运行每周完整备份、每日差异备份以及更频繁的增量备份。
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-1手动备份和恢复
# 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)" \
--overwrite在 PITR 恢复期间,PGO 关闭所有 PostgreSQL 实例,从最近的完整备份或差异备份恢复,重播 WAL 段直至目标时间,然后启动集群。整个操作由操作员精心安排,无需对各个 Pod 进行手动干预。
PgBouncer 连接池
PostgreSQL 为每个客户端连接创建一个新进程。在规模上——数百或数千个应用程序 Pod,每个应用程序 Pod 维护连接池——这种模型就会崩溃。 PgBouncer 位于您的应用程序和 PostgreSQL 之间,通过少量服务器连接复用许多客户端连接。
PGO 将 PgBouncer 部署为具有自己服务的单独部署。您的应用程序应连接到production-db-pgbouncer服务,而不是直接连接到 PostgreSQL 主服务。 PgBouncer 支持三种池模式:
- 会话池— 在客户端连接的生命周期内将服务器连接分配给客户端。最安全,但效率最低。
- 事务池— 仅在事务期间分配服务器连接。对于 Web 工作负载最有效。这是推荐的默认值。
- 语句池— 为单个语句分配服务器连接。仅适用于简单的非事务性工作负载。
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: pgbouncer请注意tcp_keepalive设置 - 当 PgBouncer 在云负载均衡器或 Kubernetes 服务后面运行时,这些设置至关重要。如果没有积极的保活,空闲连接可能会被中间网络组件悄悄丢弃,从而导致下一次查询尝试时出现应用程序错误。
pgMonitor 和 Prometheus/Grafana 监控
PGO 的监控集成在每个 PostgreSQL Pod 中部署crunchy-postgres-exportersidecar。该导出器抓取 PostgreSQL 的内部统计数据视图,并将其作为 Prometheus 指标在端口 9187 上公开。该导出器涵盖超过 150 个开箱即用的指标,包括连接、复制延迟、事务率、缓存命中率、表和索引统计数据、锁争用和 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 提供了一组可直接导入的预构建 Grafana 仪表板。这些内容涵盖 PostgreSQL 概述、复制状态、pgBackRest 备份状态、PgBouncer 统计信息和 Pod 级资源使用情况。通过 Grafana 的仪表板配置或手动从 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 monitoring适用于 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 EKS 部署
在 AWS EKS 上部署 PGO 需要配置用于持久卷的 EBS CSI 驱动程序和用于 S3 备份访问的服务帐户 (IRSA) 的 IAM 角色。此方法避免将长期存在的 AWS 凭证存储在 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 \
--approve在PostgresCluster规范中,引用 S3 存储桶和区域。 PGO 将自动使用 Pod 的服务帐户凭据(通过 IRSA)——无需显式访问密钥。
backups:
pgbackrest:
repos:
- name: repo1
s3:
bucket: mycompany-pg-backups
endpoint: s3.eu-west-1.amazonaws.com
region: eu-west-1对于多可用区弹性,请确保您的 EKS 节点组至少跨越三个可用区,并使用前面显示的拓扑分布约束在它们之间分布 PostgreSQL Pod。
Azure AKS 部署
Azure AKS 使用托管磁盘进行持久存储,使用 Azure Blob 存储进行 pgBackRest 备份。建议的存储类别使用 Premium SSD v2 或 Premium LRS 来处理数据库工作负载。
# 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_KEY对于工作负载身份(Azure 相当于 AWS IRSA),使用 OIDC 颁发者配置 AKS 群集,并为 pgBackRest 服务帐户创建联合身份凭据。这消除了在秘密中存储帐户密钥的需要。
GCP GKE 部署
GKE 使用持久磁盘 (pd-ssd) 进行存储,使用 GCS 进行 pgBackRest 备份。 GKE Workload Identity 将 Kubernetes 服务帐号映射到 Google Cloud 服务帐号,以进行安全、无密钥的身份验证。
# 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]"多区域灾难恢复
PGO支持备用集群,实现跨地域容灾。备用集群不断地从主集群的 pgBackRest 存储库重播 WAL,维护一个热副本,在发生区域故障时可以将其提升为独立主集群。
# 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-1要在灾难期间提升备用集群,请在规格中设置standby.enabled: false并应用更改。 PGO 将备用集群提升为独立的主集群。这种提升是不可逆的——在原始主区域恢复后,您将需要从头开始重建复制关系。
裸机 k3s/Rancher 部署
在具有 Rancher 管理的裸机 k3s 上运行 PGO 需要仔细注意存储和网络,因为您没有云提供商管理的磁盘或负载均衡器。
Longhorn 存储
Longhorn 是适用于 Kubernetes 的轻量级分布式块存储系统,非常适合裸机环境。它跨多个节点复制卷以实现持久性,并支持快照、备份和卷扩展。
# 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 用于负载平衡
# 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-pool配置 MetalLB 后,LoadBalancer类型的 PGO 服务将从您的池中接收 IP 地址,从而使 PostgreSQL 主端点和 PgBouncer 端点可从您的网络直接访问,无需手动端口转发。
pgBackRest,带有本地 PVC 或 NFS,适用于裸机
# 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: 200Gi对于气隙环境,您还可以配置辅助 pgBackRest 存储库,将备份发送到 NFS 共享或本地运行的 MinIO 实例,从而提供不依赖于云的集群外备份副本。
TLS/SSL 加密配置
默认情况下,PGO 为所有内部通信生成自签名 TLS 证书 — PostgreSQL 实例、复制、pgBackRest 和 PgBouncer 均通过加密通道进行通信,无需任何手动证书管理。但是,对于生产环境,您通常希望使用由组织的 CA 签名的证书。
# 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 使用ssl = on配置 PostgreSQL,并设置适当的ssl_cert_file、ssl_key_file和ssl_ca_file参数。 PgBouncer 的配置类似,需要 TLS 进行客户端连接,并在连接到 PostgreSQL 后端时使用 TLS。
# 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-256定制 PostgreSQL 配置
PGO 通过 Patroni 动态配置部分公开 PostgreSQL 配置。这是推荐的方法,因为 Patroni 确保所有实例保持一致的配置并在需要时处理重新启动。
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.01这些参数假定节点具有 16 个 CPU 内核和专用于 PostgreSQL 的 32 GB RAM。 将shared_buffers调整为可用 RAM 的大约 25%,将effective_cache_size调整为大约 75%。 WAL 和检查点设置针对写入密集型工作负载进行了调整 — 对于检查点频率影响较小的读取密集型系统,减少max_wal_size。
用户和数据库管理
PGO 通过PostgresCluster规范的users部分以声明方式管理 PostgreSQL 用户和数据库。添加用户时,PGO 在 PostgreSQL 中创建角色,生成随机密码,并将凭据存储在 Kubernetes Secret 中。
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/appdb要授予只读访问权限,请使用databaseInitSQL功能在创建集群时运行 SQL,以创建只读角色并授予其对所有表的 SELECT 权限。
# 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;滚动更新和主要版本升级
PGO 通过滚动更新处理小版本升级。当您将PostgresCluster规范中的映像标签更改为较新的补丁版本时,PGO 一次更新一个实例,从副本开始,到主实例结束(这会触发 Patroni 切换以最大限度地减少停机时间)。
# Minor version upgrade: change the image tag
spec:
image: registry.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.7-0主要版本升级(例如 PostgreSQL 15 至 16)需要pg_upgrade,PGO 通过单独的PGUpgradeCRD 进行协调。
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-0升级过程会关闭现有集群,运行pg_upgrade --link以使用硬链接执行就地升级(最大限度地减少数据复制),验证升级,然后在新版本上启动集群。在开始主要版本升级之前始终进行完整备份,并首先在非生产集群上测试该过程。
# 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 cluster生产调整建议
在生产中运行 PGO 需要注意初始部署之外的多个操作领域。
存储性能
- 独立的数据和 WAL 卷:始终使用
walVolumeClaimSpec将 WAL 放置在专用 PVC 上。这可以防止写入量大的 WAL 活动与数据 I/O 发生争用。 - 使用高 IOPS 存储:gp3 (AWS)、Premium SSD v2 (Azure)、pd-ssd (GCP) 或裸机上支持 NVMe 的 Longhorn。 PostgreSQL 的随机 I/O 模式需要低延迟存储。
- 卷扩展:确保您的 StorageClass 具有
allowVolumeExpansion: true。 PGO 可以在支持的存储提供商上无中断地扩展 PVC。
Pod 中断预算
PGO 会自动为您的 PostgreSQL 实例创建 PDB,但请验证它们是否适合您的 HA 要求。
# Check PDBs
kubectl get pdb -n databases
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
production-db-pgha1 1 N/A 2 7d资源请求和限制
- 将内存请求设置为等于数据库 Pod 的限制,以防止 OOM 终止并确保有保证的 QoS 级别。
- 保守地设置 CPU 请求并限制更高,以允许在真空或维护操作期间突发。
- 通过 pgMonitor 指标监控实际使用情况并每季度进行调整。
连接管理
- 始终通过 PgBouncer 连接,而不是直接连接到 PostgreSQL。
- 将 PostgreSQL 中的
max_connections设置为考虑 PgBouncer 池大小加上系统连接(复制、监控、超级用户)的值。 - 对 Web 应用程序使用事务池模式。仅当您的应用程序使用准备好的语句或会话级状态(例如
SET命令、临时表)时,才切换到会话池。
备份验证
- 通过从备份创建克隆集群并运行应用程序级冒烟测试来定期测试恢复。
- 监控
PgBackRestStaleBackup警报以确保备份按计划完成。 - 通过恢复到特定时间戳并验证数据一致性来验证 PITR。
# 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: 50Gi网络策略
# 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: TCP监控清单
- 复制延迟:当任何副本超过 100MB 或延迟 60 秒时发出警报。
- 连接饱和:当活动连接超过
max_connections的 80% 时发出警报。 - 交易率异常:确定 TPS 基线并针对重大偏差发出警报。
- 磁盘使用情况:使用卷扩展运行手册在 70% 和 85% 阈值时发出警报。
- 备份新鲜度:当上次完整备份早于 RPO 窗口时发出警报。
- 锁定争用:针对可能指示应用程序错误的长期锁定(> 30 秒)发出警报。
- 自动清理运行状况:当表在超过 24 小时内未清理时发出警报。
灾难恢复运行手册
灾难恢复计划只有经过测试才有用。以下运行手册概述了从常见故障场景中恢复的关键过程。
单 Pod 故障
Patroni 和 Kubernetes 自动处理此问题。如果主 Pod 崩溃,Patroni 将在 10-30 秒内升级副本。 Kubernetes 重新启动发生故障的 Pod,该 Pod 作为副本重新加入。
单节点故障
如果运行 PostgreSQL Pod 的节点死亡,Kubernetes 会在健康节点上重新调度 Pod。 Pod 连接到其现有的 PVC(如果存储连接到网络)或从备份恢复(如果使用本地存储)。 Pod 反关联性规则可确保剩余实例继续提供流量服务。
完全集群丢失
如果整个 Kubernetes 集群丢失,请部署新集群,安装 PGO,并创建一个新的PostgresCluster,其中dataSource指向备份存储库。 PGO 恢复最新的备份并重播 WAL 到最近的可用点。
区域故障转移
如果主区域丢失,可通过设置standby.enabled: false提升备用集群。更新您的 DNS 或负载均衡器以指向新的主要区域。当原Region恢复后,可以反向重建备用关系。
# 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 list操作命令快速参考
# 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}]}}'结论
Crunchy Data PGO 将 Kubernetes 上的 PostgreSQL 从操作负担转变为可管理的声明性系统。PostgresClusterCRD 在单个版本控制资源中捕获整个部署(实例、复制、备份、池化、监控、TLS)。 Patroni 提供经过实战检验的自动故障转移。 pgBackRest 为任何云对象存储提供企业级备份和时间点恢复。 PgBouncer 处理 PostgreSQL 的每连接一个进程模型所需的大规模连接池。 pgMonitor 将您需要的指标提供给 Prometheus 和 Grafana 以实现操作可见性。
AWS EKS、Azure AKS、GCP GKE 和裸机 k3s 的部署模式共享相同的核心PostgresCluster规范 - 变化的是存储类、备份存储库配置和网络层。这种一致性是基于操作员的方法的真正价值:您的团队学习一种工具、一种操作模型和一套适用于任何地方的操作手册。
从三实例集群开始,事务池模式下的 PgBouncer、每周完整加每日差异备份计划以及核心 Prometheus 警报。在第一天就验证您的备份恢复过程,而不是在您第一次需要它时。随着可用性要求和运营成熟度的增长,扩展到多区域备用集群、同步复制和高级调整。操作员负责机械操作;您的责任是充分了解架构,以便针对您的工作负载做出正确的权衡。