本指南全面介绍了如何在 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,负载平衡器会将该 VIP 路由到当前主实例虚拟机上的 PgBouncer。然后,PgBouncer 会将请求代理到本地 PostgreSQL 实例。为了管理流量,负载平衡器会持续轮询 Patroni HTTPS 端点作为健康检查。
主实例虚拟机上的健康检查会返回 HTTP 200 OK,以表明虚拟机已准备好处理流量,而副本虚拟机上的健康检查会返回 HTTP 503 Service Unavailable,以表明负载均衡器应绕过它们。如果主实例失败,系统会选举出一个新的主实例,其 Patroni 实例开始返回 HTTP 200 OK,从而使负载均衡器自动将流量重定向到新虚拟机的 PgBouncer 端口。
为了确保高可用性并防止数据丢失,该堆栈依赖于法定人数的概念。对于 3 个虚拟机,系统需要至少两个成员处于健康状态并进行通信,才能选举出主实例并保持运行。这种基于多数的共识由 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 法定人数:平均往返时间 (RTT) 应小于 50 毫秒(理想情况下小于 10 毫秒),以防止选举超时和集群不稳定。
- 同步复制:如果已配置,则每个写入事务都必须等待副本的确认。在 GDC 网闸隔离配置中,可用区之间的延迟通常小于 1 毫秒,这非常适合将写入开销保持在最低水平(通常为 10-30%)。
- PgBouncer 的作用:PostgreSQL 为每个连接创建一个新的操作系统进程,这会消耗约 10MB 的 RAM 并产生 CPU 上下文切换费用。PgBouncer 通过维护持久连接池来减少此开销,从而使数据库能够以显著减少的后端进程处理数千个应用连接。
- Sidecar 组件: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+:执行部署剧本和角色的自动化引擎。
- Docker:用于在与目标虚拟机 (Ubuntu 22.04) 相同的环境中拉取和打包操作系统依赖项。
- PostgreSQL 客户端 (
psql):需要运行测试查询并从本地工作站验证数据复制。 Autobase 代码库:
- 克隆代码库以访问自动化剧本和角色: https://github.com/vitabaks/autobase
检出特定版本(本指南使用 2.5.2 版):
git checkout 2.5.2如果您使用其他发行版,本指南中的后续步骤可能会有所不同。
如需运行本指南中的剧本,您必须将本地
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
预配数据库基础架构
在虚拟机启动并配置清单后,您现在可以使用 Ansible 中的 Autobase 自动化剧本来部署高可用性 PostgreSQL 堆栈。
首先,运行预检检查以确保环境已准备就绪:
ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini \
--tags pre_checks
如果检查通过,请继续进行完整部署:
ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini
预期输出:剧本应完成,并显示成功的“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
验证部署
部署完成后,您应执行多项检查,以确保所有组件均正常运行。
检查高可用性状态
检查高可用性管理器的状态,以查看分配给每个虚拟机的角色:
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 正确识别主实例和副本。
主实例虚拟机的健康检查应返回 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参数值替换为每个可用区的相应管理 APIkubeconfig文件):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参数值替换为相应环境的全局 APIkubeconfig文件)。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