2. Kubernetes核心对象与PostgreSQL工作负载
在Kubernetes上运行PostgreSQL,第一步不是写YAML,而是先建立一张“对象地图”:哪些对象负责调度、哪些负责稳定身份、哪些负责网络入口、哪些负责配置注入。只有把对象职责分清,才能理解为什么PostgreSQL这类有状态数据库不能简单套用无状态应用的部署方式。
Kubernetes的最小调度单元是Pod,它封装一个或多个容器、共享网络命名空间与存储卷。Pod本身是“易失”的:被删除后重建,名字和IP都会变化。对于无状态Web服务,这没有问题;但PostgreSQL的数据目录、主从复制身份、客户端连接地址都依赖稳定标识,因此需要更高层的控制器来管理Pod。
Deployment管理无状态Pod副本,支持滚动更新与回滚,但它不保证Pod名称和网络标识的稳定性。它适合前端、API网关等场景,不适合直接承载数据库主节点。StatefulSet则专为有状态负载设计:它为每个副本分配固定序号(如postgres-0、postgres-1),配合Headless Service提供稳定的DNS名称,并通过volumeClaimTemplates为每个Pod绑定独立的PersistentVolumeClaim(PVC)。这意味着即使Pod被重新调度,它仍会挂载原来的数据卷,并保留原有网络身份。
Service解决“如何访问Pod”的问题。普通ClusterIP Service为一组Pod提供虚拟IP和负载均衡;Headless Service(clusterIP: None)不分配虚拟IP,而是直接返回Pod的DNS记录,常用于StatefulSet的稳定网络标识。对于PostgreSQL,通常需要两个Service:一个Headless Service用于主从复制和内部发现,一个普通Service用于客户端读写入口。
ConfigMap与Secret负责配置与敏感信息注入。ConfigMap存放非敏感配置,如postgresql.conf片段、时区、日志级别;Secret存放密码、复制凭据、TLS证书等。两者都可以通过环境变量或卷挂载进入Pod。对于PostgreSQL,推荐将配置文件以卷方式挂载到/etc/postgresql,密码通过Secret注入环境变量,避免硬编码在镜像中。
上图展示了PostgreSQL StatefulSet部署中核心对象的协作关系。StatefulSet创建并管理三个Pod,每个Pod拥有独立的PVC和稳定的DNS名称。Headless Service为Pod提供postgres-0.postgres-headless.default.svc.cluster.local这类可预测的域名,主从复制通过该域名建立连接。ClusterIP Service为客户端提供统一读写入口,ConfigMap和Secret分别注入配置与密码。
下面给出一个可参考的PostgreSQL StatefulSet部署示例。该示例使用官方postgres:16镜像,配置1主2从的流复制拓扑,存储类使用standard(示例假设集群已存在该StorageClass)。
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
POSTGRES_PASSWORD: "PgDemo#2024"
REPLICATION_PASSWORD: "ReplDemo#2024"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
postgresql.conf: |
listen_addresses = '*'
max_connections = 200
shared_buffers = 256MB
wal_level = replica
max_wal_senders = 5
hot_standby = on
---
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
name: postgres
---
apiVersion: v1
kind: Service
metadata:
name: postgres-client
spec:
selector:
app: postgres
role: primary
ports:
- port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-headless
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: config
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
volumes:
- name: config
configMap:
name: postgres-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi
应用该清单后,可以通过以下命令观察部署状态与Pod身份:
kubectl get statefulset postgres
kubectl get pods -l app=postgres -o wide
kubectl get pvc -l app=postgres
kubectl exec postgres-0 -- pg_isready -U postgres
预期结果:postgres-0、postgres-1、postgres-2依次创建,每个Pod绑定一个独立的PVC,名称分别为data-postgres-0、data-postgres-1、data-postgres-2。通过kubectl exec postgres-0 -- psql -U postgres -c "SELECT pg_is_in_recovery();"应返回f(表示主节点),在postgres-1上执行同样命令应返回t(表示备节点)。
<statefulset-name>-<ordinal>,序号从0开始。删除Pod后重建,名称和PVC绑定关系保持不变,这是与Deployment最本质的区别。storageClassName需根据实际集群的StorageClass名称调整,否则PVC会一直处于Pending状态。