在 Azure Kubernetes 服务(AKS)上部署高度可用的 PostgreSQL 数据库

在本文中,你将在 AKS 上部署高度可用的 PostgreSQL 数据库。

重要

AKS 文档和示例中都提到了开源软件。 AKS 服务级别协议、有限保修和 Azure 支持不涵盖你部署的软件。 将开源技术与 AKS 一起使用时,请查阅相应社区和项目维护者提供的支持选项来制定计划。

例如,Ray GitHub 存储库描述了多个在响应时间、用途和支持级别方面各不相同的平台。

Microsoft 将负责生成我们在 AKS 上部署的开源包。 该责任包括对生成、扫描、签名、验证和修补过程拥有完整的所有权,以及对容器映像中的二进制文件的控制。 如需了解详细信息,请参阅 AKS 漏洞管理AKS 支持范围

为启动应用用户创建机密

  1. 使用 kubectl create secret 命令为启动应用用户生成一个机密,通过交互式登录来验证 PostgreSQL 部署。

重要

Microsoft建议使用可用的最安全的身份验证流。 此过程中所述的身份验证流要求在应用程序中高度信任,并具有在其他流中不存在的风险。 仅当其他更安全的流(如托管标识)不可行时,才应使用此流。

PG_DATABASE_APPUSER_SECRET=$(echo -n | openssl rand -base64 16)

kubectl create secret generic db-user-pass \
    --from-literal=username=app \
     --from-literal=password="${PG_DATABASE_APPUSER_SECRET}" \
     --namespace $PG_NAMESPACE \
     --context $AKS_PRIMARY_CLUSTER_NAME
  1. 使用 kubectl get 命令验证是否已成功创建机密。

    kubectl get secret db-user-pass --namespace $PG_NAMESPACE --context $AKS_PRIMARY_CLUSTER_NAME
    

设置用于 PostgreSQL 群集的环境变量

  • 部署 ConfigMap 以使用以下kubectl apply命令配置 CNPG 操作器。 这些值将替换不再需要的旧 ENABLE_AZURE_PVC_UPDATES 开关,以便错开升级并加快副本重新连接的速度。 将此配置滚动到生产环境之前,请验证你依赖的任何现有 DRAIN_TAINTS 设置是否与 Azure 环境保持兼容。

    cat <<EOF | kubectl apply --context $AKS_PRIMARY_CLUSTER_NAME -n $PG_NAMESPACE -f -
    apiVersion: v1
    kind: ConfigMap
    metadata:
        name: cnpg-controller-manager-config
    data:
        CLUSTERS_ROLLOUT_DELAY: '120'
        STANDBY_TCP_USER_TIMEOUT: '10'
    EOF
    

安装 Prometheus PodMonitor

Prometheus 使用 CNPG GitHub 示例存储库中存储的记录规则来擦除 CNPG。 由于操作员管理的 PodMonitor 已弃用,因此请自行创建和管理 PodMonitor 资源,以便你可以将其定制到监视堆栈。

  1. 使用 helm repo add 命令添加 Prometheus 社区 Helm 存储库。

    helm repo add prometheus-community \
        https://prometheus-community.github.io/helm-charts
    
  2. 升级 Prometheus 社区 Helm 存储库,并使用带有 helm upgrade 标志的 --install 命令在主群集上安装它。

    helm upgrade --install \
        --namespace $PG_NAMESPACE \
        -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/docs/src/samples/monitoring/kube-stack-config.yaml \
        prometheus-community \
        prometheus-community/kube-prometheus-stack \
        --kube-context=$AKS_PRIMARY_CLUSTER_NAME
    
  3. 为群集创建 PodMonitor。 CNPG 团队正在弃用操作员管理的 PodMonitor,因此现在直接对其进行管理:

    cat <<EOF | kubectl apply --context $AKS_PRIMARY_CLUSTER_NAME --namespace $PG_NAMESPACE -f -
    apiVersion: monitoring.coreos.com/v1
    kind: PodMonitor
    metadata:
      name: $PG_PRIMARY_CLUSTER_NAME
      namespace: ${PG_NAMESPACE}
      labels:
        cnpg.io/cluster: ${PG_PRIMARY_CLUSTER_NAME}
    spec:
      selector:
        matchLabels:
          cnpg.io/cluster: ${PG_PRIMARY_CLUSTER_NAME}
      podMetricsEndpoints:
        - port: metrics
    EOF
    

创建联合凭据

在本部分,你将创建 PostgreSQL 备份的联合标识凭据,以允许 CNPG 使用 AKS 工作负载标识向存储帐户目标进行身份验证来进行备份。 CNPG 运算符会创建一个与 CNPG 群集部署清单中使用的群集同名的 Kubernetes 服务帐户。

  1. 使用 az aks show 命令获取群集 OIDC 颁发者 URL。

    export AKS_PRIMARY_CLUSTER_OIDC_ISSUER="$(az aks show \
        --name $AKS_PRIMARY_CLUSTER_NAME \
        --resource-group $RESOURCE_GROUP_NAME \
        --query "oidcIssuerProfile.issuerUrl" \
        --output tsv)"
    
  2. 使用 az identity federated-credential create 命令创建联合标识凭据。

    az identity federated-credential create \
        --name $AKS_PRIMARY_CLUSTER_FED_CREDENTIAL_NAME \
        --identity-name $AKS_UAMI_CLUSTER_IDENTITY_NAME \
        --resource-group $RESOURCE_GROUP_NAME \
        --issuer "${AKS_PRIMARY_CLUSTER_OIDC_ISSUER}" \
        --subject system:serviceaccount:"${PG_NAMESPACE}":"${PG_PRIMARY_CLUSTER_NAME}" \
        --audience api://AzureADTokenExchange
    

部署高度可用的 PostgreSQL 群集

在本部分,你将使用 CNPG 群集自定义资源定义 (CRD) 部署高度可用的 PostgreSQL 群集。

群集 CRD 参数

下表概述了在群集 CRD 的 YAML 部署清单中设置的关键属性:

properties 定义
imageName 指向 CloudNativePG 操作容器镜像。 按照本指南中所示,与核心备份集成一起使用 ghcr.io/cloudnative-pg/postgresql:18-system-trixie,或者在采用 Barman Cloud 插件时切换到 18-standard-trixie
inheritedMetadata 特定于 CNPG 运算符。 CNPG 运算符将元数据应用于与群集相关的每个对象。
annotations 包括暴露群集终结点时所需的 DNS 标签,并为基于仲裁的故障转移启用 alpha.cnpg.io/failoverQuorum
labels: azure.workload.identity/use: "true" 指示 AKS 应将工作负载标识依赖项注入托管 PostgreSQL 群集实例的 Pod。
topologySpreadConstraints 需要不同的区域和具有 "workload=postgres" 标签的不同节点。
resources 配置服务质量 (QoS) 类 Guaranteed。 在生产环境中,这些值是最大限度使用基础节点 VM 的关键,根据所使用的 Azure VM SKU 而有所不同。
probes 替换旧 startDelay 配置。 流媒体启动和就绪探测有助于确保副本在对外提供流量服务之前正常运行。
smartShutdownTimeout 允许长时间运行的事务在更新期间优雅地完成,而不是采用强制停止延迟。
bootstrap 特定于 CNPG 运算符。 使用空的应用数据库进行初始化。
storage 定义数据库的 PersistentVolume 设置。 使用 Azure 托管磁盘时,简化的语法会将数据和 WAL 保留在相同的 64 GiB 卷上,从而在托管磁盘上提供更好的吞吐量层。 如果需要单独的 WAL 卷,请进行调整。
postgresql.synchronous minSyncReplicas / maxSyncReplicas已被较新的架构替换,并允许您指定同步复制行为。
postgresql.parameters 特定于 CNPG 运算符。 映射 postgresql.confpg_hba.confpg_ident.conf 的设置。 该示例强调符合 AKS 工作负荷标识方案的可观测性和 WAL 保留默认值,但应针对每个工作负荷进行优化。
serviceAccountTemplate 包含生成服务帐户所需的模板,并将 AKS 联合标识凭据映射到 UAMI,从而能够从托管 PostgreSQL 实例的 Pod 进行 AKS 工作负载标识身份验证来访问外部 Azure 资源。
barmanObjectStore 特定于 CNPG 运算符。 使用 AKS 工作负载标识配置 barman-cloud 工具套件,以便向 Azure Blob 存储对象存储进行身份验证。

若要进一步隔离 PostgreSQL 工作负荷,可以将污点(例如 node-role.kubernetes.io/postgres=:NoSchedule)添加到数据平面节点,并将示例 nodeSelector/tolerations 替换为 CloudNativePG 建议的值。 如果采用此方法,请相应地标记节点,并确认 AKS 自动缩放程序策略与拓扑保持一致。

PostgreSQL 性能参数

PostgreSQL 性能在很大程度上取决于群集的基础资源和工作负荷。 下表提供了标准 D4s v3 节点上运行的三节点群集(16-GiB 内存)的基线指导。 将这些值视为起点,并在了解工作负荷配置文件后对其进行调整:

properties 建议的值 定义
wal_compression lz4 使用指定方法压缩 WAL 文件中的整页写入
max_wal_size 6 GB 设置触发检查点的 WAL 大小
checkpoint_timeout 15 分钟 在自动 WAL 检查点之间设置最长时间
checkpoint_completion_target 0.9 在检查点窗口期间平衡检查点工作负载
checkpoint_flush_after 2 MB 在将以前执行的写入刷新到磁盘之前所要达到的页数
wal_writer_flush_after 2 MB WAL 写入器写出的 WAL 数量,达到该数量将触发刷新
min_wal_size 2 GB 设置要将 WAL 收缩到的最小大小
max_slot_wal_keep_size 10 GB WAL 剩余用于服务复制槽的上限
shared_buffers 4 GB 设置服务器使用的共享内存缓冲区数(本示例中节点内存的 25%)
effective_cache_size 12 GB 设置规划器关于数据缓存总大小的假设
work_mem 1/256 的节点内存 设置要用于查询工作区的最大内存
maintenance_work_mem 6.25% 的节点内存 设置用于维护操作的最大内存
autovacuum_vacuum_cost_limit 2400 autovacuum 在短暂睡眠之前可用的清空成本量
random_page_cost 1.1 设置规划器对不按顺序提取磁盘页的成本估计值
effective_io_concurrency 64 设置磁盘子系统能够有效处理的同时请求数
maintenance_io_concurrency 64 用于维护工作的“effective_io_concurrency”变体

部署 PostgreSQL

  1. 使用 kubectl apply 命令通过群集 CRD 部署 PostgreSQL 群集。

    cat <<EOF | kubectl apply --context $AKS_PRIMARY_CLUSTER_NAME -n $PG_NAMESPACE -v 9 -f -
    apiVersion: postgresql.cnpg.io/v1
    kind: Cluster
    metadata:
      name: $PG_PRIMARY_CLUSTER_NAME
      annotations:
        alpha.cnpg.io/failoverQuorum: "true"
    spec:
      imageName: ghcr.io/cloudnative-pg/postgresql:18-system-trixie
      inheritedMetadata:
        annotations:
          service.beta.kubernetes.io/azure-dns-label-name: $AKS_PRIMARY_CLUSTER_PG_DNSPREFIX
        labels:
          azure.workload.identity/use: "true"
    
      instances: 3
      smartShutdownTimeout: 30
    
      probes:
        startup:
          type: streaming
          maximumLag: 32Mi
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 120
        readiness:
          type: streaming
          maximumLag: 0
          periodSeconds: 10
          failureThreshold: 6
    
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            cnpg.io/cluster: $PG_PRIMARY_CLUSTER_NAME
    
      affinity:
        nodeSelector:
          workload: postgres
    
      resources:
        requests:
          memory: '8Gi'
          cpu: 2
        limits:
          memory: '8Gi'
          cpu: 2
    
      bootstrap:
        initdb:
          database: appdb
          owner: app
          secret:
            name: db-user-pass
          dataChecksums: true
    
      storage:
        storageClass: $POSTGRES_STORAGE_CLASS
        size: 64Gi
    
      postgresql:
        synchronous:
          method: any
          number: 1
        parameters:
          wal_compression: lz4
          max_wal_size: 6GB
          max_slot_wal_keep_size: 10GB
          checkpoint_timeout: 15min
          checkpoint_completion_target: '0.9'
          checkpoint_flush_after: 2MB
          wal_writer_flush_after: 2MB
          min_wal_size: 2GB
          shared_buffers: 4GB
          effective_cache_size: 12GB
          work_mem: 62MB
          maintenance_work_mem: 1GB
          autovacuum_vacuum_cost_limit: "2400"
          random_page_cost: "1.1"
          effective_io_concurrency: "64"
          maintenance_io_concurrency: "64"
          log_checkpoints: 'on'
          log_lock_waits: 'on'
          log_min_duration_statement: '1000'
          log_statement: 'ddl'
          log_temp_files: '1024'
          log_autovacuum_min_duration: '1s'
          pg_stat_statements.max: '10000'
          pg_stat_statements.track: 'all'
          hot_standby_feedback: 'on'
        pg_hba:
          - host all all all scram-sha-256
    
      serviceAccountTemplate:
        metadata:
          annotations:
            azure.workload.identity/client-id: "$AKS_UAMI_WORKLOAD_CLIENTID"
          labels:
            azure.workload.identity/use: "true"
    
      backup:
        barmanObjectStore:
          destinationPath: "https://${PG_PRIMARY_STORAGE_ACCOUNT_NAME}.blob.core.chinacloudapi.cn/backups"
          azureCredentials:
            inheritFromAzureAD: true
        retentionPolicy: '7d'
    EOF
    

注释

示例清单使用 ghcr.io/cloudnative-pg/postgresql:18-system-trixie 镜像,因为它适用于稍后显示的核心 Barman Cloud 集成。 准备好切换到 Barman Cloud 插件时,在重新部署群集之前,请更新spec.imageNameghcr.io/cloudnative-pg/postgresql:18-standard-trixie并遵循插件配置指南

重要

该示例 pg_hba 条目允许非 TLS 访问。 如果保留此配置,请记录团队的安全影响,并尽可能首选加密连接。

  1. 使用 kubectl get 命令验证是否已成功创建主要 PostgreSQL 群集。 CNPG 群集 CRD 指定了三个实例,可以在启动并加入每个实例进行复制后查看正在运行的 Pod 来验证。 请耐心等待,因为所有三个实例需要一些时间才能联机并加入群集。

    kubectl get pods --context $AKS_PRIMARY_CLUSTER_NAME --namespace $PG_NAMESPACE -l cnpg.io/cluster=$PG_PRIMARY_CLUSTER_NAME
    

    示例输出

    NAME                         READY   STATUS    RESTARTS   AGE
    pg-primary-cnpg-r8c7unrw-1   1/1     Running   0          4m25s
    pg-primary-cnpg-r8c7unrw-2   1/1     Running   0          3m33s
    pg-primary-cnpg-r8c7unrw-3   1/1     Running   0          2m49s
    

重要

如果您将本地 NVMe 与 Azure 容器存储配合使用,而 Pod 仍处于初始化状态并出现多重连接错误,则 Pod 仍在由于该错误而在丢失的节点上搜索卷。 Pod 开始运行后,会进入 CrashLoopBackOff 状态,因为 CNPG 在新的节点上创建了没有数据的新副本,并且无法找到 pgdata 目录。 若要解决此问题,请销毁受影响的实例并启动一个新实例。 运行下面的命令:

kubectl cnpg destroy [cnpg-cluster-name] [instance-number]  

验证 Prometheus PodMonitor 是否正在运行

手动创建的 PodMonitor 将 kube-prometheus-stack 抓取配置与之前部署的 CNPG Pod 关联起来。

使用 kubectl get 命令验证 PodMonitor 是否正在运行。

kubectl --namespace $PG_NAMESPACE \
    --context $AKS_PRIMARY_CLUSTER_NAME \
    get podmonitors.monitoring.coreos.com \
    $PG_PRIMARY_CLUSTER_NAME \
    --output yaml

示例输出

kind: PodMonitor
metadata:
  labels:
    cnpg.io/cluster: pg-primary-cnpg-r8c7unrw
  name: pg-primary-cnpg-r8c7unrw
  namespace: cnpg-database
spec:
  podMetricsEndpoints:
  - port: metrics
  selector:
    matchLabels:
      cnpg.io/cluster: pg-primary-cnpg-r8c7unrw

如果使用 Azure Monitor for Managed Prometheus,则需要使用自定义组名称添加另一个 Pod 监视器。 托管版 Prometheus 不会从 Prometheus 社区获取自定义资源定义(CRDs)。 除了组名外,CRD 是相同的。 该设计允许托管 Prometheus 下的 Pod 监视器与使用社区 CRD 的 Pod 监视器并行运行。 如果不使用托管 Prometheus,可以跳过本部分。 创建新的 Pod 监视器:

cat <<EOF | kubectl apply --context $AKS_PRIMARY_CLUSTER_NAME --namespace $PG_NAMESPACE -f -
apiVersion: azmonitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: cnpg-cluster-metrics-managed-prometheus
  namespace: ${PG_NAMESPACE}
  labels:
    azure.workload.identity/use: "true"
    cnpg.io/cluster: ${PG_PRIMARY_CLUSTER_NAME}
spec:
  selector:
    matchLabels:
      azure.workload.identity/use: "true"
      cnpg.io/cluster: ${PG_PRIMARY_CLUSTER_NAME}
  podMetricsEndpoints:
    - port: metrics
EOF

验证是否已创建 Pod 监视器(请注意组名的差异)。

kubectl --namespace $PG_NAMESPACE \
    --context $AKS_PRIMARY_CLUSTER_NAME \
    get podmonitors.azmonitoring.coreos.com \
    -l cnpg.io/cluster=$PG_PRIMARY_CLUSTER_NAME \
    -o yaml

选项 A - Azure Monitor 工作区

部署 Postgres 群集和 Pod 监视器后,可以在 Azure Monitor 工作区中使用 Azure 门户查看指标。

显示 Azure 门户中 Azure Monitor 工作区中的 Postgres 群集指标的屏幕截图。

选项 B - 托管 Grafana

或者,在部署 Postgres 群集和 Pod 监视器后,可以在部署脚本创建的托管 Grafana 实例上创建指标仪表板,以可视化导出到 Azure Monitor 工作区的指标。 可以通过 Azure 门户访问托管 Grafana。 导航到部署脚本创建的托管 Grafana 实例,然后选择端点链接,如下所示:

Azure 门户中 Azure 托管 Grafana 实例中的 Postgres 群集指标的屏幕截图。

选择“终结点”链接将打开一个新的浏览器窗口,你可以在托管 Grafana 实例上创建仪表板。 按照说明配置 Azure Monitor 数据源,然后就可添加可视化效果,创建 Postgres 群集指标仪表板。 设置数据源连接后,从主菜单中选择“数据源”选项。 您应该能看到用于数据源连接的一组数据源选项,正如这里所示。

显示 Azure 门户中的 Azure Monitor 数据源选项的屏幕截图。

在“托管 Prometheus”选项上,选择“生成仪表板”选项以打开仪表板编辑器。 编辑器窗口打开后,选择“添加可视化效果”选项,然后选择“托管 Prometheus”选项以浏览 Postgres 群集中的指标。 选择要可视化的指标后,选择“运行查询”按钮以提取可视化效果的数据,如下所示:

显示具有 Postgres 群集指标的托管 Prometheus 仪表板的屏幕截图。

选择“保存”图标,将面板添加到仪表板。 在仪表板编辑器中选择“添加”按钮后,可以添加其他面板,并通过重复此过程来可视化其他指标。 添加指标可视化效果后,应会显示如下所示的内容:

显示 Azure 门户中保存的托管 Prometheus 仪表板的屏幕截图。

选择“保存”图标以保存仪表板。


后续步骤

供稿人

Microsoft 会维护本文。 本系列文章为以下参与者的原创作品:

  • Ken Kilty | 首席 TPM
  • Russell de Pina | 首席 TPM
  • Adrian Joian | 高级客户工程师
  • Jenny Hayes | 高级内容开发人员
  • Carol Smith | 高级内容开发人员
  • Erin Schaffer | 内容开发人员 2
  • Adam Sharif | 客户工程师 2

确认书

本文档是与 CloudNativePG 操作员的维护者 EnterpriseDB 联合开发的。 我们感谢 加布里埃勒·巴托林尼 审查本文档的早期草稿并提供技术改进。