Kubernetes 上 PostgreSQL 运维与 CloudNativePG 实战

深入讲解在 Kubernetes 上运维 PostgreSQL 的核心挑战与 CloudNativePG Operator 的实战方案。涵盖云原生数据库运维的关键痛点、CloudNativePG 架构设计、Cluster CRD 自定义资源定义、自动备份与基于 WAL 的时间点恢复(PITR)、Replica 管理与故障自动切换、storage 与 Secret 安全编排、滚动升级策略,以及 Patroni 与 Operator 模式对比。

将 PostgreSQL 部署到 Kubernetes 看似只是把容器编排进去,实际生产中会立刻遇到挂载卷同步、Pod 漂移后数据一致性、副本切换自动化、WAL 归档生命周期管理等一系列问题。传统手动部署在 K8s 上不仅维护成本高,每次滚动更新都可能触发无意的换主。

CloudNativePG 是目前最完整的 PostgreSQL Kubernetes Operator 方案,它用自定义资源(CRD)将数据库集群当作一等公民进行管理,提供声明式备份、时间点恢复(PITR)、滚动升级等高级能力。本专题将从痛点出发,逐一拆解 Operator 的核心工作模式。

核心认知:Kubernetes 上的数据库 ≠ 无状态容器集群。数据持久性、一致性切换、备份恢复是需要 Operator 显式抽象的复杂逻辑,不能靠普通 Deployment + PVC 自行拼凑。


一、云原生数据库运维痛点

1.1 为什么普通 StatefulSet 不够

# 反例:用 StatefulSet 手动维护 PG
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-postgres  # 这行有坑

单纯 StatefulSet + PVC 能启动 PostgreSQL,但生产上必须解决:

痛点说明
副本角色管理StatefulSet 不区分主从,需要手动维护 pg_is_in_recovery()
故障切换Pod 挂了谁触发 failover?自动还是手动?数据丢失窗口多大?
滚动升级新版本容器推动作如何做到不终止活跃连接?
备份与恢复WAL 归档到 object storage 的过程谁管理?PITR 如何做?
Secret/证书pg_hba.conf 和 replication 密码如何安全编排?
监控集成如何在 Pod 生命周期之外保持指标连续性?

普通 StatefulSet 这些问题全部要自己写脚本解决,Operator 的存在正是为了把这些运维逻辑标准化。


二、CloudNativePG 架构

2.1 架构组件

┌──────────────────┐
│  CloudNativePG   │
│    Manager Pod   │  ← 监听 Cluster CRD 变更,协调所有子资源
└────────┬─────────┘
         │
    ┌────┴────┐
    ↓         ↓
┌────────┐ ┌────────┐
│Cluster │ │Backup  │  CRD 声明式资源
│  CRD   │ │  CRD   │
└───┬────┘ └───┬────┘
    │          |
    ↓          ↓
┌────────┐ ┌────────────┐
│Primary │ │ Object     │  (S3 / GCS / Azure Blob)
│  Pod   │ │ Storage    │
└──┬─────┘ └────────────┘
   │
┌──┴──┐
↓     ↓
Replica Pods (同步/异步)

CloudNativePG 的运行时主要包含:

  • Manager:一个 Deployment,负责监听 CRD 并协调所有子资源
  • Cluster CRD:声明式定义数据库集群(主从、资源、存储类、备份配置)
  • Instance Pod:实际运行 PostgreSQL 的 Pod,每个 Pod 运行一个 postgres 实例
  • Backup / ScheduledBackup CRD:定义一次性或周期性备份任务
  • Pooler CRD(可选):管理 PgBouncer 连接池实例

2.2 与传统 Patroni 方案对比

特性Patroni(手动部署)CloudNativePG
部署方式手动配置 etcd/consul + PatroniOperator 自动部署
API 交互REST API + 环境变量声明式 CRD
备份管理需集成 wal-g/pgBackRest 脚本内置,CRD 声明
PITR手动配置 WAL 归档 + 时间戳恢复内置,一个 CRD 声明
滚动升级手动依次重启Operator 自动串行滚动
监控自行集成 postgres_exporter可选 sidecar 自动注入

三、Cluster CRD 定义

3.1 基础集群

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: my-pg-cluster
  namespace: database
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  instances: 3  # 1 Primary + 2 Replicas

  postgresql:
    pg_hba:
      - hostssl all all 0.0.0.0/0 scram-sha-256

  bootstrap:
    initdb:
      database: myapp
      owner: myapp_user
      secret:
        name: myapp-db-secret

  storage:
    size: 100Gi
    storageClass: ssd-block

  monitoring:
    enabled: true
    customQueriesConfigMap:
      name: cnpg-custom-queries
关键字段说明
instances总实例数(1 个 Primary,其余 Replica)
bootstrap初始化方式:initdb 新建库、或 recovery 从备份恢复
storagePVC 大小与 StorageClass
superuserSecretsuperuser 密码 Kubernetes Secret
replicationSlots.highAvailability.enabled启用复制槽确保 WAL 不丢失

3.2 从备份初始化(PITR 预备)

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: my-pg-cluster
  namespace: database
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  instances: 3

  bootstrap:
    recovery:
      source: my-pg-cluster  # 从同名集群的备份恢复
      database: myapp
      owner: myapp_user

  storage:
    size: 100Gi
    storageClass: ssd-block

  backup:
    enabled: true
    retentionPolicy: "30d"
    schedule: "0 2 * * *"  # 每天凌晨 2 点
    barmanObjectStore:
      destinationPath: "s3://my-bucket/backups"
      s3Credentials:
        inheritFromIAMRole: true

四、自动备份与时间点恢复

4.1 备份原理

CloudNativePG 底层使用 Barman 进行备份管理:

  • Base Backup:周期性的全量物理备份(pg_basebackup)
  • WAL Archiving:持续将 WAL 段归档到对象存储
  • PITR:基于 base backup + WAL timeline 恢复到任意时间点
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: daily-backup
  namespace: database
spec:
  immediate: true
  schedule: "0 2 * * *"
  cluster:
    name: my-pg-cluster
  backupOwnerReference: self

4.2 执行一次性备份

kubectl apply -f - <<'EOF'
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
  name: manual-backup-$(date +%s)
  namespace: database
spec:
  cluster:
    name: my-pg-cluster
EOF
# 查看备份状态
kubectl get backup -n database
kubectl describe backup manual-backup-xxx -n database

4.3 时间点恢复(PITR)

# 恢复到一个特定时间点的完整流程:
# 1. 确认目标时间点有完整 WAL 覆盖
# 2. 创建新集群,指定 recovery target
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster-restored
  namespace: database
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  instances: 1  # 恢复时通常先单节点验证

  bootstrap:
    recovery:
      source: my-pg-cluster
      database: myapp
      owner: myapp_user
      recoveryTarget:
        targetTime: "2026-09-28T14:30:00+08:00"
        # 目标时间点必须有 base backup + 前后 WAL 完整

  storage:
    size: 100Gi
    storageClass: ssd-block

关键:PITR 指定的时间点必须在 oldest_full_backup_time 与 latest_wal_time 之间。可用 kubectl cnpg backup --immediate-check 验证连续性。


五、Replica 管理与故障自动切换

5.1 复制拓扑

# 查看集群拓扑与角色
kubectl cnpg status my-pg-cluster -n database

输出示例:

Cluster Summary
Name:              my-pg-cluster
Namespace:         database
PostgreSQL Image:  ghcr.io/cloudnative-pg/postgresql:16.2
Instances:         3
Primary:           my-pg-cluster-1
    Status:        OK

5.2 同步与异步复制

spec:
  postgresql:
    synchronous:
      method: first
      number: 1  # 至少 1 个同步副本
      # 等效于 synchronous_standby_names = 'FIRST 1 (*)'
方法含义
method: firstFIRST n (standby_names) 列前 n 个
method: anyANY n (standby_names) 任意 n 个

5.3 手动发起 Failover

# 手动触发 failover(将 Primary 切换到指定实例)
kubectl cnpg promote my-pg-cluster my-pg-cluster-2 -n database
# 查看复制延迟
kubectl cnpg status my-pg-cluster -n database --verbose
# 关注 streaming replication lag

5.4 故障自动恢复

CloudNativePG 内置健康检测与自动故障切换:

  1. 检测到 Primary Pod 不健康
  2. 选举同步副本中最健康的实例
  3. 提升为 Primary
  4. 更新 Service Endpoint 指向新 Primary
  5. 其余 Replica 自动重新连接到新 Primary

六、Secret 与访问控制编排

6.1 Secret 自动生成

apiVersion: v1
kind: Secret
metadata:
  name: myapp-db-secret
  namespace: database
type: Opaque
stringData:
  username: myapp_user
  password: "$(openssl rand -base64 32)"

CloudNativePG 创建的 cluster 会自动生成以下 Secret:

Secret 名称内容
{cluster}-app应用账号(owner)用户名密码
{cluster}-superuserpostgres superuser 密码
{cluster}-ca集群内部 TLS CA
{cluster}-replicationreplication 密码

6.2 pg_hba.conf 与网络策略

spec:
  postgresql:
    pg_hba:
      # 仅允许同 namespace 内访问
      - hostssl all all 10.0.0.0/8 scram-sha-256
      # K8s 内部 service CIDR
      - hostssl all all 172.16.0.0/12 scram-sha-256

6.3 网络隔离

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pg-allow-internal
  namespace: database
spec:
  podSelector:
    matchLabels:
      cnpg.io/cluster: my-pg-cluster
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: backend
      ports:
        - protocol: TCP
          port: 5432

七、滚动升级策略

7.1 Minor PostgreSQL 版本升级

# 修改 imageName 到新 minor 版本
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.3
kubectl apply -f cluster.yaml
# Operator 会自动:
#   1. 依次对 replica 执行滚动重启
#   2. 最后对 primary 执行 switchover + 重启
#   3. 保证整个过程中集群可用

7.2 大版本升级

# CloudNativePG 支持通过 pg_upgrade 进行大版本升级
# 需要创建新的 Cluster,并挂载旧版本的数据 PVC
kubectl cnpg backup my-pg-cluster -n database --immediate
# 然后用新版本镜像创建 new-cluster,指定 recovery.source

常见问题(FAQ)

可以用 CloudNativePG 替代自建高可用方案吗?

完全可以。CloudNativePG 已在多家生产环境中验证,其故障切换、备份管理、滚动升级能力覆盖大多数场景。如果你的 K8s 集群自身高可用有缺陷(如单 Master、不可靠的网络分区处理),Operator 也无法弥补。

备份存在哪里最安全?

建议同时满足三条原则:

  1. 跨区冗余:对象存储桶开启跨区域复制
  2. 不可变策略:S3 Object Lock / GCS Retention Policy,防止备份被误删
  3. 定期恢复演练:每月至少一次用 PITR 恢复到测试环境

为什么 Primary 重启时会触发连接中断?

PostgreSQL 进程的连接伴随进程本身存在。任何 Primary 重启(即使是 switchover)都会终止当前活跃连接。建议应用层配置连接池重试策略(如 HikariCP connectionTestQuery + connectionTimeout),或在前端加 PgBouncer 作为 TCP 层缓冲。


相关阅读

延伸阅读


完整示例(一键复制)

# ========== CloudNativePG Cluster 完整模板 ==========
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: production-pg
  namespace: database
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  instances: 3

  postgresql:
    pg_hba:
      - hostssl all all 0.0.0.0/0 scram-sha-256
    synchronous:
      method: first
      number: 1

  bootstrap:
    initdb:
      database: myapp
      owner: myapp_user
      secret:
        name: myapp-db-secret

  storage:
    size: 500Gi
    storageClass: ssd-block

  monitoring:
    enabled: true
    customQueriesConfigMap:
      name: cnpg-custom-queries

  backup:
    enabled: true
    retentionPolicy: "30d"
    schedule: "0 2 * * *"
    barmanObjectStore:
      destinationPath: "s3://my-backup-bucket/pg-backups"
      s3Credentials:
        inheritFromIAMRole: true

  resources:
    requests:
      memory: "4Gi"
      cpu: "2"
    limits:
      memory: "8Gi"
      cpu: "4"

  affinity:
    enablePodAntiAffinity: true
    topologyKey: kubernetes.io/hostname

---
# ========== 每天凌晨 2 点的定时备份 ==========
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: daily-backup
  namespace: database
spec:
  immediate: true
  schedule: "0 2 * * *"
  cluster:
    name: production-pg
  backupOwnerReference: self

---
# ========== PITR 恢复用的 Cluster 定义 ==========
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: production-pg-restored
  namespace: database
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  instances: 1

  bootstrap:
    recovery:
      source: production-pg
      database: myapp
      owner: myapp_user
      recoveryTarget:
        targetTime: "2026-09-28T14:30:00+08:00"

  storage:
    size: 500Gi
    storageClass: ssd-block
# ========== 常用 kubectl cnpg 命令 ==========

# 查看集群状态
kubectl cnpg status production-pg -n database

# 查看集群拓扑
kubectl cnpg status production-pg -n database --verbose

# 手动备份
kubectl cnpg backup production-pg -n database --immediate

# 手动提升 replica 为 primary
kubectl cnpg promote production-pg production-pg-2 -n database

# 查看集群 pod 日志
kubectl logs -n database -l cnpg.io/cluster=production-pg

# 进入 primary 数据库
kubectl cnpg psql production-pg -n database

# 查看所有备份
kubectl get backup -n database

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章

  1. Supabase 平台与 PostgreSQL 边缘函数实践
  2. PostgreSQL 事件触发器与审计日志实现
  3. PostgreSQL 统计信息与查询计划器:ANALYZE、pg_statistic 与代价模型