CloudNativePG 应用连接指南:通过 Kubernetes Services 与 Secrets 接入 PostgreSQL 集群
【免费下载链接】cloudnative-pgThe most popular Kubernetes Operator for PostgreSQL.项目地址: https://gitcode.com/GitHub_Trending/cl/cloudnative-pg
本指南面向希望在 Kubernetes 集群内让应用接入 CloudNativePG 所管理 PostgreSQL 集群的开发者与运维人员,系统讲解 Operator 为每个集群自动生成的服务发现机制(DNS 解析与环境变量)、凭据 Secrets 的字段结构,以及如何遵循最佳实践安全、稳定地建立数据库连接。阅读本文后,你将掌握rw/ro/r三类服务的选择方法、-app与-superuser两类凭据的正确使用场景,并能基于仓库源码理解其底层实现,在真实集群中直接落地可运行的连接配置。
应用接入的核心原则:通过 Operator 管理的服务而非直连实例
CloudNativePG 假定应用与其托管的 PostgreSQL 集群位于同一个 Kubernetes 集群内,并通过 Operator 为每个Cluster资源创建的标准 Kubernetes 网络服务(Service)来暴露数据库访问。官方文档明确建议:
强烈推荐在应用中使用这些服务,避免直接连接某个具体的 PostgreSQL 实例,因为实例(尤其主节点)在集群生命周期内会发生切换、滚动更新或故障恢复,直连实例将导致连接中断与应用不可用。
以 CloudNativePG 的集群pg-database为例,其 Pod 名称形如pg-database-1、pg-database-2……如果应用硬编码连接pg-database-1,一旦该实例因故障被替换、或因提升/降级改变角色,应用将失去可用端点。而通过服务名连接,Kubernetes 会持续将流量转发到当前满足条件的实例上。
CloudNativePG 为集群创建的三类(四类)服务
从仓库中的服务实现源码可以确认,CloudNativePG 为每个集群默认创建以下服务(见 pkg/specs/services.go 中的CreateClusterReadWriteService、CreateClusterReadOnlyService、CreateClusterReadService、CreateClusterAnyService四个构造函数):
服务名(以集群pg-database为例) | 作用 | 底层 Selector 依据 | 对应源码函数 |
|---|---|---|---|
pg-database-rw | 指向集群主实例(Primary),支持读写 | role=primary标签 | CreateClusterReadWriteService |
pg-database-ro | 指向全部热备副本(Replica),只读 | role=replica标签 | CreateClusterReadOnlyService |
pg-database-r | 指向集群内任意一个实例(含主实例),只读 | role=instance标签(含主与备) | CreateClusterReadService |
pg-database-any | 指向所有实例(包括未就绪的 Pod) | role=instance标签,且publishNotReadyAddresses: true | CreateClusterAnyService |
服务命名遵循<CLUSTER_NAME>-<SERVICE_NAME>约定,该约定定义于 api/v1/cluster_types.go 的ServiceAnySuffix(-any)、ServiceReadSuffix(-r)、ServiceReadOnlySuffix(-ro)、ServiceReadWriteSuffix(-rw),并由 api/v1/cluster_funcs.go 中的GetServiceAnyName、GetServiceReadName、GetServiceReadOnlyName、GetServiceReadWriteName四个方法拼接生成。
其中rw服务在保证 PostgreSQL 复制上不可或缺,不能被禁用;ro与r服务可通过managed.services.disabledDefaultServices选项关闭(详见下文"服务管理")。上述默认服务名均被 CloudNativePG 保留,用户自定义服务时不可占用。
这些服务默认均为ClusterIP类型,端口映射为postgres.ServerPort(5432),且-any服务设置了publishNotReadyAddresses: true,确保实例在尚未就绪时也能被解析到——这对需要等待所有实例建立联系的运维工具很有用,但对一般业务应用请使用rw/ro/r。
服务在读写分离中的典型用法
- 读写应用(ORM、业务主流程):连接
pg-database-rw,始终打到主实例,保证写一致性。 - 只读报表/分析查询:连接
pg-database-ro,流量分散到各热备副本,减轻主实例负载;集群无副本时该服务不暴露端点。 - 容忍读主节点(如既想负载均衡又不介意主实例参与只读):连接
pg-database-r。
服务发现方式一:Kubernetes DNS 解析(推荐)
Kubernetes 集群 DNS 是 CloudNativePG 正常运行所依赖的基础设施,也是最推荐的发现方式。应用可通过服务名直接解析到对应 Service 的 ClusterIP:
- 同命名空间:直接使用服务名,例如
pg-database-rw、pg-database-ro。 - 跨命名空间:使用完整限定名(FQDN 短形式)
service-name.namespace-name,例如pg-database-rw.apps。
更进一步,Kubernetes 会为每个 Service 生成形如pg-database-rw.default.svc.cluster.local的完整 DNS 名(cluster.local为默认集群域)。CloudNativePG 在生成凭据时使用的正是此类 FQDN,细节见下文 Secrets 章节。
在 Deployment 中通过 DNS 使用服务的示例
apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: containers: - name: app image: my-app:latest env: # 同命名空间,直接使用服务名 - name: PGHOST value: pg-database-rw - name: PGPORT value: "5432"服务发现方式二:环境变量(同命名空间)
如果应用与 PostgreSQL 集群部署在同一个命名空间,Kubernetes 会自动为同命名空间内每个 Service 注入一组<SERVICE_NAME>相关的环境变量到应用 Pod 中。假设集群名为pg-database,应用 Pod 中可直接使用:
| 环境变量 | 指向的服务 | 说明 |
|---|---|---|
PG_DATABASE_R_SERVICE_HOST | pg-database-r | 指向集群全部 PostgreSQL 实例(含主实例)的只读服务 IP |
PG_DATABASE_RO_SERVICE_HOST | pg-database-ro | 指向全部热备副本的只读服务 IP |
PG_DATABASE_RW_SERVICE_HOST | pg-database-rw | 指向主实例的读写服务 IP |
这些变量名由 Kubernetes 依据"服务名中的连字符替换为下划线 +_SERVICE_HOST后缀"的规则自动生成(pg-database-rw→PG_DATABASE_RW_SERVICE_HOST)。应用启动时读取这些变量即可获得对应服务的 ClusterIP,无需硬编码地址。注意:环境变量方式仅在同命名空间生效,跨命名空间时请优先使用 DNS 解析。
凭据管理:Operator 自动生成的 basic-auth Secrets
对于每个部署的 PostgreSQL 集群,CloudNativePG 会生成最多两个kubernetes.io/basic-auth类型的 Secret,用于携带连接凭据(源码见 pkg/specs/secrets.go 的CreateSecret函数,Secret 类型为corev1.SecretTypeBasicAuth):
<cluster-name>-app(如pg-database-app):应用连接使用的凭据,对应用户是数据库的属主(owner)。除非通过.spec.bootstrap.initdb.secret.name指定了已存在的 Secret,否则 Operator 一定会生成。<cluster-name>-superuser(如pg-database-superuser):仅用于管理目的的超级用户凭据,对应postgres用户。仅在.spec.enableSuperuserAccess为true且未通过.spec.superuserSecret指定其他 Secret 时生成。
重要:默认情况下,通过网络禁用超级用户访问(
.spec.enableSuperuserAccess默认关闭)。应用应一律使用-app凭据,-superuser凭据仅限管理员在需要时启用。
Secret 的字段清单
每个生成的 Secret 都包含以下字段(对应 pkg/specs/secrets.go 中的StringData):
| 字段名 | 内容 | 说明 |
|---|---|---|
username/user | 数据库用户名 | 两个字段值相同,兼容不同客户端读取习惯 |
password | 数据库密码 | |
dbname | 数据库名称 | 通常与集群名一致的默认数据库 |
host | 主机名 | 指向rw服务的短主机名(不带端口) |
port | 端口号 | 即postgres.ServerPort(5432) |
pgpass | .pgpass文件格式内容 | 可直接写入~/.pgpass供 libpq 工具链使用 |
uri | PostgreSQL 连接 URI | postgresql://user:pass@host:port/dbname形式,主机为短名 |
jdbc-uri | JDBC 连接 URI | 供 Java/JVM 生态的 JDBC 驱动使用 |
fqdn-uri | 基于 FQDN 的 PostgreSQL URI | 主机为完整域名 |
fqdn-jdbc-uri | 基于 FQDN 的 JDBC URI | 主机为完整域名 |
其中uri/jdbc-uri使用命名空间限定的主机名(host.namespace:port),而fqdn-uri/fqdn-jdbc-uri使用完整域名(host.namespace.svc.<cluster-domain>:port)。
FQDN 与集群域配置
Secret 中 FQDN 形式 URI 所使用的主机名,依据 Kubernetes 集群域计算而来:<service>.<namespace>.svc.<kubernetes-cluster-domain>。集群域通过 Operator 配置参数KUBERNETES_CLUSTER_DOMAIN指定(环境变量同名),其默认值为cluster.local,定义于 internal/configuration/configuration.go 的DefaultKubernetesClusterDomain。非默认集群域(如k8s.example.com)的集群需调整该参数,否则fqdn-*系列连接串无法解析。更多细节见 Operator 配置文档。
在应用中挂载 Secret 的示例
apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: containers: - name: app image: my-app:latest env: - name: DB_USER valueFrom: secretKeyRef: name: pg-database-app key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: pg-database-app key: password - name: DB_NAME valueFrom: secretKeyRef: name: pg-database-app key: dbname # 也可直接使用完整连接串 - name: DATABASE_URL valueFrom: secretKeyRef: name: pg-database-app key: uri对于 Python(psycopg2/SQLAlchemy)、Go(pgx/lib/pq)、Node.js(pg)等支持 libpq 连接串的驱动,直接使用uri字段即可;Java 应用则使用jdbc-uri;需要走.pgpass的 CLI 工具链(psql、pg_dump等)可使用pgpass字段内容。
高级话题:服务管理、连接池与外部访问
通过managed.services自定义服务
CloudNativePG 在 服务管理文档 中提供了对服务的高级定制能力:
- 禁用默认服务:通过
managed.services.disabledDefaultServices可关闭ro与r服务;但rw服务因承载复制关键路径而不可禁用。 - 新增自定义服务:通过
managed.services.additional段,以serviceTemplate定义任意 KubernetesService(如LoadBalancer类型对外暴露),通过selectorType(rw/ro/r)指定其选中的实例角色,updateStrategy控制更新策略(默认patch直接修改,replace则删除重建但会引发短暂中断)。
例如为数据库主实例创建一个对外LoadBalancer服务:
# <snip> managed: services: additional: - selectorType: rw serviceTemplate: metadata: name: "mydb-lb" labels: test-label: "true" annotations: test-annotation: "true" spec: type: LoadBalancer上述能力的实现位于 pkg/specs/services.go 的BuildManagedServices与buildDefaultService函数:Operator 依据selectorType构造对应的默认服务,再以用户的serviceTemplate覆盖元数据与 spec,并强制写入由 Operator 管理的 Selector 与端口。
连接池:PgBouncer 接入层
对于高并发应用,CloudNativePG 支持通过 PgBouncer 构建连接池,作为应用与 PostgreSQL 集群之间的访问层,以降低连接开销并提升吞吐。详细的部署与配置方法参见 Connection Pooling 章节。
将服务暴露到集群外的安全须知
CloudNativePG 服务默认是集群内可见的ClusterIP。如需对外暴露(如 DBaaS 场景、遗留虚拟机应用迁移),需自行创建LoadBalancer或NodePort类型的服务。需要强调的是,将数据库暴露到公网会使其面临恶意攻击风险,请务必在对外暴露前完成数据库加固,或确保 Kubernetes 集群仅从私有网络可达。
小结:推荐的连接组合
综合上述内容,一个遵循 CloudNativePG 最佳实践的应用连接方案是:
- 发现方式:优先使用服务名进行 DNS 解析(同命名空间直接用
<cluster>-rw,跨命名空间用<cluster>-rw.<namespace>),让 Kubernetes 负责实例切换时的流量重定向; - 凭据来源:从
<cluster>-appSecret 读取username、password、dbname、host/port或现成的uri/jdbc-uri/pgpass字段,杜绝硬编码密码; - 读写分离:按需在
rw、ro、r三类服务中选择合适的端点; - 高并发场景:引入 PgBouncer 连接池(连接池文档);
- 对外暴露:通过
managed.services.additional自定义服务,并做好安全加固。
这样组合既保证了连接的稳定与高可用,也让应用代码天然适配主备切换、滚动升级等运维动作,充分发挥 CloudNativePG 的自动化能力。可进一步阅读 服务管理完整文档 与 Cluster CRD 参考 获取全部配置项。
【免费下载链接】cloudnative-pgThe most popular Kubernetes Operator for PostgreSQL.项目地址: https://gitcode.com/GitHub_Trending/cl/cloudnative-pg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考