使用 CloudNativePG 将 PostgreSQL 部署到 GKE


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

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

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

优势

CloudNativePG 是由 EDB 根据 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. 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.
  2. Install the Google Cloud CLI.
  3. To initialize the gcloud CLI, run the following command:

    gcloud init
  4. 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.

  5. Make sure that billing is enabled for your Google Cloud project.

  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. Install the Google Cloud CLI.
  8. To initialize the gcloud CLI, run the following command:

    gcloud init
  9. 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.

  10. Make sure that billing is enabled for your Google Cloud project.

  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. Grant roles to your user account. Run the following command once for each of the following IAM roles: 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_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.

设置您的环境

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

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

清理

删除项目

    Delete a Google Cloud project:

    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
    

后续步骤

  • 探索有关 Google Cloud 的参考架构、图表和最佳做法。查看我们的 Cloud 架构中心