使用 CloudNativePG 将 PostgreSQL 部署到 GKE


本指南介绍了如何使用 CloudNativePG 操作器在 Google Kubernetes Engine (GKE) 上部署 PostgreSQL 集群。

PostgreSQL 是一种开源对象关系型数据库,经过了数十年的积极开发,可确保稳定的客户端性能。它提供一系列功能,包括复制、时间点恢复、安全功能和扩展性。PostgreSQL 与主要操作系统兼容,并完全符合 ACID(原子性、一致性、隔离性、耐用性)标准。

本指南适用于有意在 GKE 上部署 Postgres 集群的平台管理员、云架构师和运营专家。在 GKE 中运行 Postgres(而不是使用 Cloud SQL)可以为经验丰富的数据库管理员提供更多灵活性和配置控制。

优势

CloudNativePG 是由 EBD 根据 Apache 2 许可开发的开源操作器。它为 PostgreSQL 部署引入了以下功能:

  • 用于管理和配置 PostgreSQL 集群的声明式 Kubernetes 原生方法
  • 使用卷快照Cloud Storage 进行备份管理
  • 在传输过程中加密的 TLS 连接,能够使用自己的证书授权机构以及与 Certificate Manager 的集成来自动颁发和轮替 TLS 证书
  • 次要 PostgreSQL 版本的滚动更新
  • 使用 Kubernetes API 服务器维护 PostgreSQL 集群状态和故障切换以实现高可用性,无需其他工具
  • 通过以 SQL 编写的用户定义的指标实现内置 Prometheus 导出器配置

目标

  • 为 Postgres 规划和部署 GKE 基础架构
  • 使用 Helm 部署和配置 CloudNativePG Postgres 操作器
  • 部署 PostgreSQL 集群
  • 配置 PostgreSQL 身份验证和可观测性

部署架构

PostgreSQL 具有各种部署选项,从独立数据库服务器到复制的高可用性集群不等。本教程重点介绍如何将高可用性集群部署到 GKE。

在此部署中,PostgreSQL 集群工作负载分布在区域 GKE 集群中的多个可用区中,确保高可用性和冗余。如需了解详情,请参阅区域级集群

下图显示了在 GKE 集群中的多个节点和可用区上运行的 Postgres 集群:

GKE 上的 Postgres 集群

  • 默认设置包括一个主 PostgreSQL 服务器和两个备份服务器,如果主服务器发生故障,备份服务器可以进行接管,从而确保持续的数据库可用性。

  • CloudNativePG 操作器资源使用 GKE 集群的单独命名空间以实现更好的资源隔离,并采用推荐的每个 PostgreSQL 集群一个数据库的微服务方法。数据库及其对应的用户(应用用户)是在表示集群的 Kubernetes 自定义资源中定义的。

  • 在讨论数据库时,存储是关键组成部分。存储必须高效运行,确保连续可用性,并保证数据一致性。出于这些原因,我们建议使用基于 SSD 磁盘的 premium-rwo 存储类别。在为 PostgreSQL 集群设置 Pod 时,CloudNativePG 操作器会根据需要自动创建 PersistentVolumeClaims

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

Cloud Shell 预安装了本教程所需的软件,包括 kubectlgcloud CLIHelmTerraform。如果您不使用 Cloud Shell,则必须安装 gcloud CLI。

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 安装 Google Cloud CLI。
  3. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  4. 创建或选择 Google Cloud 项目

    • 创建 Google Cloud 项目:

      gcloud projects create PROJECT_ID

      PROJECT_ID 替换为您要创建的 Google Cloud 项目的名称。

    • 选择您创建的 Google Cloud 项目:

      gcloud config set project PROJECT_ID

      PROJECT_ID 替换为您的 Google Cloud 项目 名称。

  5. 确保您的 Google Cloud 项目已启用结算功能

  6. Enable the Compute Engine, IAM, GKE, Resource Manager APIs:

    gcloud services enable compute.googleapis.com iam.googleapis.com container.googleapis.com cloudresourcemanager.googleapis.com
  7. 安装 Google Cloud CLI。
  8. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  9. 创建或选择 Google Cloud 项目

    • 创建 Google Cloud 项目:

      gcloud projects create PROJECT_ID

      PROJECT_ID 替换为您要创建的 Google Cloud 项目的名称。

    • 选择您创建的 Google Cloud 项目:

      gcloud config set project PROJECT_ID

      PROJECT_ID 替换为您的 Google Cloud 项目 名称。

  10. 确保您的 Google Cloud 项目已启用结算功能

  11. Enable the Compute Engine, IAM, GKE, Resource Manager APIs:

    gcloud services enable compute.googleapis.com iam.googleapis.com container.googleapis.com cloudresourcemanager.googleapis.com
  12. 向您的 Google 账号授予角色。对以下每个 IAM 角色运行以下命令一次: roles/compute.securityAdmin, roles/compute.viewer, roles/container.clusterAdmin, roles/container.admin, roles/iam.serviceAccountAdmin, roles/iam.serviceAccountUser

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:EMAIL_ADDRESS" --role=ROLE
    • PROJECT_ID 替换为您的项目 ID。
    • EMAIL_ADDRESS 替换为您的电子邮件地址。
    • ROLE 替换为每个角色。

设置您的环境

如需设置您的环境,请按以下步骤操作:

  1. 设置环境变量:

    export PROJECT_ID=PROJECT_ID
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    

    PROJECT_ID 替换为您的 Google Cloud 项目 ID

  2. 克隆 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. 切换到工作目录:

    cd kubernetes-engine-samples/databases/postgresql-cloudnativepg
    

创建集群基础架构

在本部分中,您将运行 Terraform 脚本以创建高可用性专用区域级 GKE 集群。

您可以使用 Standard 或 Autopilot 集群安装操作器。

Standard

下图显示了部署在三个不同可用区中的专用区域级 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 区域中)
  • 启用了自动扩缩的节点池(每个可用区一个到两个节点,每个可用区最少一个节点)

输出类似于以下内容:

...
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}

部署 CloudNativePG 运算符

使用 Helm 图表将 CloudNativePG 部署到 Kubernetes 集群:

  1. 添加 CloudNativePG 运算符 Helm 图表代码库:

    helm repo add cnpg https://cloudnative-pg.github.io/charts
    
  2. 使用 Helm 命令行工具部署 CloudNativePG 运算符:

    helm upgrade --install cnpg \
        --namespace cnpg-system \
        --create-namespace \
        cnpg/cloudnative-pg
    

    输出类似于以下内容:

    Release "cnpg" does not exist. Installing it now.
    NAME: cnpg
    LAST DEPLOYED: Fri Oct 13 13:52:36 2023
    NAMESPACE: cnpg-system
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    ...
    

部署 Postgres

以下清单描述了一个由 CloudNativePG 操作器的自定义资源定义的 PostgreSQL 集群:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: gke-pg-cluster
spec:
  description: "Standard GKE PostgreSQL cluster"
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  enableSuperuserAccess: true
  instances: 3
  startDelay: 300
  primaryUpdateStrategy: unsupervised
  postgresql:
    pg_hba:
      - host all all 10.48.0.0/20 md5
  bootstrap:
    initdb:
      database: app
  storage:
    storageClass: premium-rwo
    size: 2Gi
  resources:
    requests:
      memory: "1Gi"
      cpu: "1000m"
    limits:
      memory: "1Gi"
      cpu: "1000m"
  affinity:
    enablePodAntiAffinity: true
    tolerations:
    - key: cnpg.io/cluster
      effect: NoSchedule
      value: gke-pg-cluster
      operator: Equal
    additionalPodAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app.component
              operator: In
              values:
              - "pg-cluster"
          topologyKey: topology.kubernetes.io/zone
  monitoring:
    enablePodMonitor: true

此清单包含以下字段:

  • spec.instances:集群 Pod 的数量
  • spec.primaryUpdateStrategy:滚动更新策略:
    • Unsupervised:在副本节点之后自主更新主集群节点
    • Supervised:主集群节点需要手动切换
  • spec.postgresqlpostgres.conf 文件参数替换值,例如 pg-hba 规则、LDAP 以及要满足的同步副本要求。
  • spec.storage:与存储相关的设置,例如存储类别、卷大小和预写式日志设置。
  • spec.bootstrap:在集群中创建的初始数据库的参数、用户凭据和数据库恢复选项
  • spec.resources:集群 Pod 的请求和限制
  • spec.affinity:集群工作负载的亲和性和反亲和性规则

创建基本 Postgres 集群

  1. 创建命名空间:

    kubectl create ns pg-ns
    
  2. 使用自定义资源创建 PostgreSQL 集群:

    kubectl apply -n pg-ns -f manifests/01-basic-cluster/postgreSQL_cluster.yaml
    

    此命令可能需要几分钟才能完成。

  3. 检查集群状态:

    kubectl get cluster -n pg-ns --watch
    

    等待输出显示 Cluster in healthy state 状态,然后再转到下一步。

    NAME             AGE     INSTANCES   READY   STATUS                     PRIMARY
    gke-pg-cluster   2m53s   3           3       Cluster in healthy state   gke-pg-cluster-1
    

检查资源

确认 GKE 为集群创建了资源:

kubectl get cluster,pod,svc,pvc,pdb,secret,cm -n pg-ns

输出类似于以下内容:

NAME                                        AGE   INSTANCES   READY   STATUS                     PRIMARY
cluster.postgresql.cnpg.io/gke-pg-cluster   32m   3           3       Cluster in healthy state   gke-pg-cluster-1

NAME                   READY   STATUS    RESTARTS   AGE
pod/gke-pg-cluster-1   1/1     Running   0          31m
pod/gke-pg-cluster-2   1/1     Running   0          30m
pod/gke-pg-cluster-3   1/1     Running   0          29m

NAME                        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/gke-pg-cluster-r    ClusterIP   10.52.11.24   <none>        5432/TCP   32m
service/gke-pg-cluster-ro   ClusterIP   10.52.9.233   <none>        5432/TCP   32m
service/gke-pg-cluster-rw   ClusterIP   10.52.1.135   <none>        5432/TCP   32m

NAME                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/gke-pg-cluster-1   Bound    pvc-bbdd1cdd-bdd9-4e7c-8f8c-1a14a87e5329   2Gi        RWO            standard       32m
persistentvolumeclaim/gke-pg-cluster-2   Bound    pvc-e7a8b4df-6a3e-43ce-beb0-b54ec1d24011   2Gi        RWO            standard       31m
persistentvolumeclaim/gke-pg-cluster-3   Bound    pvc-dac7f931-6ac5-425f-ac61-0cfc55aae72f   2Gi        RWO            standard       30m

NAME                                                MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/gke-pg-cluster           1               N/A               1                     32m
poddisruptionbudget.policy/gke-pg-cluster-primary   1               N/A               0                     32m

NAME                                TYPE                       DATA   AGE
secret/gke-pg-cluster-app           kubernetes.io/basic-auth   3      32m
secret/gke-pg-cluster-ca            Opaque                     2      32m
secret/gke-pg-cluster-replication   kubernetes.io/tls          2      32m
secret/gke-pg-cluster-server        kubernetes.io/tls          2      32m
secret/gke-pg-cluster-superuser     kubernetes.io/basic-auth   3      32m

NAME                                DATA   AGE
configmap/cnpg-default-monitoring   1      32m
configmap/kube-root-ca.crt          1      135m

操作器创建以下资源:

  • 表示由运算符控制的 PostgreSQL 集群的集群自定义资源
  • 具有相应永久性卷的 PersistentVolumeClaim 资源
  • 包含用户凭据的 Secret,用于访问数据库以及在 Postgres 节点之间进行复制。
  • 三个数据库端点服务:<name>-rw<name>-ro<name>-r,用于连接到集群。如需了解详情,请参阅 PostgreSQL 架构

向 Postgres 进行身份验证

您可以连接到 PostgreSQL 数据库,并通过运算符创建的不同服务端点检查访问权限。为此,请使用一个额外 Pod,其中包含一个 PostgreSQL 客户端并以环境变量的形式装载了同步应用用户凭据。

  1. 运行客户端 Pod 以与您的 Postgres 集群进行交互:

    kubectl apply -n pg-ns -f manifests/02-auth/pg-client.yaml
    
  2. pg-client Pod 上运行 exec 命令并登录 gke-pg-cluster-rw Service:

    kubectl wait --for=condition=Ready -n pg-ns pod/pg-client --timeout=300s
    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  3. 使用 gke-pg-cluster-rw Service 登录数据库以建立具有读写权限的连接:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app
    

    终端以您的数据库名称开头:

    app=>
    
  4. 创建表:

    CREATE TABLE travel_agency_clients (
    client VARCHAR ( 50 ) UNIQUE NOT NULL,
    address VARCHAR ( 50 ) UNIQUE NOT NULL,
    phone VARCHAR ( 50 ) UNIQUE NOT NULL);
    
  5. 将数据插入到表中:

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('Tom', 'Warsaw', '+55555')
    RETURNING *;
    
  6. 查看您创建的数据:

    SELECT * FROM travel_agency_clients ;
    

    输出类似于以下内容:

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  7. 退出当前数据库会话:

    exit
    
  8. 使用 gke-pg-cluster-ro Service 登录数据库以验证只读权限。此 Service 允许查询数据,但限制任何写入操作:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-ro.pg-ns/app
    
  9. 尝试插入新数据:

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('John', 'Paris', '+55555')
    RETURNING *;
    

    输出类似于以下内容:

    ERROR:  cannot execute INSERT in a read-only transaction
    
  10. 尝试读取数据:

    SELECT * FROM travel_agency_clients ;
    

    输出类似于以下内容:

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  11. 退出当前数据库会话:

    exit
    
  12. 退出 Pod shell:

    exit
    

了解 Prometheus 如何收集 Postgres 集群的指标

下图展示了 Prometheus 指标收集的工作原理:

在该图中,GKE 专用集群包含:

  • Postgres Pod,用于通过路径 / 和端口 9187 收集指标
  • 基于 Prometheus 的收集器,用于处理 Postgres Pod 中的指标
  • 将指标发送到 Cloud Monitoring 的 PodMonitoring 资源

如需启用从 Pod 收集指标的功能,请执行以下步骤:

  1. 创建 PodMonitoring 资源:

    kubectl apply -f manifests/03-observability/pod-monitoring.yaml -n pg-ns
    
  2. 在 Google Cloud 控制台中,转到 Metrics Explorer 页面:

    转到 Metrics Explorer

    信息中心显示非零指标提取率。

  3. 选择一个指标中,输入 Prometheus Target(Prometheus 目标)。

  4. 活跃指标类别部分中,选择 Cnpg

创建指标信息中心

如需直观呈现导出的指标,请创建指标信息中心。

  1. 部署信息中心:

    gcloud --project "${PROJECT_ID}" monitoring dashboards create --config-from-file manifests/03-observability/gcp-pg.json
    
  2. 在 Google Cloud 控制台中,转到信息中心页面。

    转到“信息中心”

  3. 选择 PostgresQL Prometheus Overview(PostgresQL Prometheus 概览)信息中心。

    如需查看信息中心如何监控函数,您可以重复使用数据库身份验证部分中的操作,对数据库应用读取和写入请求,然后在信息中心内查看收集的指标可视化效果。

  4. 连接到客户端 Pod:

    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  5. 插入随机数据:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);INSERT INTO test (randomdata) VALUES (generate_series(1, 1000));"
    
  6. 刷新信息中心。图表将使用已实现的指标进行更新。

  7. 退出 Pod shell:

    exit
    

清理

删除项目

    删除 Google Cloud 项目:

    gcloud projects delete PROJECT_ID

删除各个资源

  1. 设置环境变量。

    export PROJECT_ID=${PROJECT_ID}
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    
  2. 运行 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-autopilotgke-standard

    出现提示时,请输入 yes

  3. 查找所有未挂接的磁盘:

    export disk_list=$(gcloud compute disks list --filter="-users:* AND labels.name=${KUBERNETES_CLUSTER_PREFIX}-cluster" --format "value[separator=|](name,zone)")
    
  4. 删除磁盘:

    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
    

后续步骤