ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostgreSQL一主一从自动化部署:流复制与pg_basebackup脚本实战

PostgreSQL一主一从自动化部署:流复制与pg_basebackup脚本实战 1. 为什么一主一从的部署必须写成自动化脚本1.1 手工部署在生产环境踩过的坑我最早搭 PostgreSQL 主从的时候走的完全是手工流程主库安装、配置文件逐项修改、建复制账号、改 pg_hba.conf然后到从库上执行 pg_basebackup 拉基础备份再配置 recovery 参数、启动实例。整套流程下来熟练的话也要四十分钟到一个小时。乍看时间还能接受但问题是这样的操作在高压力环境下非常容易出错而且错了之后排查要花更久。举几个我真实踩过的例子主库的wal_level没改成replica基础备份倒是拉过去了结果从库启动后一直报FATAL: could not receive data from WAL stream排查才发现主库 wal_level 还是默认的replica看着没错实际是 Ubuntu 源里 PostgreSQL 默认已经是 replica 了但我用的是源码编译的版本默认是minimal。pg_hba.conf 里只给复制账号配了replication权限但没放通主从节点之间互相访问的网段导致从库连不上主库。两台机器系统版本不一致一台 Ubuntu 20.04一台 22.04apt 默认拉的 PostgreSQL 小版本不同主从大版本一致但小版本有差异pg_basebackup 执行时能过但后续复制过程中不断出现WAL segment has been removed之类的告警。这些问题的共同点是单步执行时都感觉没问题但组合在一起就出乱子。手工部署对操作者要求非常高你得把每一条参数、每一个顺序都背清楚否则就会像解谜一样在报错日志里打转。1.2 自动化脚本要解决的三个核心问题我写这套一主一从自动化部署脚本目标非常明确就是解决三个问题第一保证部署一致性。不管部署多少次只要环境满足前置条件脚本跑出来的配置、参数、目录结构、权限设置必须完全一致。生产环境最怕的就是两台机器配置有细微差异复制链路跑着跑着就断。第二节省操作时间。手动操作四十分钟到一个小时脚本跑起来五分钟以内完成而且不需要人一直盯着终端等每一步的输出。时间不仅仅是效率问题还能减少操作窗口期引入的误操作风险。第三可复用、可传承。脚本写好后放到团队的部署仓库里新同事照着 README 跑一遍就能搭好环境不需要把老师傅脑袋里的经验一点点挖出来。实际上很多团队的基础设施能力弱恰恰是因为关键操作都留在个人脑子里没有沉淀成自动化脚本。1.3 这套脚本适用的场景边界需要注意的是自动化脚本不代表万能。我这个脚本解决的是一主一从、异步流复制、Ubuntu 系统这个特定场景。它不包含高可用自动切换比如 Patroni 或 repmgr 那套也不包含多级级联复制更不包含读写分离中间件。如果你的需求是“数据库不能挂挂了要自动切换”那需要的是 Patroni、etcd/Consul 配合的方案不是一主一从脚本能搞定的。如果只是想快速搭一套带备库的 PostgreSQL 环境用于测试、开发、小规模生产那么这套脚本非常合适。我反复强调场景边界是因为我在不少技术群里看到有人拿部署脚本当高可用方案用最后出故障时发现从库无法自动提升于是把锅甩给“自动化部署不好用”这其实是用错了工具。2. 部署前必须确认的四件事2.1 版本选型Ubuntu 源、官方仓库还是源码编译这是热搜里出现频率极高的问题也是很多人第一步就卡住的地方。我的建议是如果只是常规部署直接用 PostgreSQL 官方 APT 仓库不要用 Ubuntu 自带的源尤其不要用源码编译。Ubuntu 自带源里的 PostgreSQL 版本通常比较保守比如 Ubuntu 22.04 自带的还是 14。官方 APT 仓库可以装到当前主流的 16/17 版本。源码编译只适合有特殊需求的情况比如要打自定义 patch、要改内核特性、要装到没有包管理器的环境。否则源码编译引入的依赖问题、编译参数问题、升级维护问题会给你后续运维挖一堆坑。安装官方仓库的方式很简单在 Ubuntu 上执行sudo apt install -y curl ca-certificates sudo install -d /usr/share/postgresql-common/pgdg curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg echo deb [signed-by/etc/apt/trusted.gpg.d/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main | sudo tee /etc/apt/sources.list.d/pgdg.list sudo apt update然后安装指定版本sudo apt install -y postgresql-16 postgresql-client-16这里有个细节官方仓库安装完成后PostgreSQL 会自动创建一个名为postgres的系统用户cluster 名默认是main数据目录在/etc/postgresql/16/main和/var/lib/postgresql/16/main。这个路径比源码编译时的/usr/local/pgsql/data要规范得多systemd 管理也方便强烈推荐。2.2 网络规划与防火墙放通一主一从之间需要打通两个网络能力主库 5432 端口要能从从库访问这是复制连接用的端口。从库要能访问主库的 5432 端口执行 pg_basebackup。如果两台机器之间有防火墙ufw必须提前放通。如果你用的是云服务器安全组规则也需要提前确认。我曾经就在云环境里遇到过安全组只放通了办公网 IP结果从库跑到一半连接被切断pg_basebackup 失败后残留的半成品目录还需要手动清理。放通防火墙的操作很简单sudo ufw allow from 从库IP to any port 5432 proto tcp sudo ufw allow from 主库IP to any port 5432 proto tcp但要注意放通之后 PostgreSQL 默认只监听 localhostlisten_addresses参数默认是localhost必须改成实际需要监听的地址通常改成/0然后交给防火墙去过滤或者直接写具体 IP。2.3 系统用户与数据目录的设计PostgreSQL 在 Ubuntu 上通过 apt 安装后会自动创建postgres系统用户这是运行数据库服务的专用账号日常管理也用这个账号切换执行 psql。数据目录我建议保持官方默认不要为了“看起来规整”去把数据目录改到自定义路径。原因很简单默认路径下pg_ctlcluster、pg_lsclusters 等工具能直接识别升级备份都方便。如果确实要自定义路径比如数据盘单独挂载那脚本里需要额外处理目录权限和 systemd 单元的 override。一个容易被忽略的坑是postgres用户的 home 目录是/var/lib/postgresql如果脚本用su - postgres -c ...执行命令注意工作目录变化可能会影响相对路径的解析。我脚本里所有涉及 postgres 用户的操作都显式指定了绝对路径或者先 cd 到数据目录。2.4 复制方案选型流复制为什么是默认选择PostgreSQL 的主从复制主要有两个大方向基于 WAL 文件拷贝的日志传送和基于 WAL 流式传输的流复制。两者对比如下对比项基于文件的日志传送流复制实时性有延迟取决于 WAL 归档频率实时性高主库产生 WAL 即推送到备库配置复杂度需要配置 archive_mode、archive_command配置 wal_level、max_wal_senders、primary_conninfo对从库的基础备份可以用 pg_basebackup也可以手动拷贝通常用 pg_basebackup逻辑上一致支持同步复制通过配置可做但实时性差原生支持 synchronous_commit在现代 PostgreSQL 版本中流复制是绝对的主流选择配置简洁、实时性好、社区资料多。脚本里我也只做流复制不涉及 archive_command 的配置这样最开始部署的基础架构最干净后续有需要再叠加归档。3. 自动化脚本核心实现拆解3.1 脚本整体结构入口、参数、执行阶段我设计脚本的思路是写一个主入口脚本传不同参数区分主库和从库通过 SSH 在两台机器之间协作完成整个部署。简化后的目录结构如下pg_repl_deploy/ ├── deploy_pg.sh # 主入口脚本 ├── conf/ │ └── pg_repl.conf # 部署参数配置 └── scripts/ ├── setup_primary.sh # 主库部署子脚本 ├── setup_replica.sh # 从库部署子脚本 └── check_replication.sh # 复制状态自检脚本主入口脚本的核心逻辑非常简单用 case 语句区分动作#!/usr/bin/env bash # 一主一从自动化部署入口脚本 set -euo pipefail source $(dirname $0)/conf/pg_repl.conf ACTION${1:-usage} case $ACTION in primary) ssh -o StrictHostKeyCheckingno $PRIMARY_HOST \ sudo bash -s scripts/setup_primary.sh ;; replica) ssh -o StrictHostKeyCheckingno $REPLICA_HOST \ sudo bash -s scripts/setup_replica.sh ;; check) ssh -o StrictHostKeyCheckingno $PRIMARY_HOST \ sudo -u postgres psql -c \SELECT client_addr, state, sync_state FROM pg_stat_replication;\ ;; *) echo Usage: $0 {primary|replica|check} exit 1 ;; esac这里有几个设计心得第一统一走 SSH 远程执行。你可以在自己的工作机上跑脚本不用登录到服务器上再执行部署记录也更清晰。第二用sudo bash -s接收 stdin 脚本。这样脚本内容不需要落地到远程服务器避免脚本文件泄露或者被篡改。配合StrictHostKeyCheckingno是为了首次连接不交互但生产环境建议提前把 SSH key 分发好再关掉这个选项否则有中间人风险。第三set -euo pipefail是必须的。set -e让脚本在出错时立即退出set -u避免变量未定义pipefail确保管道中任意命令失败都算失败。没有这三行脚本往往会在出错后继续执行最后你拿到一个看似成功其实是残废的状态。3.2 主库参数自动落地的实现方式setup_primary.sh 里最核心的部分是配置文件修改。我用的不是直接覆盖整个 postgresql.conf而是用 PostgreSQL 原生的ALTER SYSTEM命令来设置参数。这个方法比直接改文件更安全参数会写入postgresql.auto.conf重启后自动生效而且不会因为改错了语法把整个文件搞坏。# 设置主库关键参数 sudo -u postgres psql -c ALTER SYSTEM SET wal_level replica; sudo -u postgres psql -c ALTER SYSTEM SET max_wal_senders 10; sudo -u postgres psql -c ALTER SYSTEM SET wal_keep_size 1GB; sudo -u postgres psql -c ALTER SYSTEM SET listen_addresses *; # 重启 PostgreSQL 使参数生效 sudo systemctl restart postgresql16-main参数说明wal_level replica是流复制的最低要求。max_wal_senders 10是允许同时存在的 WAL 发送进程数一主一从场景 10 足够了如果你后续要加级联从库或者做备份适当调大。wal_keep_size 1GB表示主库保留最近的 1GB WAL 文件防止从库短暂断连后需要从归档恢复。注意 PostgreSQL 15 之前用的是wal_keep_segments15 之后改成了基于大小的wal_keep_size不同版本容易搞混。还有一个参数容易被忽略synchronous_commit。如果只想做异步复制保持默认on就可以虽然名字叫 synchronous但它只有在配置了 sync standby 时才会同步等待没有配置的情况下仍然是异步的。所以默认值不用改。3.3 pg_hba.conf 的自动改写配置好参数后还需要让从库能够以复制身份连到主库。这需要修改 pg_hba.conf 添加一条规则。修改 pg_hba.conf 和修改 postgresql.conf 不一样ALTER SYSTEM管不了它只能直接编辑文件。我脚本里是这样处理的PG_HBA/etc/postgresql/16/main/pg_hba.conf REPLICA_IP${REPLICA_IP:-192.168.1.10/32} PRIMARY_USER${PRIMARY_USER:-repl_user} # 在文件末尾追加复制规则的注释标记方便后续识别和清理 grep -q managed by deploy_pg $PG_HBA || cat $PG_HBA EOF # --- managed by deploy_pg --- host replication ${PRIMARY_USER} ${REPLICA_IP} md5 EOF sudo systemctl reload postgresql16-main这里用reload而不是restart因为 pg_hba.conf 的修改是热加载的不需要重启数据库。这也是很多新手容易犯的错——改完 pg_hba.conf 就 restart白白制造连接中断窗口。复制账号的创建也在这个阶段完成sudo -u postgres psql SQL DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname ${PRIMARY_USER}) THEN CREATE ROLE ${PRIMARY_USER} WITH LOGIN REPLICATION PASSWORD ${PRIMARY_PASSWORD}; END IF; END \$\$; SQLREPLICATION权限是复制的关键它允许该账号建立复制连接和运行 pg_basebackup。注意这里密码是明文写在脚本里的所以脚本文件本身的权限必须收紧建议chmod 600并且部署完以后把密码统一托管到公司的密钥系统或环境变量里。3.4 从库初始化pg_basebackup 的正确用法从库脚本的核心是 pg_basebackup。这一步我从初学到现在用了几十次总结下来有几个铁律必须在从库 PostgreSQL 服务停止的状态下执行或者在执行前清掉从库已有数据目录否则会冲突报错。从库数据目录必须为空或者不存在。如果从库也已经安装并初始化过 cluster需要先pg_dropcluster或者手动清目录。我的从库脚本流程是# 1. 停止从库服务如果存在 sudo systemctl stop postgresql16-main || true # 2. 清理已有数据目录 sudo rm -rf /var/lib/postgresql/16/main sudo mkdir -p /var/lib/postgresql/16/main sudo chown postgres:postgres /var/lib/postgresql/16/main sudo chmod 700 /var/lib/postgresql/16/main # 3. 以 postgres 用户执行 pg_basebackup sudo -u postgres pg_basebackup -h $PRIMARY_HOST -D /var/lib/postgresql/16/main \ -U $PRIMARY_USER -v -P --wal-methodstream \ -R -C -S pg_replica_1 # 4. 启动从库 sudo systemctl start postgresql16-main几个关键参数--wal-methodstream表示在基础备份过程中同时通过流复制接收 WAL这样备份出来的实例和主库 LSN 基本一致不需要额外处理 WAL 归档文件。如果不加这个参数默认是收集pg_wal目录下已有的 WAL 文件可能因为主库 WAL 已经推进导致备份不一致。-R表示自动生成standby.signal文件和postgresql.auto.conf中的primary_conninfo这是从库能够连接主库的关键。-C表示在备份过程中自动创建复制槽槽名为-S指定的pg_replica_1。复制槽的意义在于防止主库清理掉从库还没接收的 WAL尤其在网络抖动频繁的环境下非常有用。启动后从库目录里会多一个standby.signal文件这是 PostgreSQL 进入备库模式的特征。注意从 PostgreSQL 12 开始不再使用recovery.conf而是用standby.signal很多老教程还在教建 recovery.conf那个在新版本里已经不生效了。3.5 从库启动与状态自检逻辑部署完成后不能直接收工必须在脚本里加上自检逻辑。check_replication.sh 脚本会在主库上执行SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;正常的一主一从异步复制状态应该是字段期望值statestreamingsync_stateasyncsent_lsn与主库当前 LSN 接近或相同replay_lsn接近 sent_lsn如果 state 是startup说明从库还在追赶 WAL通常几十秒内会变成streaming如果一直停在catchup或报错就需要检查网络和连接配置。自检脚本还可以在从库上执行一个简单的读写一致性测试# 主库写入一条测试数据 sudo -u postgres psql -c CREATE TABLE IF NOT EXISTS repl_test(id int primary key, ts timestamptz default now()); sudo -u postgres psql -c INSERT INTO repl_test(id) VALUES (1) ON CONFLICT (id) DO UPDATE SET ts now(); # 等待 2 秒后从库查询 ssh $REPLICA_HOST sudo -u postgres psql -c SELECT * FROM repl_test WHERE id 1;注意从库是只读的只能执行 SELECT如果从库上能查到主库刚写入的数据说明复制链路是通的并且数据一致性没问题。4. 真实部署过程实录与排障4.1 一个完整的部署执行记录这里我记录一次在 Ubuntu 22.04 上部署 PostgreSQL 16 一主一从的真实过程。假设主库 IP 是 10.0.0.10从库 IP 是 10.0.0.11复制账号是 repl_user。配置文件的初始内容# conf/pg_repl.conf PRIMARY_HOST10.0.0.10 REPLICA_HOST10.0.0.11 PRIMARY_USERrepl_user PRIMARY_PASSWORDS0meSecurePass PG_VERSION16然后在工作机上依次执行./deploy_pg.sh primary ./deploy_pg.sh replica ./deploy_pg.sh check主库执行过程中setup_primary.sh 的输出大概是这样[INFO] 配置 PostgreSQL 主库参数... [INFO] 创建复制账号 repl_user... [INFO] 更新 pg_hba.conf... [INFO] 重启主库服务... [INFO] 主库部署完成。从库执行过程则多了一步 pg_basebackup 的进度输出。我加上的是-P参数所以能看到百分比进度从 0% 到 100%整个备份过程取决于数据量大小几 GB 的库一般在几十秒内完成。4.2 验证阶段遇到的一个连接失败问题有一次执行到./deploy_pg.sh check时主库的pg_stat_replication查出来是空表也就是从库根本没连上来。从库日志里反复报FATAL: could not connect to the primary server: Connection refused这个报错第一反应是主库没监听或防火墙没开。我先在主库上确认端口监听sudo ss -lntp | grep 5432发现监听是正常的地址是0.0.0.0:5432。接着我在从库上手动测试到主库的连接pg_isready -h 10.0.0.10 -p 5432结果是能通的说明 TCP 层没问题。然后我换了一个角度——猜测是 pg_hba.conf 的规则顺序问题。PostgreSQL 的 pg_hba.conf 是从上往下匹配第一条匹配到就生效后面的规则不会再看。我追加的规则在文件末尾但文件上方已经有一条拒绝来自所有地址访问的host all all 0.0.0.0/0 reject之类的规则新规则永远匹配不到。解决方法有两个一是把复制规则插到文件顶部二是在追加规则前先检查并去掉不合理的拒绝规则。最稳妥的做法是在追加规则之前先备份原文件然后把除了本地连接以外的规则全部注释掉只保留自己需要的放通规则。生产环境不建议随便保留一堆规则规则越多越容易撞车。4.3 关于复制槽的一个认知误区前面提到我用-C -S pg_replica_1创建了复制槽。复制槽的好处是防止主库 WAL 被过早清理但如果从库长时间断线主库的 WAL 会不断堆积把磁盘撑爆。这就是复制槽的代价。我在一个测试环境里故意把从库停机半天再启动后主库的pg_wal目录暴涨了几个 GB。虽然 PostgreSQL 在max_slot_wal_keep_size参数上有限制但默认值依赖wal_keep_size的设置如果用户不看文档很难意识到这个问题。所以在脚本里我额外加了sudo -u postgres psql -c ALTER SYSTEM SET max_slot_wal_keep_size 2GB;如果没有这个限制复制槽可以让主库 WAL 无限增长。max_slot_wal_keep_size设成 2GB 的含义是当复制槽导致的 WAL 保留量超过 2GB 时主库可以丢弃被复用槽标记为需要的 WAL从库此时会进入需要重新基础备份的状态。这个参数是 PostgreSQL 13 引入的旧版本没有所以不同版本的处理方式完全不同。4.4 从库只读测试的完整路径部署完成后我习惯这么验证一轮主库建表、插入测试数据。从库查询该表。从库尝试 INSERT确认报错只读。第三步很容易理解从库的transaction_read_only仍然是on任何写操作都会报错比如postgres# INSERT INTO repl_test(id) VALUES (2); ERROR: cannot execute INSERT in a read-only transaction看到这个报错不是坏事它说明从库确实处于 Standby 模式复制是健康的。很多时候用户会误以为从库不能写就是“出了问题”实际上恰恰是工作正常的表现。5. 脚本之外日常运维与扩展方向5.1 主从状态监控的日常手段部署脚本只是第一步真正的考验在运维。我每天上班第一件事是看一眼复制状态sudo -u postgres psql -c SELECT client_addr, state, sync_state, replay_lsn - sent_lsn AS lag_bytes FROM pg_stat_replication;如果 lag_bytes 持续走高就要考虑主库是否在跑大批量写入或者从库机器性能是否跟不上。除了手动查询建议配合定时任务做监控。我后来在一台监控机上放了这样一个 crontab 条目*/5 * * * * /opt/scripts/check_repl.sh /dev/null 21check_repl.sh 内部执行复制状态检查如果 state 不是 streaming 就发告警。告警通道用的是企业微信机器人和钉钉机器人根据团队的沟通工具来定。5.2 安全方面必须做好的三件事第一复制账号密码不能写死在 git 里。脚本和配置分离只是第一步配置里的密码需要替换成环境变量引用或者部署时从密钥管理服务拉取。我自己的脚本里虽然用了明文密码但那是单机测试环境的妥协方案生产环境绝对不能这样。第二pg_hba.conf 只放通必要地址。不要写host replication repl_user 0.0.0.0/0 md5尤其不要为了方便调试把整个网段全放通。只放通从库 IP 或从库所在安全组对应的网段。第三SSH 免密要小心。脚本依赖工作机到两台服务器的 SSH 免密这本身是一个风险面。如果工作机被攻破攻击者可以直接控制数据库服务器。建议单独建立一个只读用户用于执行部署脚本不要直接复用 root 或 sudo 权限超管账号。5.3 从一主一从到更多拓扑的扩展思路这套脚本虽然只解决一主一从但改造起来可以逐步扩展级联复制只需要把从库 2 的primary_conninfo指向从库 1 而不是主库从库 1 也要设置max_wal_senders和wal_level。同步复制在主库设置synchronous_standby_names pg_replica_1然后重启主库sync_state 会变成sync此时主库提交事务时必须等待从库确认数据安全性更高但写入延迟会增加。自动故障切换在脚本基础上叠加 Patroni把一主一从变成高可用集群。要注意的是 Patroni 有自己的一套配置体系和原生流复制的配置不完全兼容需要额外做适配。我自己的经验是一主一从脚本跑顺了之后先把监控和告警做起来再考虑自动切换。没有监控就谈高可用是空中楼阁数据库挂了没人知道自动切换也白搭。6. 结尾部署脚本之外的一点个人体会整套脚本从最初的手工操作提炼到自动化前后迭代了大概两个礼拜。最大的体会是自动化不是把命令抄进脚本就完事而是要把每一步的原理理解透之后再决定哪些要写成脚本、哪些必须交给人工确认。比如pg_basebackup -R -C -S这几个参数如果你不理解 standby.signal 的意义和复制槽的副作用写进脚本只是埋雷。反过来理清原理后写出来的脚本哪怕几年后再拿出来也能一眼看出每一条命令在干什么。最后分享一个我常用的收尾动作部署完成后在主库和从库的/etc/postgresql/16/main/下各放一个DEPLOY_INFO文件记录部署时间、脚本版本、主机 IP、PG 版本。这个文件平时没人看但出故障排查时它能帮你快速确认环境基线省掉大量“这个环境到底是不是脚本部署的”之类的猜测时间。
返回列表