上个月帮一个内部项目搭数据库层,需求很明确:业务量不大,但不想把数据库放在单点上,要求先有一套 PostgreSQL 主从集群,同时把连接池和读写入口的问题一起解决。折腾一圈之后,最后落地的方案就是标题里这套组合:Helm 部署 Bitnami 的 PostgreSQL chart,前面再挂一个 Bitnami 的 Pgpool chart。这套方案最大的价值在于,不用手写 StatefulSet,不用自己拼初始化脚本,也不用纠结每个 Pod 的探针和配置挂载,大部分基础设施层面的脏活都被 chart 处理掉了。
这篇文章适合两类人看。一类是已经在用 Kubernetes、想快速搭一套能用的 PostgreSQL 主从 + 读写分离入口的运维或后端同学;另一类是对 Helm 部署中间件还不太熟、想找一个完整例子练手的人。我会按“为什么这么选 → 环境准备 → 部署 PostgreSQL → 部署 Pgpool → 读写分离验证 → 常见坑”的顺序讲,命令和 values 配置都会给到位,照着做基本能复现。
先提醒一句,这里说的“集群”是指 PostgreSQL 的一主多从拓扑,不是 Hadoop 那种分布式计算集群。PostgreSQL 本身是单机数据库,它的“集群”靠流复制把主库数据同步到只读副本,配合中间件对外暴露统一入口。理解这一点,后面所有配置就不会困惑了。
1. 方案选型与架构拆解
动手写 values 之前,先把选型逻辑说清楚。很多人看到标题第一反应是:为什么是 Bitnami,而不是裸写 Helm chart,或者直接用数据库 Operator?我一开始也在这几个方案之间犹豫过。
1.1 为什么是 Bitnami,而不是裸 Chart 或 Operator
裸手写 StatefulSet 做 PostgreSQL 主从,听起来很“硬核”,实际维护成本极高。你要自己处理的东西包括但不限于:primary 和 replica 的配置文件差异、流复制用户的创建与认证、Pod 重启后的数据卷挂载、readinessProbe 和 livenessProbe 的探测命令、升级时的滚动策略。这些东西单独拎出来都不难,但叠在一起就是大量边缘情况。真要在生产环境手写一套,光测试就要花掉不少时间。
Operator 方案比如 CloudNativePG 或 Zalando 的 postgres-operator,功能确实强,可以做自动故障转移、备份恢复、表空间管理。但它的学习曲线和概念模型也重,对于只需要“一主一从 + 一个连接池入口”的中小规模项目来说,有点杀鸡用牛刀。
Bitnami 的 chart 处在中间位置:它把 PostgreSQL 打包成参数化程度很高的 Helm chart,镜像基于官方 PostgreSQL,社区用的人多,踩坑案例也容易搜到。它帮你处理好了探针、权限、初始化脚本、副本配置这些底层逻辑,同时又保留了大量 values 参数让你控制集群形态。部署节奏很快,后期维护也不会太痛苦。对我这次的需求来说,这是最稳的折中选择。
1.2 角色分工:PostgreSQL 负责存储,Pgpool 负责入口
这套架构里,PostgreSQL 和 Pgpool 分工很明确。
PostgreSQL 这一侧,通过 Bitnami chart 的architecture: replication模式,拉起一个 primary 和一个或多个 read replica。primary 负责所有写操作,通过流复制把 WAL 日志持续同步到 replica。replica 只接受读请求,具备只读副本的全部能力。默认情况下这套复制是异步的,也就是说主库和从库之间存在极小的数据延迟,但对大量读多写少的业务场景已经够用。
Pgpool 站在所有应用连接的最前面。应用不再直接连 PostgreSQL,而是统一连到 Pgpool 的 Service 上。Pgpool 至少帮你做三件事:
- 连接池:复用后端数据库连接,减少 PostgreSQL 频繁建立连接的开销。
- 读写分离:识别 SQL 是读还是写,把写流量转发给 primary,把读流量按权重分发给 replica。
- 健康检查:周期性探测后端节点状态,节点不可用时把对应后端标记为 down,避免请求被打到故障节点上。
简单说,PostgreSQL 决定数据怎么存、怎么复制,Pgpool 决定连接怎么进、请求怎么分发。两者合在一起,对外体现为一个数据库地址,对内体现为一主一从的高可用拓扑。
1.3 版本组合与前置条件
我这次用的版本组合是:Kubernetes 1.28+、Helm 3.14、PostgreSQL 16、Pgpool 4.5。Bitnami 的 chart 版本会持续更新,注意锁定你验证过的版本,别直接追最新版。
前置条件列一张表:
| 条件 | 要求 | 用途 |
|---|---|---|
| Kubernetes 集群 | 1.28 及以上,建议至少 3 节点 | 提供 Pod 调度和故障恢复 |
| Helm | 3.x | 部署和管理 chart |
| 存储类 | 支持动态创建 PVC,云盘或 local-path 均可 | PostgreSQL 数据持久化 |
| 节点资源 | 每个数据库节点至少 4 核 8G,本地测试也建议 4G 以上 | primary + replica + pgpool 都要吃内存 |
本地测试的话,minikube 或者 k3s 都行,但注意如果是单节点,就没法演示节点故障场景。生产环境建议 primary 和 replica 通过节点亲和性分开调度,避免同一台物理机挂掉后整个集群一起遭殃。
2. 环境准备:Helm、仓库与命名空间
环境准备这一步看起来很简单,但很多人翻车就翻在没先做“参数勘察”,直接照着网上的配置文件套,结果 chart 版本一升级,字段全变了。这里我会把每一步的目的也讲清楚。
2.1 安装 Helm 并添加 Bitnami 仓库
先确认你的 Helm 版本:
helm version --short只要输出类似v3.14.0就行。如果本地还没装 Helm,请参考官方安装文档,mac 用户直接brew install helm,Linux 用户可以用官方脚本或包管理器,这里不展开了。
接着添加 Bitnami 仓库并更新索引:
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update添加之后,建议先确认你当前能拿到哪些版本的 chart:
helm search repo bitnami/postgresql helm search repo bitnami/pgpool生产环境记住一个习惯:用--version锁住 chart 版本。不加锁的情况下,别人在另一个时间点部署,拉到的 chart 版本可能不同,生成的 Service 名称和 values 字段也就可能不同。
2.2 用 helm show values 摸清 Chart 参数
这一步是最容易被跳过的,但恰恰是最重要的。Bitnami 的 chart 文档更新很快,网上的博客写得再好也赶不上版本变化。正确做法是把默认 values 拉到本地,先看一遍再改。
helm show values bitnami/postgresql > postgresql-values.yaml helm show values bitnami/pgpool > pgpool-values.yaml比如在 PostgreSQL 的 values 里,你要重点关注这几个区域:
architecture:设置成replication才会创建主从。auth:PostgreSQL 的超级用户密码、复制用户密码。primary:主库的资源、存储、节点选择。readReplicas:从库数量、存储、资源。
Pgpool 的 values 里重点关注:
auth:Pgpool 用来连接后端 PostgreSQL 的用户名和密码。pgpool.backends:后端数据库节点列表,也就是 primary 和 replica 的地址。service:Pgpool 对外暴露方式。
不同 chart 版本的字段命名可能略有差异,所以实际操作时以你手里的helm show values输出为准。这不是废话,我见过有人把旧版配置直接套到新版上,结果 Pgpool 明明起来了,却始终找不到后端节点。
2.3 命名空间与网络规划
部署之前先规划好命名空间。我这次统一放在database命名空间里:
kubectl create ns database为什么要单独规划命名空间?因为 Pgpool 需要访问后端的 PostgreSQL 节点。如果两者在同一个命名空间,Pods 之间可以通过 Service 短名访问,比如mypg-postgresql-primary和mypg-postgresql-read。一旦跨了命名空间,就得写完整的 FQDN,比如mypg-postgresql-primary.database.svc.cluster.local。虽然也不是不行,但配置起来容易出错,也不利于权限隔离。
另外,如果你在集群里启用了 NetworkPolicy,记得允许 database 命名空间内部 5432 端口的互访,否则 Pgpool 会一直报连接超时。
3. 部署 PostgreSQL 读写分离集群
这一章是核心中的核心。先把 PostgreSQL 的主从集群拉起来,后面的 Pgpool 才有后端可连。
3.1 最小可用 values 配置
在刚才拉取下来的postgresql-values.yaml基础上,裁剪出一份最小可用配置。以 release 名mypg为例:
architecture: replication auth: postgresPassword: "change-me-postgres" replicationPassword: "change-me-repl" database: "appdb" username: "appuser" primary: persistence: size: 8Gi resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: "1" readReplicas: replicaCount: 1 persistence: size: 8Gi resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: "1"逐项解释一下为什么这样写。
architecture: replication是总开关。不设这个字段,chart 默认部署单实例,后面的readReplicas.replicaCount根本不会生效。
auth.postgresPassword是 PostgreSQL 超级用户postgres的密码。auth.replicationPassword是专门用于流复制的用户repl_user的密码。这两个密码必须设置,否则 chart 会在初始化时生成随机密码,你后面连接会非常痛苦。auth.database和auth.username让 chart 在初始化时顺便创建一个业务数据库和业务用户,避免后面手动建库建用户。
primary.persistence和readReplicas.persistence分别控制主库和从库的存储容量。注意这两份 PVC 是独立创建的,容量最好保持一致,否则以后从库追上主库时数据量不够用。
资源限制这里只给了保守值。实际生产建议主库至少 2Gi 内存起步,如果业务复杂,4Gi 也不为过。不要省略resources,不限制资源的话,Pod 可能把节点内存吃满,触发 OOMKilled,那画面太惨。
3.2 部署并验证主从就绪
执行安装:
helm install mypg bitnami/postgresql \ --namespace database \ --values postgresql-values.yaml \ --version 12.x.x--version里的具体版本号自己替换成helm search repo bitnami/postgresql查到的稳定版本。安装过程中可以用两个终端,一个执行安装,另一个执行:
kubectl get pods -n database -w正常情况下你会看到类似这样的状态变化:
mypg-postgresql-primary-0先进入Pending,等 PVC 绑定后变成ContainerCreating,最后Running。- 随后
mypg-postgresql-read-0开始创建,它初始化之后会通过流复制从 primary 拉取数据。
Pod 全部 Running 之后,再看 Service:
kubectl get svc -n databaseBitnami 在architecture: replication模式下,通常会生成这样几个 Service:
mypg-postgresql:对外的主服务入口,一般指向 primary。mypg-postgresql-primary:primary 的独立 Service。mypg-postgresql-read:所有只读副本的聚合入口。mypg-postgresql-headless:用于 StatefulSet 内部稳定网络标识。
有了这几个 Service 名,后面配 Pgpool 时直接拿来当 host 用。
现在验证主从复制是否正常。临时起一个 psql 客户端 Pod:
kubectl run psql-client --rm -it \ --restart=Never \ --namespace database \ --image docker.io/bitnami/postgresql:16 \ --env PGPASSWORD=change-me-postgres \ --env PGHOST=mypg-postgresql-primary \ -- psql -U postgres -c "SELECT pg_is_in_recovery();"主库上执行SELECT pg_is_in_recovery()返回f,表示不是只读副本,是主库。如果返回t,说明你连到了只读副本。
再查一下流复制状态:
SELECT client_addr, state, sync_state FROM pg_stat_replication;正常会有一条state = streaming的记录,sync_state通常是async,代表异步复制。到这里,PostgreSQL 主从集群已经就绪。
3.3 存储与资源要点
存储和调度是 PostgreSQL 上 K8s 之后最常见的两个坑位,这里单独强调。
存储方面,Bitnami 的镜像默认使用非 root 用户运行,PVC 挂载目录的权限不对会直接导致初始化失败。如果你用的是云厂商存储类,一般没问题;如果自己搭的 NFS 或 local-path,要确认 StorageClass 支持设置fsGroup或挂载后的目录允许非 root 写入。出现chmod: changing permissions of '/bitnami/postgresql': Operation not permitted这类错误,基本就是存储权限问题。
调度方面,如果集群有多个节点,强烈建议让 primary 和 replica 分布在不同节点上:
primary: nodeSelector: role: db-primary readReplicas: nodeSelector: role: db-standby或者用topologySpreadConstraints做打散。不这样做的话,你看着是两个副本,实际上可能调度到同一台机器上,物理节点挂了照样全挂。
4. 部署 Pgpool 作为统一入口
PostgreSQL 集群起来之后,下一步部署 Pgpool。这一章的重点是理解 Pgpool 需要的配置数据和它跟后端数据库之间的认证关系。
4.1 先理解 Pgpool 需要什么
Pgpool 本质上是一个数据库代理,它要正常工作,需要三样东西:
第一,一个能连通后端 PostgreSQL 的数据库账号。这个账号用于 Pgpool 做健康检查、在实际连接转发时以对应用户身份连接到后端。最简单的做法是直接用postgres超级用户,但生产环境强烈不建议,应该创建一个专用账号,只授予必要的权限。
第二,后端节点列表。Pgpool 必须知道 primary 和 replica 的主机名、端口。它会对每个后端做健康检查,并根据节点角色决定读写转发策略。
第三,节点角色识别能力。Pgpool 通过执行SELECT pg_is_in_recovery()来判断一个后端是主库还是从库。返回f的是主,返回t的是从。这个探活逻辑内建在 Pgpool 里,不需要你额外写脚本。
Bitnami 的 Pgpool chart 把这些配置都收敛到了 values 文件里。你只需要提供用户名密码、后端地址列表,chart 会在启动时生成 Pgpool 需要的pool_passwd文件和配置文件。
4.2 values 配置与参数解释
在pgpool-values.yaml里,我这里给出一份可用配置:
auth: username: "postgres" password: "change-me-postgres" pgpool: backends: - host: "mypg-postgresql-primary" port: 5432 - host: "mypg-postgresql-read" port: 5432 loadBalanceMode: true healthCheckPeriod: 10 healthCheckTimeout: 5 replicaCount: 2 service: type: ClusterIP这份配置的含义:
auth.username和auth.password是 Pgpool 连接后端 PostgreSQL 用的凭据。这里直接用了postgres超级用户来打通链路,适合前期验证。后面如果要收敛权限,可以在 PostgreSQL 里创建专用账号pgpool,给它pg_monitor等必要权限,再把这里的用户名密码替换掉。pgpool.backends是后端节点列表。host 填 primary 和 read 聚合 Service 的名字,port 填 5432。这里的 host 之所以能用短名,是因为 Pgpool 和 PostgreSQL 部署在同一个database命名空间。pgpool.loadBalanceMode控制读写负载均衡。打开后,查询请求会被分发到多个后端节点,写请求始终走主库。pgpool.healthCheckPeriod和pgpool.healthCheckTimeout分别控制健康检查的间隔和超时。演示环境里 10 秒和 5 秒够用,生产可以调得更保守一些。replicaCount: 2表示部署 2 个 Pgpool 副本。Pgpool 本身无状态,多副本可以避免单点。service.type: ClusterIP表示只在集群内部暴露。如果外部业务需要访问,可以改成LoadBalancer或NodePort,但通常建议数据库入口不直接暴露到外网,而是只允许业务 Pod 所在的命名空间访问。
4.3 部署和登录验证
执行安装:
helm install pgpool bitnami/pgpool \ --namespace database \ --values pgpool-values.yaml安装完成后确认 Pod 状态:
kubectl get pods -n database -l app.kubernetes.io/name=pgpool两个 Pgpool Pod 都 Running 后,进到 Pod 里直接执行show pool_nodes,这是验证节点识别最直观的方式:
kubectl exec -it -n database deploy/pgpool-pgpool -- \ psql -h 127.0.0.1 -U postgres -c "show pool_nodes;"密码会提示输入,输入change-me-postgres即可。输出的大致格式如下,不同版本列名可能略有差异:
node_id | hostname | port | status | pg_role | weight --------+----------------------------+------+--------+---------+-------- 0 | mypg-postgresql-primary | 5432 | up | primary | 0.5 1 | mypg-postgresql-read | 5432 | up | standby | 0.5这里最关键的是status和pg_role两列。status为up表示 Pgpool 能成功连上并完成健康检查;pg_role则标明了它识别出的主从角色。如果看到down,说明 Pgpool 连不上对应后端,或者后端认证报错,需要排查网络和密码。
如果你部署 Pgpool 时碰到类似password authentication failed,大概率是auth.password和 PostgreSQL 的postgresPassword不一致,或者数据库里对应账号的密码不是这个值。
5. 读写分离与故障转移验证
部署都完成之后,不能光看 Pod 是 Running 就收工。数据库这类基础设施,不实际验证读写路径,你永远不知道自己配置有没有生效。
5.1 验证读写流量走向
用一个客户端 Pod 通过 Pgpool 的 Service 连接数据库,做一轮基本验证:
kubectl run pg-test --rm -it \ --restart=Never \ --namespace database \ --image docker.io/bitnami/postgresql:16 \ --env PGPASSWORD=change-me-postgres \ --env PGHOST=pgpool-pgpool \ --env PGPORT=5432 \ -- psql -U postgres -d appdb连接成功后,先建一张测试表并插入一条数据:
CREATE TABLE IF NOT EXISTS test_table (id serial primary key, value text); INSERT INTO test_table (value) VALUES ('write-to-primary');这条 INSERT 会通过 Pgpool 转发到 primary。然后执行查询:
SELECT * FROM test_table;只要结果能返回,说明读路径是通的。如果你连续执行多次查询,并打开 Pgpool 的日志观察,会发现查询请求会被负载均衡到mypg-postgresql-read后面的副本节点上。
更直接的验证方式是再次执行show pool_nodes,并配合在 PostgreSQL 主库上观察连接来源:
SELECT usename, client_addr, application_name FROM pg_stat_activity;你会看到 Pgpool 产生的连接来自 Pgpool Pod 的 IP,而不是业务 Pod 的 IP,这就说明连接池生效了。
5.2 模拟主库不可用,观察 Pgpool 行为
只验证正常流量还不够,数据库高可用架构必须验证故障场景。这里我可以给你一个安全的演练方案。
先进入 primary Pod,通过pg_ctl stop模拟 PostgreSQL 进程不可用:
kubectl exec -it -n database mypg-postgresql-primary-0 -- \ bash -c "pg_ctl stop -D /bitnami/postgresql/data -m fast"注意用-m fast,不要用-m immediate,避免模拟误删数据。
停掉之后,等一个健康检查周期,再连 Pgpool 执行:
SHOW pool_nodes;你会发现对应 primary 的节点状态会变成down。此时如果业务继续往这个入口发写请求,会被 Pgpool 拒绝或挂起,直到节点恢复。
这就引出一个重要认知:Pgpool 本身并不负责 PostgreSQL 主从角色的自动提升。它只负责感知节点健康状态,并在节点恢复后重新纳入池子。真正的故障转移,也就是把某个从库提升为新主库,还需要在 PostgreSQL 那一侧做处理,比如用pg_ctl promote手动提升,或者接入自动故障转移组件。对绝大多数中小项目来说,默认的异步流复制 + Pgpool 健康检查已经能扛住“主库进程假死、自动重启”这一类场景;但如果是主库所在节点永久宕机,你必须有明确的恢复预案,不能指望 Pgpool 帮你变魔术。
5.3 应用接入方式的建议
验证通过后,应用侧的接入方式其实很简单。应用不再需要配置 primary 和 replica 两套数据源,只需要一个地址:
jdbc:postgresql://pgpool-pgpool.database.svc.cluster.local:5432/appdb如果是 Python、Go 等其他语言,配置项也差不多,就是把 host 换成 Pgpool 的 Service 名。
这里有两个实用建议。第一,应用的连接池上限不要设得比 Pgpool 的后端连接池大太多,否则流量一上来,连接堆积在 Pgpool 层,照样会把数据库打垮。第二,如果业务对只读副本的数据延迟很敏感,要提前评估异步复制的延迟量级,必要时在应用层针对强一致读走单独入口,而不是把所有流量都扔给 Pgpool 自动分发。
6. 常见问题与避坑实录
最后这部分是真正的干货。下面这些问题都是我在实际部署和后续维护中真实遇到过、排查过、解决过的,整理成一份速查表方便你对照。
6.1 密码认证失败
现象:Pgpool Pod 日志里持续报password authentication failed for user "postgres",或者 Pgpool 启动后所有后端节点都是down。
排查思路:
- 确认 PostgreSQL chart 里
auth.postgresPassword和你记的密码一致。Bitnami chart 在数据库初始化时设置了密码,之后如果直接改 values 里的密码再执行helm upgrade,数据库的密码不会跟着变,容易出现你以为改了、实际没改的情况。 - 如果用
auth.username创建了业务用户,确认 Pgpool 里用的也是这个用户名而非postgres。 - 检查
pg_hba.conf是否允许 Pgpool 所在网段使用scram-sha-256认证。Bitnami 默认配置一般没问题,但如果你手动改过数据库认证配置,这是高发点。
6.2 副本处于 async 状态
现象:执行SELECT client_addr, state, sync_state FROM pg_stat_replication;,所有副本的sync_state都是async。
这其实是 Bitnami 默认的异步复制模式,不是故障。异步复制的含义是主库提交事务时不会等待从库确认,因此主库异常宕机时可能丢失少量已提交但未同步到从库的数据。如果你的业务对数据丢失零容忍,需要配置同步复制,在 chart 的 values 里找postgresql.synchronousMode或类似字段。
注意:同步复制会显著增加写延迟,因为每次提交都要等从库确认。要根据业务场景权衡,不要盲目追求“最强一致性”。
6.3 PVC 权限和存储问题
现象:PostgreSQL Pod 卡在ContainerCreating,事件里出现chmod: changing permissions of '/bitnami/postgresql': Operation not permitted。
原因:Bitnami 镜像以非 root 用户运行,PVC 挂载目录的所有者不是这个用户,导致初始化时无法写入。
解决办法:
- 云厂商的 StorageClass 大多能自动处理 fsGroup,不会遇到这个问题。
- 自己搭的 NFS 或 local-path,需要确认 StorageClass 配置了正确的
fsGroup策略,或者挂载目录本身对非 root 用户可写。 - 千万别为了省事把 Pod 安全上下文改成 root 运行,那属于给自己埋雷。
6.4 升级与迁移注意事项
Bitnami 的 chart 升级频率很高,但这不代表你可以无脑helm upgrade。
最常见的坑是跨大版本升级 PostgreSQL,比如从 15 升到 16。这种升级需要在数据库层面做pg_upgrade,而不是简单替换镜像。直接替换镜像启动时,数据目录的版本不匹配,Pod 会反复崩溃。
另一个坑是helm upgrade --reuse-values。这个参数看起来很省事,但它会把旧版 values 直接搬到新版本上。如果新版 chart 改了某个字段的语义,你会在毫不知情的情况下得到一份错误配置。我的习惯是把自己改过的 values 文件提交到 git,升级时先用helm show values对比新版默认值,再显式传一份完整配置。
还有一件事容易被忽略:升级前一定先备份。PostgreSQL 表空间数据不能光靠 PVC 快照,建议结合pg_dump或至少把 PVC 快照打一个,否则升级过程中出现问题,恢复成本会很高。
最后再分享一点个人体会。这套 Helm + Bitnami + Pgpool 的组合,最适合的场景是“你已经决定用 Kubernetes 管数据库,但又不希望自己造轮子”。它把部署复杂度降得很低,但并没有把运维复杂度完全抹掉。你在生产环境真正要花心思的,仍然是备份恢复、监控告警、版本升级、容量规划这些老问题。建议上线前把pg_stat_replication的延迟监控和 Pgpool 的show pool_nodes状态采集都接到告警里,这两项指标能帮你提前发现大多数隐患。