GDC 网闸隔离配置上的 PostgreSQL 数据库参考实现

本指南全面介绍了如何在 Google Distributed Cloud (GDC) 网闸隔离配置环境中跨三个可用区部署高可用性 PostgreSQL 堆栈。您将学习如何准备必要的软件工件、引导目标虚拟机,以及使用 Autobase 自动执行整个预配过程。在本指南中,Patroni 用作主要管理层,用于编排 PostgreSQL 生命周期并处理自动故障切换。

架构

该架构由分布在三个可用区的三个虚拟机环境组成。

运行共置服务堆栈的三虚拟机架构。

每个虚拟机都完全相同,并运行一组共置的服务:

  • PostgreSQL 17:核心关系型数据库引擎。
  • Patroni:高可用性管理器。它负责处理 PostgreSQL 进程的生命周期,并执行自动故障切换。它在端口 8008(端点 /primary)上公开一个 HTTPS REST API,负载均衡器使用该 API 来识别当前领导者。
  • etcd:分布式配置存储区 (DCS)。它为领导者选举提供共识层,并存储 Patroni 的配置。
  • PgBouncer:位于 PostgreSQL 前面的连接池,用于稳定连接开销。它为端口 6432 上的应用流量提供推荐的入口点。

该堆栈还包含一个 GDC 网闸隔离配置全球 L4 负载均衡器,这是一项平台代管式服务,可提供稳定的虚拟 IP (VIP)。应用通过端口 6432 连接到稳定的 VIP,负载均衡器会将流量路由到当前主虚拟机上的 PgBouncer。然后,PgBouncer 将请求代理到本地 PostgreSQL 实例。为了管理流量,负载均衡器会持续轮询 Patroni HTTPS 端点,以进行健康检查。

主虚拟机上的健康检查会返回 HTTP 200 OK,以指示虚拟机已准备好接收流量,而副本虚拟机上的健康检查会返回 HTTP 503 Service Unavailable,以指示负载均衡器绕过它们。如果主实例发生故障,系统会选出一个新的主实例,其 Patroni 实例会开始返回 HTTP 200 OK,从而使负载均衡器自动将流量重定向到新虚拟机的 PgBouncer 端口。

为确保高可用性并防止数据丢失,该堆栈依赖于仲裁的概念。如果使用 3 个虚拟机,系统要求至少有 2 个成员(即大多数成员)处于健康状态并保持通信,才能选出领导者并保持正常运行。这种基于多数的共识由 etcd 和 Patroni 管理,使堆栈能够自动容忍任何单个虚拟机或可用区的完全故障。

性能考虑因素

在规划部署时,请考虑以下具体因素,以优化性能和可靠性:

  • 硬件容量:虽然要求因工作负载而异,但请使用以下标准配置文件作为每个虚拟机的起点:
    • 开发/概念验证:2 个 vCPU,8GB RAM(稳定运行的最低要求)。
    • 小型生产环境:4 个 vCPU,16GB RAM。适用于并发量适中的内部工具。
    • 标准生产环境:8 个 vCPU,32GB RAM。任务关键型应用的建议基准。
    • 高吞吐量:16 个以上 vCPU,64GB 以上 RAM。适用于需要在内存中进行大量数据缓存(PostgreSQL 共享缓冲区)的工作负载。
  • 存储性能:高性能存储至关重要。强烈建议使用 SSD 磁盘,以确保 etcd 的稳定性。etcd 对磁盘写入延迟时间极为敏感;官方 etcd 硬件指南建议 p99 磁盘 WAL fdatasync 延迟时间小于 10 毫秒。
  • 网络延迟时间:虚拟机之间的延迟时间会直接影响复制性能:
    • etcd Quorum:平均往返时间 (RTT) 应小于 50 毫秒(最好小于 10 毫秒),以防止选举超时和集群不稳定。
    • 同步复制:如果配置了同步复制,则每个写入事务都必须等待副本的确认。GDC 网闸隔离配置中的可用区之间延迟时间通常小于 1 毫秒,这对于将写入开销保持在最低水平(通常为 10-30%)非常有利。
  • PgBouncer 的作用:PostgreSQL 会为每个连接创建一个新的操作系统进程,这会消耗大约 10MB 的 RAM,并产生 CPU 上下文切换费用。PgBouncer 通过维护一个持久连接池来减少这种开销,从而使数据库能够使用明显更少的后端进程处理数千个应用连接。
  • 边车组件:Patroni 和 etcd 虽然轻量,但需要持续的 CPU 可用性。在高负载场景中,请确保虚拟机在 Hypervisor 级别未超额订阅,以避免“窃取”检测信号和领导者维护所需的 CPU 周期。
  • 内核调优:Autobase 自动化功能会自动应用对 PostgreSQL 有益的优化,例如配置 sysctl 参数(例如 vm.swappiness、net.core.somaxconn)和停用透明大页面 (THP)。这些更改可减少内存管理开销,并提高高流量数据库实例的网络吞吐量。

准备工作

在开始部署之前,您必须确保您的环境满足以下要求。

查看虚拟机要求

在本教程中,您必须在 GDC 网闸隔离配置项目中创建三个虚拟机。您需要考虑虚拟机的以下要点和要求:

  • 可用区分布:为了使此部署真正能够应对可用区故障,您应将虚拟机分布在三个不同的可用区中。不过,如果虚拟机位于两个可用区甚至单个可用区中,则部署保持不变。最重要的是,所有虚拟机都可以通过网络使用其内部 IP 地址相互通信。
  • 操作系统:本教程假设您使用的是 Ubuntu 22.04 映像。如果您使用其他发行版,则本指南中的后续步骤可能会有所不同。
  • 资源:在本教程中,您应为每个虚拟机预配至少 2 个 CPU 和 8GB 内存。在生产环境中,您必须预配适合特定工作负载的资源(请参阅性能注意事项)。
  • 网络 IP:您应记下每个虚拟机的内部和外部 IP 地址。在本指南中,您将使用外部 IP 进行 Ansible 控制,因为您是从外部工作站运行命令。内部 IP 用于服务间通信和绑定。如果您在网络内预配了引导程序虚拟机,则只需要内部 IP。
  • 访问权限:部署用户需要无密码 sudo 访问权限,因为 Ansible 自动化需要执行管理任务(安装软件包、修改系统配置),而不会被密码提示阻止。
  • SSH:必须启用基于密钥的身份验证,以允许 Ansible 安全且非交互式地连接到目标虚拟机。

准备本地工作站软件

如需管理部署并准备经过网闸隔离的工件,您需要在本地工作站上安装一组自动化和容器化工具。

  • Ansible 2.17.0+:执行部署 playbook 和角色的自动化引擎。
  • Docker:用于在与目标虚拟机 (Ubuntu 22.04) 相同的环境中拉取和打包操作系统依赖项。
  • PostgreSQL 客户端 (psql):需要运行测试查询并验证本地工作站中的数据复制。
  • Autobase 代码库:

    • 克隆代码库以访问自动化 playbook 和角色:https://github.com/vitabaks/autobase
    • 检出特定版本(本指南使用 2.5.2 版):

      git checkout 2.5.2

      如果您使用其他发行版,则本指南中的后续步骤可能会有所不同。

    • 如需运行本指南中的 playbook,您必须将本地 autobase 源代码安装为 Ansible 集合,以便解析角色前缀:

      cd autobase/automation
      ansible-galaxy collection install . --force
      

创建一些环境变量

在本指南中,您将使用以下环境变量来简化命令。这些变量用于存储关键参数,例如项目 ID、虚拟机可用区、虚拟机主机名,以及负载均衡器用于识别集群的标签。在当前 shell 会话中,将这些变量设置为您环境的实际值(确保可用区以空格分隔)。

请注意,虽然您可以为虚拟机设置任何所需的名称,但本指南使用 postgres-vm-1、postgres-vm-2 和 postgres-vm-3 作为集群节点的任意示例名称:

export PROJECT_ID="your-project-id"
export ZONES="zone1 zone2 zone3"
export VM1_NAME="postgres-vm-1"
export VM2_NAME="postgres-vm-2"
export VM3_NAME="postgres-vm-3"
export VM_LABEL="app=my-postgres-cluster"
export CLUSTER_NAME="my-postgres-cluster"

配置 Ansible

使用以下模板在 inventory.ini 文件中定义虚拟机环境:

[master]
postgres-vm-1 ansible_host=XX.XX.XX.XX hostname=postgres-vm-1 bind_address=XX.XX.XX.XX

[replica]
postgres-vm-2 ansible_host=XX.XX.XX.XX hostname=postgres-vm-2 bind_address=XX.XX.XX.XX
postgres-vm-3 ansible_host=XX.XX.XX.XX hostname=postgres-vm-3 bind_address=XX.XX.XX.XX

[postgres_cluster:children]
master
replica

[etcd_cluster]
postgres-vm-1
postgres-vm-2
postgres-vm-3

[all:vars]
ansible_user=...
ansible_ssh_private_key_file=~/.ssh/...
postgresql_version=17
with_haproxy_load_balancing=false
patroni_superuser_password=...
etcd_package_repo="file:///tmp/packages/etcd-v3.5.25-linux-amd64.tar.gz"
installation_method="packages"
install_postgresql_repo=false
install_timescale_repo=false
install_citus_repo=false
apt_repository=[]
yum_repository=[]
install_system_packages=false
patroni_installation_method=deb

了解配置:

  • [master] 和 [replica]:定义主数据库虚拟机和辅助数据库虚拟机。请确保使用在 VM1_NAME、VM2_NAME 和 VM3_NAME 环境变量中设置的实际虚拟机名称。
  • [postgres_cluster:children]:一个汇总了主节点和副本节点的组,使 Ansible 能够通过单个命令定位整个数据库集群。
  • [etcd_cluster]:定义将参与 etcd 共识集群的节点。这包括所有三个数据库节点,以确保高可用性。
  • ansible_host:(对于每个虚拟机)Ansible 用于连接到该虚拟机的虚拟机的外部入站 IP。将 XX.XX.XX.XX 替换为实际的外部 IP。
  • bind_address:(对于每个虚拟机)虚拟机的内部 IP 地址。将 XX.XX.XX.XX 替换为实际内部 IP。
  • ansible_user:Ansible 用于通过 SSH 连接到目标虚拟机的远程用户。将 ... 替换为实际用户名。
  • ansible_ssh_private_key_file:用于向目标虚拟机进行身份验证的 SSH 密钥的本地路径。将 ~/.ssh/... 替换为实际路径。
  • patroni_superuser_password:postgres 用户的密码。 请务必在此处使用安全系数高的密码。
  • with_haproxy_load_balancing=false:停用本地 HAProxy,因为您使用的是平台原生 L4 负载均衡器。
  • etcd_package_repo:指向虚拟机引导目录中 etcd 二进制文件的本地路径。
  • installation_method="packages":指示自动化工具使用操作系统软件包安装组件,而不是从源代码编译或使用 Python pip。
  • install_..._repo=false 和 _repository=[]:这些替换可防止 Ansible 尝试访问互联网以添加外部代码库或更新软件包列表。
  • install_system_packages=false:防止自动化任务尝试下载和安装您在初始化或引导阶段已预配的软件包。
  • patroni_installation_method=deb:明确告知角色使用您安装的 .deb 软件包。

初始化虚拟机

数据库堆栈需要多个操作系统软件包和库,这些软件包和库可能不包含在您的基础 Ubuntu 映像中。由于虚拟机位于没有互联网访问权限的经过网闸隔离的环境中,因此它们无法自行下载这些依赖项。

如需解决此问题,请按以下步骤操作:

  • 在本地工作站上使用 Docker 容器下载所有必要的文件。以下命令使用 apt-rdepends 递归识别目标应用所需的每个共享库和依赖项。它会在容器内配置官方 PostgreSQL 代码库,以提取版本 17 工件,然后遍历依赖项列表,下载各个 .deb 文件,同时过滤掉核心系统库(例如 libc6 或 hostname),以避免目标虚拟机上发生版本冲突。最后,它会直接从 GitHub 提取独立的 etcd 二进制文件。

    首先,创建一个用于存放软件包的目录:

    mkdir -p ./packages
    

    然后,运行 Docker 命令以下载所有必需的软件包和 etcd 二进制文件:

    docker run --rm --platform linux/amd64 -v "$(pwd)/packages:/packages" \
      ubuntu:22.04 bash -c "
      set -e
      apt-get update
      apt-get install -y ca-certificates curl gnupg apt-rdepends
    
      # Add PostgreSQL Repository
      curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
        gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
      echo 'deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main' > \
        /etc/apt/sources.list.d/pgdg.list
      apt-get update
    
      # Define Application Targets + Explicit dependencies needed for air-gap
      TARGETS='unzip tar pgbouncer patroni netdata postgresql-17 \
        postgresql-client-17 postgresql-contrib-17 \
        postgresql-server-dev-17 postgresql-17-dbgsym \
        python3-psycopg2 python3-click python3-yaml python3-prettytable \
        python3-urllib3 python3-tz python3-pip python3-setuptools \
        python3-cryptography moreutils vim jq acl zstd libjq1 \
        libpython3.10-stdlib libexpat1-dev zlib1g-dev libipc-run-perl \
        libtime-duration-perl libjson-perl libpython3-dev \
        libjs-sphinxdoc python3-wheel'
    
      # Resolve all recursive dependencies
      ALL_DEPS=\$(apt-cache depends --recurse --no-recommends --no-suggests \
        --no-conflicts --no-breaks --no-replaces --no-enhances \$TARGETS | \
        grep '^\w' | sort -u)
    
      cd /packages
      for pkg in \$ALL_DEPS; do
        if apt-cache show \"\$pkg\" > /dev/null 2>&1; then
          # Filter system core to avoid VM conflicts/breaks
          # We exclude core OS libraries (libc, systemd, etc.) because these
          # often cause version conflicts if the VM's patch level differs
          # from the online container.
          FILTER='base-files|debianutils|coreutils|findutils|diffutils|sed'
          FILTER+='|grep|gzip|hostname|ncurses|perl-base|libc6|binutils'
          FILTER+='|linux-libc|libc-bin|libc-dev-bin|systemd|dpkg|init'
          if [[ ! \"\$pkg\" =~ \$FILTER ]]; then
            apt-get download \"\$pkg\" || echo \"Failed \$pkg\"
          fi
        fi
      done
    
      # Download etcd binary
      if [ ! -f etcd-v3.5.25-linux-amd64.tar.gz ]; then
        curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.25/\
    etcd-v3.5.25-linux-amd64.tar.gz -o etcd-v3.5.25-linux-amd64.tar.gz
      fi
    "
    
  • 使用 Ansible 同时将归档文件上传到所有三个目标虚拟机:

    ansible all -i inventory.ini -m copy -a "src=packages.tar.gz dest=/tmp/" -b
    
  • 清除虚拟机上的所有现有软件包数据,并解压缩新的 tar 文件:

    ansible all -i inventory.ini -m shell -a "rm -rf /tmp/packages && \
      mkdir -p /tmp/packages && tar -xzf /tmp/packages.tar.gz -C /tmp/packages" -b
    
  • 以非交互方式安装所有已下载的 .deb 软件包。 为避免在经过网闸隔离的环境中出现特定预依赖项问题,请使用 --force-depends 标志,后跟 apt-get install -fy,以在本地解析依赖项树:

    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a dpkg -i --force-depends /tmp/packages/*.deb" -b
    
    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a apt-get install -fy" -b
    
  • 立即停止所有服务,以防止它们在自动化准备就绪之前以默认、未配置的状态启动:

    ansible all -i inventory.ini -m shell -a \
      "systemctl stop patroni etcd pgbouncer postgresql || true" -b
    
  • 最后,移除默认的 PostgreSQL 集群和所有现有 etcd 数据,以便进行干净的初始化:

    ansible all -i inventory.ini -m shell -a \
      "pg_dropcluster 17 main --stop || true" -b
    ansible all -i inventory.ini -m shell -a \
      "rm -rf /var/lib/postgresql/17/main/* /var/lib/etcd/default.etcd/*" -b
    

预配数据库基础设施

在引导虚拟机并配置资产清单后,您现在可以使用 Autobase 自动化 playbook 和 Ansible 来部署高可用性 PostgreSQL 堆栈。

首先,运行预检,确保环境已准备就绪:

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini \
  --tags pre_checks

如果检查通过,请继续进行完整部署:

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini

预期输出:Playbook 应成功完成并显示“PLAY RECAP”,表明所有目标虚拟机均已访问并更新:

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=254  rescued=0    ignored=0
postgres-vm-1              : ok=160  changed=53   unreachable=0    failed=0    skipped=514  rescued=0    ignored=2
postgres-vm-2              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2
postgres-vm-3              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2

验证 Deployment

部署完成后,您应执行多项检查,以确保所有组件都正常运行。

检查高可用性状态

检查高可用性管理器的状态,查看分配给每个虚拟机的角色:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

输出示例:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

验证健康检查端点

使用 Patroni 的 REST API 测试 Patroni 是否正确识别了主节点和副本。 主虚拟机健康检查应返回 200 OK,而副本虚拟机健康检查应返回 503 Service Unavailable:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM3_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

验证单个虚拟机的运行状况

检查所有 PostgreSQL 实例的就绪情况:

ansible postgres_cluster -i inventory.ini -m shell -a "pg_isready -p 5432" -b

输出示例:

postgres-vm-1 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-2 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-3 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

验证 etcd 运行状况

使用 localhost 作为端点,验证所有虚拟机上的共识层的健康状况:

ansible all -i inventory.ini -m shell -a "ETCDCTL_API=3 etcdctl \
  --endpoints=https://localhost:2379 \
  --cacert=/etc/etcd/tls/ca.crt \
  --cert=/etc/etcd/tls/server.crt \
  --key=/etc/etcd/tls/server.key \
  endpoint health" -b

输出示例:

postgres-vm-1 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 28.78311ms

postgres-vm-2 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 33.081265ms

postgres-vm-3 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 24.414291ms

配置全球负载均衡器

如需为数据库堆栈提供稳定的虚拟 IP (VIP),请使用 gdcloud CLI 配置平台原生的全球 L4 负载均衡器。

  • 前提条件:

    • 确保您在项目中拥有 load-balancer-admin 角色。
    • 为虚拟机应用标签,以便负载均衡器能够正确定位到需要提供服务的实例(将 kubeconfig 参数值替换为每个可用区的相应 Management API kubeconfig 文件):

      kubectl --kubeconfig=ZONE_A_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM1_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_B_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM2_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_C_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM3_NAME} \
        ${VM_LABEL}
      
  • 定义负载均衡访问权限级别。如果您需要从项目网络外部进行连接,请设置为 EXTERNAL;如果只需要从 VPC 内部进行访问,请设置为 INTERNAL。在本教程中,我们将使用外部设置:

    export LB_SCHEME=EXTERNAL
    
  • 创建健康检查。负载均衡器使用 Patroni 的 REST API 来识别主节点:

    gdcloud compute health-checks create https ${CLUSTER_NAME}-hc \
      --project=${PROJECT_ID} \
      --port=8008 \
      --request-path="/primary" \
      --check-interval=10 \
      --timeout=5 \
      --healthy-threshold=2 \
      --unhealthy-threshold=3 \
      --global
    
  • 为虚拟机所在的每个可用区创建单独的可用区级后端:

    for zone in $(echo $ZONES); do
      gdcloud compute backends create ${CLUSTER_NAME}-backend-${zone} \
        --project=${PROJECT_ID} \
        --zone=${zone} \
        --labels="${VM_LABEL}"
    done
    
  • 创建全球后端服务:

    gdcloud compute backend-services create ${CLUSTER_NAME}-bes \
      --project=${PROJECT_ID} \
      --health-check="${CLUSTER_NAME}-hc" \
      --global
    
  • 将可用区级后端添加到全球级服务:

    for zone in $(echo $ZONES); do
      gdcloud compute backend-services add-backend ${CLUSTER_NAME}-bes \
        --project=${PROJECT_ID} \
        --backend=${CLUSTER_NAME}-backend-${zone} \
        --backend-zone=${zone} \
        --global
    done
    
  • 创建全局转发规则(VIP)。此规则会在端口 6432 上公开数据库:

    gdcloud compute forwarding-rules create ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --backend-service=${CLUSTER_NAME}-bes \
      --ip-protocol-port="TCP:6432" \
      --global
    
  • 检索 VIP 地址:

    LB_IP=$(gdcloud compute forwarding-rules describe ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --global \
      --format=json \
      | jq -r '.metadata.annotations["networking.gke.io/forwardingRuleCIDR"]'  \
      | cut -d '/' -f 1)
    
    echo "The load balancer IP is: ${LB_IP}"
    
  • 创建 ProjectNetworkPolicy (PNP) 以允许入站流量进入 PgBouncer 端口(将 kubeconfig 参数值替换为环境的相应全局 API kubeconfig 文件)。

    kubectl --kubeconfig=GLOBAL_API_KUBECONFIG apply -f - <<EOF
    apiVersion: networking.global.gdc.goog/v1
    kind: ProjectNetworkPolicy
    metadata:
      name: allow-pgbouncer
      namespace: ${PROJECT_ID}
    spec:
      ingress:
      - ports:
        - port: 6432
          protocol: TCP
      policyType: Ingress
      subject:
        subjectType: UserWorkload
    EOF
    

验证数据复制

如需确认高可用性堆栈是否按预期运行,您可以在领导者上创建示例数据,并验证其是否存在于副本上。

插入示例数据

如果在 inventory.ini 中未提供密码,则自动化会在首次部署期间为 postgres 用户生成随机密码。您可以从任何虚拟机中检索它:

export PG_PASSWORD=$(ansible master -i inventory.ini -m shell -a \
  "grep -A10 'authentication:' /etc/patroni/patroni.yml | \
  grep -A3 'superuser' | grep 'password:' | awk '{ print \$2 }'" -b | \
  tail -n 1)
echo $PG_PASSWORD

在 PgBouncer 端口 (6432) 上连接到负载均衡器 VIP,并创建一个示例表:

PGPASSWORD="${PG_PASSWORD}" psql -h ${LB_IP} -p 6432 -U postgres -c "
  CREATE TABLE employees (first_name TEXT, last_name TEXT);
  INSERT INTO employees (first_name, last_name) VALUES ('John', 'Doe');
"

预期输出:

INSERT 0 1

验证复制状态

在所有虚拟机上执行 SELECT 查询,以确保数据已从领导者复制到所有副本:

ansible postgres_cluster -i inventory.ini -m shell -a "psql -U postgres -c \
  'SELECT * FROM employees;'" -b

输出示例:

postgres-vm-1 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-2 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-3 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

测试手动切换

通过手动切换,您可以顺利地将主实例角色移至特定候选虚拟机。这通常是为了进行计划内维护、软件升级或平衡各可用区之间的资源利用率。

确定当前领导者

验证虚拟机的当前角色和状态:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

输出示例:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

执行切换

触发从当前领导者到另一个虚拟机的切换(在本例中,分别从 postgres-vm-1 切换到 postgres-vm-2)。该命令使用 --force 跳过手动确认提示:

ansible master -i inventory.ini -m shell -a "patronictl switchover \
  --leader ${VM1_NAME} --candidate ${VM2_NAME} --force" -b

输出示例:

Successfully switched over to "postgres-vm-2"
+ Cluster: postgres-cluster (7607933704953386478) -+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | stopped |    |     unknown |     |    unknown |     |
| postgres-vm-2 | 10.253.1.253 | Leader  | running |  1 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | running |  1 |   0/70000A0 |   0 |  0/70000A0 |   0 |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+

验证健康检查班次

切换后,验证健康检查状态是否已转移到新的主实例:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

旧领导者 (postgres-vm-1) 应返回 503,而新领导者 (postgres-vm-2) 应返回 200。

测试自动故障切换

与手动切换不同,自动故障切换会在主虚拟机不可用时发生。此测试可确认 Patroni 会选出新的领导者,并且负载均衡器会在无需人工干预的情况下重定向流量。假设 postgres-vm-2 是之前执行手动切换后的当前领导者。

确定当前领导者

验证虚拟机的当前角色和状态:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

输出示例:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
| postgres-vm-2 | 10.253.1.253 | Leader  | running   |  2 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

模拟虚拟机故障

停止主虚拟机上的 patroni 服务,以模拟崩溃或硬故障:

ansible master -i inventory.ini -m shell -a "systemctl stop patroni" -b

观察新选举

等待 10-20 秒,然后从另一个虚拟机检查状态,以查看新领导者的提升:

ansible replica -i inventory.ini -m shell -a "patronictl list" -b

输出示例:

postgres-vm-3 | CHANGED | rc=0 >>
+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  3 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | stopped   |    |     unknown |     |    unknown |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  3 |   0/90003F8 |   0 |  0/90003F8 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

您应该会看到其他虚拟机之一(postgres-vm-1 或 postgres-vm-3)已成为领导者,而旧领导者 (postgres-vm-2) 被标记为已停止。

验证健康检查班次

确认负载均衡器健康检查现在可以正确识别新选出的领导者:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

新的主节点应返回 200 OK。

恢复故障虚拟机

在原始虚拟机上重新启动 patroni 服务,使其作为副本重新加入堆栈并追赶任何缺失的数据:

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "systemctl start patroni" -b