本指南介绍如何使用 Zalando Postgres 运算符将 Postgres 集群部署到 Google Kubernetes Engine (GKE)。
PostgreSQL 是一种功能强大的开源对象关系型数据库系统,已持续数十年积极开发,使其在可靠性、功能稳健性和性能方面享有盛誉。
本指南适用于有兴趣将 PostgreSQL 作为数据库应用在 GKE 上运行(而不是使用 Cloud SQL for PostgreSQL)的平台管理员、云架构师和运维专业人员。
目标
- 为 Postgres 规划和部署 GKE 基础架构
- 部署和配置 Zalando Postgres 运算符
- 使用运算符配置 Postgres,以确保可用性、安全性、可观测性和性能
优势
Zalando 具有以下优势:
- 用于管理和配置 PostgreSQL 集群的声明式 Kubernetes 原生方法
- Patroni 提供的高可用性
- 使用 Cloud Storage 存储桶的备份管理支持
- 对 Postgres 集群更改的滚动更新,包括快速次要版本更新
- 使用自定义资源进行密码生成和轮替的声明式用户管理
- 对 TLS、证书轮替和连接池的支持
- 集群克隆和数据复制
部署架构
在本教程中,您将使用 Zalando Postgres 运算符将一个高可用性 Postgres 集群部署和配置到 GKE。该集群具有一个主副本和两个备用(只读)副本(由 Patroni 管理)。Patroni 是由 Zalando 维护的开源解决方案,可为 Postgres 提供高可用性和自动故障切换功能。如果主副本发生故障,则一个备用副本会自动升级为主角色。
您还将部署 Postgres 的高可用性区域级 GKE 集群,其中多个 >Kubernetes 节点分布在多个可用区中。此设置有助于确保容错、可伸缩性和地理冗余。这样可支持滚动更新和维护,同时提供 SLA 以保证正常运行时间和可用性。如需了解详情,请参阅区域级集群。
下图显示了在 GKE 集群中的多个节点和可用区上运行的 Postgres 集群:
在该图中,Postgres StatefulSet
部署在三个不同可用区的三个节点上。您可以通过以下方式控制 GKE 如何部署到节点:在 postgresql
自定义资源规范中设置所需的 Pod 亲和性和反亲和性规则。如果一个可用区发生故障,GKE 将使用推荐的配置在集群中的其他可用节点上重新调度 Pod。为了持久保留数据,您可使用 SSD 磁盘 (premium-rwo
StorageClass),由于这类磁盘具备低延迟,高 IOPS 特征,因而在大多数情况下推荐用于高负载数据库。
费用
在本文档中,您将使用 Google Cloud 的以下收费组件:
您可使用价格计算器根据您的预计使用情况来估算费用。
完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理。
准备工作
Cloud Shell 预安装了本教程所需的软件,包括 kubectl
、gcloud CLI、Helm 和 Terraform。如果您不使用 Cloud Shell,则必须安装 gcloud CLI。
- Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
- Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
-
Create or select a Google Cloud project.
-
Create a Google Cloud project:
gcloud projects create PROJECT_ID
Replace
PROJECT_ID
with a name for the Google Cloud project you are creating. -
Select the Google Cloud project that you created:
gcloud config set project PROJECT_ID
Replace
PROJECT_ID
with your Google Cloud project name.
-
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine, IAM, GKE, Backup for GKE APIs:
gcloud services enable compute.googleapis.com
iam.googleapis.com container.googleapis.com gkebackup.googleapis.com - Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
-
Create or select a Google Cloud project.
-
Create a Google Cloud project:
gcloud projects create PROJECT_ID
Replace
PROJECT_ID
with a name for the Google Cloud project you are creating. -
Select the Google Cloud project that you created:
gcloud config set project PROJECT_ID
Replace
PROJECT_ID
with your Google Cloud project name.
-
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Compute Engine, IAM, GKE, Backup for GKE APIs:
gcloud services enable compute.googleapis.com
iam.googleapis.com container.googleapis.com gkebackup.googleapis.com -
Grant roles to your user account. Run the following command once for each of the following IAM roles:
roles/storage.objectViewer, roles/container.admin, roles/iam.serviceAccountAdmin, roles/compute.admin, roles/gkebackup.admin, roles/monitoring.viewer
gcloud projects add-iam-policy-binding PROJECT_ID --member="USER_IDENTIFIER" --role=ROLE
- Replace
PROJECT_ID
with your project ID. -
Replace
USER_IDENTIFIER
with the identifier for your user account. For example,user:myemail@example.com
. - Replace
ROLE
with each individual role.
- Replace
设置环境
如需设置您的环境,请按以下步骤操作
设置环境变量:
export PROJECT_ID=PROJECT_ID export KUBERNETES_CLUSTER_PREFIX=postgres export REGION=us-central1
将
PROJECT_ID
替换为您的 Google Cloud 项目 ID。克隆 GitHub 代码库:
git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
切换到工作目录:
cd kubernetes-engine-samples/databases/postgres-zalando
创建集群基础架构
在本部分中,您将运行 Terraform 脚本以创建高可用性专用区域级 GKE 集群。
您可以使用 Standard 或 Autopilot 集群安装操作器。
标准
下图显示了部署在三个不同可用区中的专用区域级 Standard GKE 集群:
部署此基础设施:
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-standard init
terraform -chdir=terraform/gke-standard apply \
-var project_id=${PROJECT_ID} \
-var region=${REGION} \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}
出现提示时,请输入 yes
。完成此命令并使集群显示就绪状态可能需要几分钟时间。
Terraform 会创建以下资源:
- Kubernetes 节点的 VPC 网络和专用子网
- 用于通过 NAT 访问互联网的路由器
- 专用 GKE 集群(在
us-central1
区域中) - 启用了自动扩缩的节点池(每个可用区一个到两个节点,每个可用区最少一个节点)
- 具有日志记录和监控权限的
ServiceAccount
- 用于灾难恢复的 Backup for GKE
- Google Cloud Managed Service for Prometheus,用于监控集群
输出类似于以下内容:
...
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
...
Autopilot
下图显示了专用区域级 Autopilot GKE 集群:
部署基础架构:
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-autopilot init
terraform -chdir=terraform/gke-autopilot apply \
-var project_id=${PROJECT_ID} \
-var region=${REGION} \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}
出现提示时,请输入 yes
。完成此命令并使集群显示就绪状态可能需要几分钟时间。
Terraform 会创建以下资源:
- Kubernetes 节点的 VPC 网络和专用子网
- 用于通过 NAT 访问互联网的路由器
- 专用 GKE 集群(在
us-central1
区域中) - 具有日志记录和监控权限的
ServiceAccount
- Google Cloud Managed Service for Prometheus,用于监控集群
输出类似于以下内容:
...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
...
连接到集群
配置 kubectl
以与集群通信:
gcloud container clusters get-credentials ${KUBERNETES_CLUSTER_PREFIX}-cluster --region ${REGION}
将 Zalando 运算符部署到您的集群
使用 Helm 图表将 Zalando 运算符部署到您的 Kubernetes 集群。
添加 Zalando 运算符 Helm 图表代码库:
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
为 Zalando 运算符和 Postgres 集群创建命名空间:
kubectl create ns postgres kubectl create ns zalando
使用 Helm 命令行工具部署 Zalando 运算符:
helm install postgres-operator postgres-operator-charts/postgres-operator -n zalando \ --set configKubernetes.enable_pod_antiaffinity=true \ --set configKubernetes.pod_antiaffinity_preferred_during_scheduling=true \ --set configKubernetes.pod_antiaffinity_topology_key="topology.kubernetes.io/zone" \ --set configKubernetes.spilo_fsgroup="103"
您无法直接在表示 Postgres 集群的自定义资源上配置
podAntiAffinity
设置。请改为在运算符设置中以全局方式为所有 Postgres 集群设置podAntiAffinity
设置。使用 Helm 检查 Zalando 运算符的部署状态:
helm ls -n zalando
输出类似于以下内容:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION postgres-operator zalando 1 2023-10-13 16:04:13.945614 +0200 CEST deployed postgres-operator-1.10.1 1.10.1
部署 Postgres
Postgres 集群实例的基本配置包括以下组件:
- 三个 Postgres 副本:一个主副本和两个备用副本。
- 一个 CPU 请求和两个 CPU 限制的 CPU 资源分配,具有 4 GB 内存请求和限制。
- 为每个工作负载配置容忍设置、
nodeAffinities
和topologySpreadConstraints
,利用它们各自的节点池和不同的可用区来确保跨 Kubernetes 节点进行适当分布。
此配置表示创建可直接用于生产环境的 Postgres 集群所需的最少设置。
以下清单描述了一个 Postgres 集群:
此清单包含以下字段:
spec.teamId
:您选择的集群对象的前缀spec.numberOfInstances
:集群的实例总数spec.users
:具有权限的用户列表spec.databases
:数据库列表,格式为dbname: ownername
spec.postgresql
:postgres 参数spec.volume
:永久性磁盘参数spec.tolerations
:容忍 Pod 模板,允许在pool-postgres
节点上调度集群 Podspec.nodeAffinity
:nodeAffinity
Pod 模板,向 GKE 告知首选在pool-postgres
节点上调度集群 Pod。spec.resources
:集群 Pod 的请求和限制spec.sidecars
:Sidecar 容器列表,其中包含postgres-exporter
如需了解详情,请参阅 Postgres 文档中的集群清单参考文档。
创建基本 Postgres 集群
使用基本配置创建新的 Postgres 集群:
kubectl apply -n postgres -f manifests/01-basic-cluster/my-cluster.yaml
此命令会创建 Zalando 运算符的 PostgreSQL 自定义资源,其中包含以下内容:
- CPU 和内存请求和限制
- 在 GKE 节点之间分发预配的 Pod 副本的污点及亲和性。
- 一个数据库
- 两个具有数据库所有者权限的用户
- 一个没有任何权限的用户
等待 GKE 启动所需工作负载:
kubectl wait pods -l cluster-name=my-cluster --for condition=Ready --timeout=300s -n postgres
此命令可能需要几分钟时间才能完成。
验证 GKE 是否已创建 Postgres 工作负载:
kubectl get pod,svc,statefulset,deploy,pdb,secret -n postgres
输出类似于以下内容:
NAME READY STATUS RESTARTS AGE pod/my-cluster-0 1/1 Running 0 6m41s pod/my-cluster-1 1/1 Running 0 5m56s pod/my-cluster-2 1/1 Running 0 5m16s pod/postgres-operator-db9667d4d-rgcs8 1/1 Running 0 12m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-cluster ClusterIP 10.52.12.109 <none> 5432/TCP 6m43s service/my-cluster-config ClusterIP None <none> <none> 5m55s service/my-cluster-repl ClusterIP 10.52.6.152 <none> 5432/TCP 6m43s service/postgres-operator ClusterIP 10.52.8.176 <none> 8080/TCP 12m NAME READY AGE statefulset.apps/my-cluster 3/3 6m43s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/postgres-operator 1/1 1 1 12m NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE poddisruptionbudget.policy/postgres-my-cluster-pdb 1 N/A 0 6m44s NAME TYPE DATA AGE secret/my-user.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m45s secret/postgres.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s secret/sh.helm.release.v1.postgres-operator.v1 helm.sh/release.v1 1 12m secret/standby.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s secret/zalando.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s
操作器创建以下资源:
- 一个 Postgres StatefulSet,控制 Postgres 的三个 Pod 副本
- 一个
PodDisruptionBudgets
,确保至少有一个可用副本 my-cluster
Service,仅以主副本为目标my-cluster-repl
Service,公开 Postgres 端口以用于传入连接以及 Postgres 副本之间的复制my-cluster-config
无头 Service,用于获取正在运行的 Postgres Pod 副本的列表- 包含用户凭据的 Secret,用于访问数据库以及在 Postgres 节点之间进行复制
向 Postgres 进行身份验证
您可以创建 Postgres 用户并为其分配数据库权限。例如,以下清单描述了一个分配用户和角色的自定义资源:
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: my-cluster
spec:
...
users:
mydatabaseowner:
- superuser
- createdb
myuser: []
databases:
mydatabase: mydatabaseowner
在此清单中:
mydatabaseowner
用户具有SUPERUSER
和CREATEDB
角色,这些角色允许获得完整管理员权限(例如管理 Postgres 配置、创建新数据库、表和用户)。您不应与客户端共享此用户。例如,Cloud SQL 不允许客户访问具有SUPERUSER
角色的用户。myuser
用户尚未分配角色。这遵循了使用SUPERUSER
创建具有最小权限的用户的最佳实践。精细权限由mydatabaseowner
授予给myuser
。为了维护安全性,您应该只与客户端应用共享myuser
凭据。
存储密码
您应该使用 scram-sha-256
推荐方法存储密码。例如,以下清单描述了一个使用 postgresql.parameters.password_encryption
字段指定 scram-sha-256
加密的自定义资源:
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: my-cluster
spec:
...
postgresql:
parameters:
password_encryption: scram-sha-256
轮替用户凭据
您可以使用 Zalando 轮替存储在 Kubernetes Secret 中的用户凭据。例如,以下清单描述了一个使用 usersWithSecretRotation
字段定义用户凭据变换的自定义资源:
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: my-cluster
spec:
...
usersWithSecretRotation:
- myuser
- myanotheruser
- ...
身份验证示例:连接到 Postgres
本部分介绍如何部署示例 Postgres 客户端,并使用来自 Kubernetes Secret 的密码连接到数据库。
运行客户端 Pod 以与您的 Postgres 集群进行交互:
kubectl apply -n postgres -f manifests/02-auth/client-pod.yaml
myuser
和mydatabaseowner
用户的凭据从相关 Secret 中获取,并作为环境变量装载到 Pod。在 Pod 准备就绪后连接到 Pod:
kubectl wait pod postgres-client --for=condition=Ready --timeout=300s -n postgres kubectl exec -it postgres-client -n postgres -- /bin/bash
使用
myuser
凭据连接到 Postgres 并尝试创建新表:PGPASSWORD=$CLIENTPASSWORD psql \ -h my-cluster \ -U $CLIENTUSERNAME \ -d mydatabase \ -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);"
该命令应该失败,并显示类似于以下内容的错误:
ERROR: permission denied for schema public LINE 1: CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR...
该命令失败是因为默认情况下,未分配权限的用户只能登录 Postgres 和列出数据库。
使用
mydatabaseowner
凭据创建表,并将表的所有权限都授予给myuser
:PGPASSWORD=$OWNERPASSWORD psql \ -h my-cluster \ -U $OWNERUSERNAME \ -d mydatabase \ -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);GRANT ALL ON test TO myuser;GRANT ALL ON SEQUENCE test_id_seq TO myuser;"
输出类似于以下内容:
CREATE TABLE GRANT GRANT
使用
myuser
凭据将随机数据插入表中:for i in {1..10}; do DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13) PGPASSWORD=$CLIENTPASSWORD psql \ -h my-cluster \ -U $CLIENTUSERNAME \ -d mydatabase \ -c "INSERT INTO test(randomdata) VALUES ('$DATA');" done
输出类似于以下内容:
INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1 INSERT 0 1
获取插入的值:
PGPASSWORD=$CLIENTPASSWORD psql \ -h my-cluster \ -U $CLIENTUSERNAME \ -d mydatabase \ -c "SELECT * FROM test;"
输出类似于以下内容:
id | randomdata ----+--------------- 1 | jup9HYsAjwtW4 2 | 9rLAyBlcpLgNT 3 | wcXSqxb5Yz75g 4 | KoDRSrx3muD6T 5 | b9atC7RPai7En 6 | 20d7kC8E6Vt1V 7 | GmgNxaWbkevGq 8 | BkTwFWH6hWC7r 9 | nkLXHclkaqkqy 10 | HEebZ9Lp71Nm3 (10 rows)
退出 Pod shell:
exit
了解 Prometheus 如何收集 Postgres 集群的指标
下图展示了 Prometheus 指标收集的工作原理:
在该图中,GKE 专用集群包含:
- Postgres Pod,用于通过路径
/
和端口9187
收集指标 - 基于 Prometheus 的收集器,用于处理 Postgres Pod 中的指标
- 将指标发送到 Cloud Monitoring 的
PodMonitoring
资源
Google Cloud Managed Service for Prometheus 支持 Prometheus 格式的指标收集。Cloud Monitoring 使用集成式信息中心来处理 Postgres 指标。
Zalando 使用 postgres_exporter 组件作为 Sidecar 容器,采用 Prometheus 格式公开集群指标。
创建
PodMonitoring
资源,以按labelSelector
爬取指标:kubectl apply -n postgres -f manifests/03-prometheus-metrics/pod-monitoring.yaml
在 Google Cloud 控制台中,转到 GKE 集群信息中心页面。
信息中心显示非零指标提取率。
在 Google Cloud 控制台中,转到信息中心页面。
打开 PostgreSQL Prometheus 概览信息中心。信息中心会显示提取的行数。信息中心可能需要几分钟时间才能完成自动预配。
连接到客户端 Pod:
kubectl exec -it postgres-client -n postgres -- /bin/bash
插入随机数据:
for i in {1..100}; do DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13) PGPASSWORD=$CLIENTPASSWORD psql \ -h my-cluster \ -U $CLIENTUSERNAME \ -d mydatabase \ -c "INSERT INTO test(randomdata) VALUES ('$DATA');" done
刷新页面。 行和块图表会更新,以显示实际数据库状态。
退出 Pod shell:
exit
清理
删除项目
Delete a Google Cloud project:
gcloud projects delete PROJECT_ID
删除各个资源
设置环境变量。
export PROJECT_ID=${PROJECT_ID} export KUBERNETES_CLUSTER_PREFIX=postgres export REGION=us-central1
运行
terraform destroy
命令:export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token) terraform -chdir=terraform/FOLDER destroy \ -var project_id=${PROJECT_ID} \ -var region=${REGION} \ -var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}
将
FOLDER
替换为gke-autopilot
或gke-standard
。出现提示时,请输入
yes
。查找所有未挂接的磁盘:
export disk_list=$(gcloud compute disks list --filter="-users:* AND labels.name=${KUBERNETES_CLUSTER_PREFIX}-cluster" --format "value[separator=|](name,zone)")
删除磁盘:
for i in $disk_list; do disk_name=$(echo $i| cut -d'|' -f1) disk_zone=$(echo $i| cut -d'|' -f2|sed 's|.*/||') echo "Deleting $disk_name" gcloud compute disks delete $disk_name --zone $disk_zone --quiet done
删除 GitHub 代码库:
rm -r ~/kubernetes-engine-samples/
后续步骤
- 探索有关 Google Cloud 的参考架构、图表和最佳做法。查看我们的 Cloud 架构中心。