Skip to content
Published on

在 K8s 上运维 DB — CNPG、Percona、Vitess、Helm chart 完全指南

分享
Authors

引言

在 Kubernetes 上运维数据库,几年前还是一个颇具争议的话题。 “在为 Stateless 工作负载优化的 K8s 上,为什么偏偏要跑 DB?”这样的疑问非常多。 但到了 2026 年的今天,随着 StatefulSet 与 Operator 生态足够成熟,在 K8s 上运维 DB 已经成为标准选项之一。

本文对比主流的数据库 Operator 与 Helm chart,并整理在实战中什么场景该用什么工具。


1. 为什么要在 K8s 上运维 DB

优点

  • 统一的部署流水线:用同一套 GitOps 工作流管理应用与 DB
  • 资源效率:节点资源由应用与 DB 共享,通过自动调度把利用率拉满
  • 自动恢复:Pod 故障时自动重启,节点故障时自动重新调度
  • 环境一致性:在开发/预发/生产用声明式方式部署完全相同的 DB 配置
  • 成本下降:相比托管 DB 服务(RDS、Cloud SQL),可以降低许可与基础设施成本

缺点

  • 运维复杂度:存储、网络、备份等需要自己直接管理的领域很多
  • 性能开销:容器网络、存储层的叠加会带来额外延迟
  • 要求专业度:需要对 K8s 与 DB 两侧都有深入理解
  • 数据丢失风险:错误的 PV/PVC 配置或升级失误都可能导致数据损失

StatefulSet 基础

StatefulSet 是 K8s 中面向需要保持状态的工作负载的控制器。 与普通 Deployment 不同,它保证以下几点。

  • 稳定的网络标识:每个 Pod 拥有并保持固有的主机名(例如 postgres-0、postgres-1)
  • 有序的部署/伸缩:Pod 从 0 开始按顺序创建,并按逆序终止
  • 持久化存储:通过 volumeClaimTemplates 为每个 Pod 绑定专属的 PVC
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ['ReadWriteOnce']
        storageClassName: gp3
        resources:
          requests:
            storage: 50Gi

但仅靠 StatefulSet 很难实现 HA、自动 Failover、备份/恢复、监控等能力。 解决这部分问题的正是 Operator 模式。


2. CloudNativePG (CNPG) — PostgreSQL 专用 Operator

概述

CloudNativePG(CNPG) 由 EDB(EnterpriseDB) 发起开发,目前作为 CNCF Sandbox 项目维护,是 PostgreSQL 专用的 Kubernetes Operator。 截至 2026 年 4 月,最新版本为 1.29,它用 Image Catalog 与 artifacts 生态对 PostgreSQL 扩展管理做了革新性的改进。

核心特性

  • 原生 K8s 设计:不依赖 Patroni 这类外部 HA 工具,直接使用 K8s 的领导者选举机制
  • 声明式 DB 管理:用 Database CRD 管理 PostgreSQL 数据库的生命周期
  • 逻辑复制:用 Publication / Subscription CRD 支持在线迁移与大版本升级
  • CNPG-I 插件框架:可以通过外部插件扩展功能
  • 支持 PITR:基于 WAL 归档的 Point-In-Time Recovery
  • 并行 reconciler:以并行处理提升集群管理效率

安装

用 Helm 安装最为简便。

# 添加 Helm 仓库
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update

# 安装 Operator
helm upgrade --install cnpg \
  --namespace cnpg-system \
  --create-namespace \
  cnpg/cloudnative-pg

也可以直接用 manifest 安装。

kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.0.yaml

HA 架构

CNPG 采用 Primary 1 台 + Standby N 台的结构。 Primary 负责写入,Standby 通过流复制同步数据。

  • Primary 故障时,其中一台 Standby 会自动升主 (Promote)
  • Switchover(计划内切换)与 Failover(故障时切换)都支持
  • 提供通过同步复制保障数据持久性的选项 (dataDurability)

备份与恢复

CNPG 内置了基于 Barman 的持续备份。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
spec:
  instances: 3
  storage:
    size: 100Gi
    storageClass: gp3
  backup:
    barmanObjectStore:
      destinationPath: s3://my-backup-bucket/prod-pg/
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: SECRET_ACCESS_KEY
      wal:
        compression: gzip
    retentionPolicy: '30d'

可以用 ScheduledBackup 预约定期备份。

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: prod-pg-daily
spec:
  schedule: '0 2 * * *'
  cluster:
    name: prod-pg
  backupOwnerReference: self

3. Percona Operator — 多 DB 支持

概述

Percona 为 MySQL、MongoDB、PostgreSQL 三种数据库分别提供专用的 Kubernetes Operator。 它以 Apache 2.0 许可完全开源,企业级功能可以免费使用。

各支持 DB 的特点

Percona Operator for MySQL (PXC)

  • 基于 Percona XtraDB Cluster 的多主 (Multi-Primary) 架构
  • 通过 Galera 同步复制,所有节点都可读写
  • 借助 ProxySQL 或 HAProxy 实现自动路由
  • 2026 年 GA 版本中新增 Group Replication 选项

Percona Operator for MongoDB (PSMDB)

  • 支持 ReplicaSet 与 Sharded Cluster
  • 支持基于 PVC 快照的备份(2025 年新增)
  • 正式支持 MongoDB 8.0
  • 用 IAM Role for Service Account 完成云存储认证

Percona Operator for PostgreSQL (PPG)

  • 基于 Patroni 的 HA 构成
  • 原生支持 pg_tde(透明数据加密)(2026 年)
  • 零停机大版本升级的路线图正在推进中

统一监控:PMM

Percona Monitoring and Management(PMM) 是一款可以在同一个仪表盘中监控 MySQL、MongoDB、PostgreSQL 全部实例的工具。

apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
  name: prod-mysql
spec:
  crVersion: '1.15.0'
  pxc:
    size: 3
    image: percona/percona-xtradb-cluster:8.0
    resources:
      requests:
        memory: 2Gi
        cpu: '1'
    volumeSpec:
      persistentVolumeClaim:
        storageClassName: gp3
        resources:
          requests:
            storage: 100Gi
  haproxy:
    enabled: true
    size: 2
  pmm:
    enabled: true
    serverHost: monitoring-service
  backup:
    schedule:
      - name: daily-backup
        schedule: '0 3 * * *'
        keep: 7
        storageName: s3-backup
    storages:
      s3-backup:
        type: s3
        s3:
          bucket: my-backup-bucket
          credentialsSecret: aws-creds
          region: ap-northeast-2

多集群支持

Percona Operator 支持跨区域复制,可以在多个 K8s 集群之间同步数据。 借此可以搭建灾难恢复 (DR) 架构。


4. Vitess — MySQL 水平分片

概述

Vitess 最初由 YouTube 为处理大规模 MySQL 工作负载而开发,目前是 CNCF Graduated 项目。 它在提供与 MySQL 兼容接口的同时,支持透明分片、连接池化与在线重分片。

PlanetScale 是把 Vitess 商业化的代表性 DBaaS 服务。

架构组成要素

组成要素作用
VTGate查询路由器,应用连接的端点
VTTablet包裹每个 MySQL 实例的代理
Topology Service保存集群元数据 (etcd 等)
VTOrcOrchestrator,负责自动 Failover
VTAdmin基于 Web 的管理 UI

什么时候该选择 Vitess

  • 单个 MySQL 实例无法承载的 大规模写入流量
  • 需要对数十亿行以上的表做 大容量表分片 的场景
  • 在线 schema 变更(Online DDL)频繁的环境
  • 在保持 MySQL 兼容性的同时需要 水平扩展 的场景

在 K8s 上安装 Vitess

使用 PlanetScale 提供的 Vitess Operator。

# 安装 Vitess Operator
kubectl apply -f https://github.com/planetscale/vitess-operator/releases/latest/download/operator.yaml

以声明式方式管理 Keyspace(逻辑数据库)与 Shard。

apiVersion: planetscale.com/v2
kind: VitessCluster
metadata:
  name: prod-vitess
spec:
  images:
    vtgate: vitess/lite:v19
    vttablet: vitess/lite:v19
    vtbackup: vitess/lite:v19
    vtctld: vitess/lite:v19
    vtorc: vitess/lite:v19
  cells:
    - name: zone1
      gateway:
        replicas: 2
        resources:
          requests:
            cpu: '1'
            memory: 1Gi
  keyspaces:
    - name: commerce
      turndownPolicy: Immediate
      partitionings:
        - equal:
            parts: 2
            shardTemplate:
              databaseInitScriptSecret:
                name: commerce-schema
                key: init.sql
              tabletPools:
                - cell: zone1
                  type: replica
                  replicas: 3
                  dataVolumeClaimTemplate:
                    storageClassName: gp3
                    resources:
                      requests:
                        storage: 50Gi

注意事项

Vitess 非常强大,但学习曲线相当陡峭。 对简单的 CRUD 应用来说可能是过度选择,分片策略 (Vschema) 的设计也需要充分论证。


5. 主流 Helm chart 对比

即便不用 Operator,只靠 Helm chart 也能把 DB 部署到 K8s 上。 Bitnami 曾是使用最广泛的 Helm chart 提供方,但 2025 年之后出现了重要变化。

Bitnami 许可变更 (2025)

2025 年 9 月之后,大部分 Bitnami Helm chart 的 OCI 包被移到了 Broadcom 的付费订阅之后。 公开的 docker.io/bitnami 镜像被迁到“Bitnami Legacy”仓库,不再获得更新、修复与安全补丁。

作为替代方案,Chainguard 提供了 40 多个从 Bitnami 分叉而来的安全加固 Helm chart, 社区主导的替代品也在持续增加。

主流 Helm chart 对比表

chartDB默认构成HA 支持内置备份备注
bitnami/postgresqlPostgreSQLPrimary + Read Replica基于 RepmgrX注意 legacy 问题
bitnami/postgresql-haPostgreSQLPrimary + Standby对接 Pgpool-IIXHA 专用 chart
bitnami/mysqlMySQLPrimary + Secondary半同步复制X有 InnoDB Cluster 选项
bitnami/redisRedisMaster + Replica基于 SentinelXCluster 模式另行配置
bitnami/mongodbMongoDBReplicaSet内置XSharded 为独立 chart
bitnami/mariadbMariaDBPrimary + Secondary可选 GaleraX兼容 MySQL

适合使用 Helm chart 的场景

  • 开发/测试环境:想快速把 DB 跑起来的时候
  • 简单构成:HA 并非必选项的小规模服务
  • 学习用途:熟悉在 K8s 上运维 DB 的基础时
  • 自定义配置很多的情况:需要用 values.yaml 做精细调优时

示例:PostgreSQL HA Helm chart

helm install prod-pg bitnami/postgresql-ha \
  --set postgresql.replicaCount=3 \
  --set postgresql.resources.requests.memory=2Gi \
  --set postgresql.resources.requests.cpu=1 \
  --set persistence.size=100Gi \
  --set persistence.storageClass=gp3 \
  --set pgpool.replicaCount=2 \
  --set metrics.enabled=true

6. CNPG vs Percona vs Vitess vs Helm chart 综合对比

功能对比表

项目CNPGPerconaVitessHelm chart
支持的 DBPostgreSQLMySQL、MongoDB、PGMySQL(分片)多样
许可Apache 2.0Apache 2.0Apache 2.0因 chart 而异
CNCF 状态Sandbox-Graduated-
HA 自动 FailoverOOO因 chart 而异
自动备份O (Barman)O(多存储后端)OX(需另行配置)
PITROO部分支持X
水平分片X仅 MongoDBO(核心能力)X
监控集成PrometheusPMM + PrometheusVTAdmin各 chart 自带指标
连接池化内置 PgBouncerProxySQL/HAProxy内置 VTGate需另行配置
运维难度
生产适配度高(大规模)中等
基于 CRD 管理OOOX

选型指南

  • 在 K8s 上运维 PostgreSQL:CNPG 是首选。K8s 原生设计加上活跃的社区
  • 运维 MySQL/MongoDB:Percona Operator。统一监控 (PMM) 是它的强项
  • 大规模 MySQL 分片:Vitess。可处理数十亿行以上的大容量数据
  • 开发/测试环境:Helm chart。部署快、构成简单
  • 多 DB 环境的统一管理:Percona。把 MySQL + MongoDB + PostgreSQL 纳入同一套运维模型

7. 运维注意点

存储 (PV/PVC)

存储是 K8s DB 运维中最重要的要素。

# 推荐的 StorageClass 示例 (AWS EBS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-db
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: '5000'
  throughput: '250'
  encrypted: 'true'
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Retain

核心原则:

  • volumeBindingMode: WaitForFirstConsumer — 在 Pod 被调度到的 AZ 中创建卷
  • reclaimPolicy: Retain — 即使删除 PVC 也保留数据
  • allowVolumeExpansion: true — 允许在线扩容卷
  • WAL 与 Data 卷分离 — 把顺序写入 (WAL) 与随机访问 (Data) 分开以提升性能

性能调优

# 为 Pod 配置 CPU pinning 与 NUMA 感知的示例
spec:
  containers:
    - name: postgres
      resources:
        requests:
          cpu: '4'
          memory: 8Gi
        limits:
          cpu: '4'
          memory: 8Gi
      # 确保拿到 Guaranteed QoS 级别
      # 设置为 requests == limits

补充建议:

  • Guaranteed QoS:把 requests 与 limits 设为相同值,防止 CPU 限流
  • 拓扑感知调度:用 nodeAffinity 把 DB Pod 放到高性能节点上
  • 反亲和性:让多个 DB Pod 分散到彼此不同的节点上
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - postgres
          topologyKey: kubernetes.io/hostname

资源限制

  • DB Pod 必须设置 resources.requests 与 limits
  • 内存超过 limits 会触发 OOMKill — DB 进程会被强制终止
  • PostgreSQL 的 shared_buffers 设为容器内存的 25% 上下
  • MySQL 的 innodb_buffer_pool_size 设为容器内存的 50~70%

监控

# 在 CNPG 中启用 PodMonitor
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
spec:
  instances: 3
  monitoring:
    enablePodMonitor: true
    customQueriesConfigMap:
      - name: pg-custom-queries
        key: queries
  storage:
    size: 100Gi

必备监控指标:

  • 复制延迟(Replication Lag):Standby 相比 Primary 落后了多少
  • 连接数:相对最大连接数的使用率
  • 事务吞吐量(TPS):每秒事务数
  • 磁盘使用率:在 PV 容量耗尽前配置告警
  • WAL 归档状态:备份系统是否正常运转

备份策略

把 3-2-1 备份法则应用到 K8s 环境。

  • 3 份副本:Primary 数据 + WAL 归档 + 物理备份
  • 2 种不同介质:本地 PV + Object Storage (S3/GCS)
  • 1 份异地副本:复制到其他区域的存储桶
# CNPG 恢复集群示例
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: recovery-cluster
spec:
  instances: 2
  storage:
    size: 100Gi
  bootstrap:
    recovery:
      source: prod-pg
      recoveryTarget:
        targetTime: '2026-04-10T08:00:00Z'
  externalClusters:
    - name: prod-pg
      barmanObjectStore:
        destinationPath: s3://my-backup-bucket/prod-pg/
        s3Credentials:
          accessKeyId:
            name: aws-creds
            key: ACCESS_KEY_ID
          secretAccessKey:
            name: aws-creds
            key: SECRET_ACCESS_KEY

8. 实战示例:用 CNPG 搭建 PostgreSQL HA 集群

下面是可以直接用在生产环境的 CNPG 集群完整 YAML。

Step 1: 创建 Namespace 与 Secret

kubectl create namespace database

kubectl create secret generic pg-superuser \
  --namespace database \
  --from-literal=username=postgres \
  --from-literal=password=CHANGE_ME_TO_STRONG_PASSWORD

kubectl create secret generic aws-creds \
  --namespace database \
  --from-literal=ACCESS_KEY_ID=your-access-key \
  --from-literal=SECRET_ACCESS_KEY=your-secret-key

Step 2: 定义 PostgreSQL 集群

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
  namespace: database
spec:
  description: 'Production PostgreSQL HA Cluster'
  imageName: ghcr.io/cloudnative-pg/postgresql:16.4

  instances: 3

  startDelay: 30
  stopDelay: 30
  primaryUpdateStrategy: unsupervised

  postgresql:
    parameters:
      shared_buffers: '2GB'
      effective_cache_size: '6GB'
      work_mem: '64MB'
      maintenance_work_mem: '512MB'
      max_connections: '200'
      max_wal_size: '2GB'
      min_wal_size: '1GB'
      wal_buffers: '64MB'
      random_page_cost: '1.1'
      effective_io_concurrency: '200'
      log_statement: 'ddl'
      log_min_duration_statement: '1000'
    pg_hba:
      - host all all 10.0.0.0/8 scram-sha-256

  bootstrap:
    initdb:
      database: appdb
      owner: appuser
      secret:
        name: pg-superuser

  storage:
    size: 100Gi
    storageClass: gp3-db

  walStorage:
    size: 30Gi
    storageClass: gp3-db

  resources:
    requests:
      memory: 8Gi
      cpu: '4'
    limits:
      memory: 8Gi
      cpu: '4'

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

  monitoring:
    enablePodMonitor: true

  backup:
    barmanObjectStore:
      destinationPath: s3://my-backup-bucket/prod-pg/
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: SECRET_ACCESS_KEY
      wal:
        compression: gzip
        maxParallel: 4
      data:
        compression: gzip
        jobs: 4
    retentionPolicy: '30d'

  nodeMaintenanceWindow:
    inProgress: false
    reusePVC: true

Step 3: 定期备份计划

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: prod-pg-daily
  namespace: database
spec:
  schedule: '0 2 * * *'
  backupOwnerReference: self
  cluster:
    name: prod-pg
  target: prefer-standby

Step 4: 配置 Pooler (PgBouncer)

apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
  name: prod-pg-pooler-rw
  namespace: database
spec:
  cluster:
    name: prod-pg
  instances: 2
  type: rw
  pgbouncer:
    poolMode: transaction
    parameters:
      max_client_conn: '1000'
      default_pool_size: '50'

Step 5: 部署与确认

kubectl apply -f cluster.yaml
kubectl apply -f scheduled-backup.yaml
kubectl apply -f pooler.yaml

# 确认集群状态
kubectl get cluster -n database

# 确认 Pod 状态
kubectl get pods -n database

# 集群详细信息
kubectl describe cluster prod-pg -n database

# 测试连接 Primary
kubectl exec -it prod-pg-1 -n database -- psql -U postgres -d appdb

9. 反模式 — 在 K8s 上运维 DB 时要避开的错误

1) 用 Deployment 部署 DB

Deployment 是给无状态 (Stateless) 工作负载用的。 对 DB 使用 Deployment,会出现 Pod 重启时数据丢失,或多个 Pod 同时访问同一数据目录的问题。 请务必使用 StatefulSetOperator CRD

2) 不用 PVC 而用 emptyDir

emptyDir 会随着 Pod 被删除而一同消失。 即便是测试环境,也请养成给 DB 数据使用 PVC 的习惯。

3) 没有备份就上线运行

即便 Operator 提供了 HA,也 必须单独配置备份。 HA 是针对基础设施故障的保护,而备份是针对逻辑错误(比如写错的 DELETE 语句)的保护。

4) 不设置资源限制

如果不给 DB Pod 设置 limits,它可能会侵占其他 Pod 的资源, 或在 OOM 时于无法预测的时点被终止。 请把 requests 与 limits 设为相同值,确保拿到 Guaranteed QoS。

5) 使用 reclaimPolicy: Delete

如果使用默认 reclaimPolicy 为 Delete 的 StorageClass, 删除 PVC 时 PV(也就是真实数据)会被一并删除。 DB 用的 StorageClass 请务必设置为 Retain

6) 把所有 DB Pod 放在单个 AZ

不配置 Pod Anti-Affinity 就部署,所有 DB Pod 可能被排到同一个节点或 AZ 上。 一旦该节点/AZ 故障,整个 DB 都会宕掉。

# 正确的 Anti-Affinity 配置
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: cnpg.io/cluster
              operator: In
              values:
                - prod-pg
        topologyKey: topology.kubernetes.io/zone

7) 没有监控/告警就上线运行

可能会出现复制延迟持续增大或磁盘被写满却无人察觉的情况。 至少要配置下面这些告警。

  • 磁盘使用率超过 80%
  • 复制延迟达到 10 秒以上
  • Pod 重启次数上升
  • 检测到备份失败

8) DB 升级时未验证滚动更新

PostgreSQL、MySQL 等的大版本升级,请务必先在独立集群上测试后再执行。 即便 Operator 支持自动升级,应用兼容性测试仍然必须由人来做。

9) 用明文管理 Secret

请不要把 DB 密码硬编码进 YAML。 请使用 External Secrets Operator 或 Sealed Secrets 来安全地管理 Secret。

# External Secrets 示例
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: pg-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: pg-superuser
  data:
    - secretKey: username
      remoteRef:
        key: prod/database/credentials
        property: username
    - secretKey: password
      remoteRef:
        key: prod/database/credentials
        property: password

结语

在 K8s 上运维 DB 已经不再是实验性的选择。 CNPG、Percona、Vitess 这些成熟的 Operator 把复杂的运维工作自动化, 并与 K8s 的声明式管理模型自然融合。

核心要点

  • 若要在 K8s 上运维 PostgreSQL,CloudNativePG 是最佳选择
  • 若要统一管理 MySQL/MongoDB/PostgreSQL,就用 Percona Operator
  • 若需要大规模 MySQL 分片,则选 Vitess
  • 在开发/测试环境中,Helm chart 依然最为便利
  • 无论选择哪种工具,存储、备份、监控 都必须落实到位

如果要引入 Operator,请先在开发环境充分测试, 并模拟故障场景(删除 Pod、节点宕机、AZ 故障)之后再应用到生产环境。