ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PostgreSQL一主一从自动化部署脚本实战:从原理到落地

PostgreSQL一主一从自动化部署脚本实战:从原理到落地 PostgreSQL一主一从很多人愿意手动搭一条命令一条命令敲。我早期也是这么干的直到有次在客户环境里连续配了四台从库每台都要去翻 conf、改权限、重启验证中间还有一次因为 pg_hba.conf 里的 CIDR 写错从库连不上主库排查了半天。那之后我就决定把整套流程固化成一个自动化部署脚本在 Ubuntu 上跑通主库执行一遍、从库执行一遍十分钟内能看到流复制正常状态。这次就把这个“实战版”脚本的完整设计思路、核心原理和每一步的实现细节全部拆开讲清楚顺便把我踩过的坑一并放出来。1. 为什么需要“一主一从自动化部署脚本”项目背景与设计思路1.1 手动搭建一主一从的真实痛点PostgreSQL 的主从复制表面上看核心步骤无非是“主库开 WAL 归档、配复制用户、改 pg_hba从库做 basebackup、写 standby 配置、启动”。可真要在生产环境或测试环境里手动操作问题往往出在细节上。我自己遇到的就有这么几类一是参数遗漏。比如wal_level忘记从replica而不是默认的minimal或者max_wal_sender保留默认值 10一旦将来要挂多个从库就得回头改。这类参数在postgresql.conf里分散在不同位置手动编辑时容易漏。二是版本差异带来的命令变化。PostgreSQL 12 之后recovery.conf被废弃改为standby.signal文件加postgresql.conf内的参数如果你之前习惯老版本的玩法在新版本上手动操作就会卡壳。不同大版本之间pg_basebackup的参数细节也略有差异。三是重复操作极易出错。一主一从两台机器每台至少要执行二三十条命令。其中任何一条命令的 IP 写错、密码写错、目录权限不对都可能让你在排查上花掉比安装多得多的时间。四是缺少可复现性。手动搭建的环境很难保证两台机器配置完全一致。今天在这台机器上多配了一个参数明天在另一台上忘了后续遇到问题对比配置时非常痛苦。所以我在实践里做了这个自动化部署脚本目标很明确把一主一从的部署过程压缩成两个脚本一条命令跑完输出明确的成功或失败信息就算换一台新机器也能直接复用。1.2 自动化脚本的设计目标写脚本之前我先给自己定了几个原则这几点也建议所有做自动化部署的朋友参考幂等性同一个脚本在已经部署过的机器上再跑一次不能把已有配置搞坏。所以我用了“追加配置前先检查是否已存在”的方式避免重复写入postgresql.conf或pg_hba.conf。最小干预脚本只改动跟复制相关的必要配置不碰业务数据库的数据不重装系统自带的其他组件。可配置化IP、端口、版本号、复制用户、密码、数据目录这些都用变量定义在脚本头部改起来一目了然。明确反馈每个关键步骤执行完后用echo输出状态最后自动验证复制是否生效。不加这个验证脚本跑完你不知道成没成功。这套设计思路其实跟写普通自动化脚本是相通的。但 PostgreSQL 有它的特殊性它涉及系统服务管理、配置文件格式、数据目录权限、以及主从之间的网络连通性。所以在具体实现上需要特别处理的地方不少。下面从原理开始讲这部分理解了后面的脚本就能读得很顺。2. PostgreSQL主从复制核心原理与关键参数2.1 主从复制到底是怎么工作的很多人第一次接触 PostgreSQL 主从复制容易被“流复制”这个术语绕晕。我用一个尽量生活化的方式解释主库会把每一次数据变更记录到WALWrite-Ahead Logging日志里。这个日志你可以理解成一本“流水账”记录了所有修改数据页的操作。主库每写完一条 WAL 记录就会把它发送给连接的从库。从库接收到 WAL 后在自己的数据目录里“重放”这些操作从而让自己的数据跟主库保持一致。整个过程是持续的、实时的所以叫“流复制”。关键在于从库有两种运行状态hot standby热备从库在接收和重放 WAL 的同时允许只读查询。这个状态需要在postgresql.conf里开启hot_standby on。warm standby从库只接收 WAL 但不可查询类似一个等待中的备用节点。实际生产环境基本都会用 hot standby既能保证高可用又能分担一部分读流量。所以脚本里我默认开启hot_standby。从库建立的过程也不是说直接“连接然后开始收日志”就行。它需要先有一份跟主库一致的全量数据作为基础再从这个时间点开始追 WAL。这个“全量数据拷贝”的操作就是我们常说的pg_basebackup。它本质上是在主库上做一个在线基础备份并把备份期间产生的 WAL 一并传输给从库。2.2 六个必须理解的关键参数脚本里配置的参数并不复杂但每个参数都有它存在的理由。我按主库和从库分开说明主库postgresql.conf中的重点参数作用我的建议值listen_addresses允许 PostgreSQL 监听哪些 IP 地址设为机器实际 IP 或用*监听所有网卡wal_level决定 WAL 记录多少信息流复制必须设为replicaPG10或hot_standbyPG9.6 及更早replicamax_wal_senders允许同时有多少个 WAL 发送进程也就是最多多少台从库可以连接10wal_keep_size主库保留多少 WAL 日志用于尚未跟上进度的从库。PG13 之前叫wal_keep_segments注意版本差异1GB高负载可调到4GBhot_standby从库开启只读查询能力但从库配置也需要同步打开on从库的postgresql.conf或standby.signal相关设置参数/文件作用standby.signal该文件存在时PostgreSQL 启动后会自动进入 standby 恢复模式primary_conninfo从库连接主库所需的连接信息包括主库 IP、端口、复制用户hot_standby同样设为on才能接受只读查询primary_conninfo更推荐写入postgresql.conf的独立配置文件里因为它记录了敏感的主库连接信息。这里注意pg_basebackup加-R参数时会自动帮你在数据目录下生成standby.signal并写入primary_conninfo。这一点脚本里会利用到能省不少事。2.3 同步复制还是异步复制还有一个绕不开的选择同步 or 异步。异步复制主库提交事务不等待从库确认性能最好但主库崩溃时可能丢失少量最近提交的数据。同步复制主库必须等待至少一个从库确认收到 WAL 后才向客户端返回提交成功。数据零丢失但写入延迟会明显增加。脚本默认采用异步复制理由是一主一从架构里如果配置同步从库一旦宕机主库的写入就会被卡住。这在高可用场景下需要有额外机制来规避例如synchronous_commit配合synchronous_standby_names的降级策略对刚入门的朋友来说复杂度偏高。所以我先把异步复制作为默认并且在文档里注明如何切换。切换同步也很简单在主库postgresql.conf里加两行synchronous_commit on synchronous_standby_names standby1同时从库的primary_conninfo里要加application_namestandby1让主库能识别到这台从库。这部分脚本没有默认启用留给有需要的读者自行扩展。3. 自动化部署脚本完整实现与步骤解析3.1 前置准备与运行环境约定先说环境。我的脚本基于 Ubuntu 20.04/22.04/24.04 测试PostgreSQL 版本选用 14、15、16 都没问题。但有一件事必须做把 PGDG 官方软件源配好。Ubuntu 自带的 PostgreSQL 版本往往偏老而且不同 Ubuntu 版本自带的 PG 版本不同——这会导致主从两台机器版本不一致的风险。主从复制强烈建议大版本完全一致所以统一通过 PGDG 源安装指定版本。配置 PGDG 源的命令也很简单以 Ubuntu 22.04 和 PG16 为例sudo apt install -y curl ca-certificates sudo install -d /usr/share/postgresql-common/pgdg sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc sudo sh -c echo deb [signed-by/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt jammy-pgdg main /etc/apt/sources.list.d/pgdg.list sudo apt update注意把jammy换成你 Ubuntu 版本的代号22.04 是 jammy20.04 是 focal24.04 是 noble。这个细节很容易被人忽略。然后约定以下网络环境主库 IP192.168.1.10从库 IP192.168.1.20复制用户repl_user复制密码在脚本头部定义建议部署完成后修改一次PostgreSQL 大版本16数据目录Ubuntu 包管理安装默认是/var/lib/postgresql/16/main我把这些全部提取为脚本顶部的变量实际使用按环境替换即可。这也是前面说的“可配置化”原则。3.2 主库自动化部署脚本拆解主库脚本setup_pg_master.sh完整结构如下#!/bin/bash set -e # 配置区 MASTER_IP192.168.1.10 PG_VERSION16 REPL_USERrepl_user REPL_PASSWORDYour_Strong_Passwd_2024 DATA_DIR/var/lib/postgresql/${PG_VERSION}/main CONF_DIR/etc/postgresql/${PG_VERSION}/main CONF_EXTRA${CONF_DIR}/conf.d/replication.conf HBA_FILE${CONF_DIR}/pg_hba.conf # echo [1/6] 安装 PostgreSQL ${PG_VERSION} sudo apt install -y postgresql-${PG_VERSION} echo [2/6] 创建复制用户 sudo -u postgres psql -v ON_ERROR_STOP1 SQL DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname ${REPL_USER}) THEN CREATE ROLE ${REPL_USER} REPLICATION LOGIN PASSWORD ${REPL_PASSWORD}; END IF; END \$\$; SQL echo [3/6] 写入复制配置 if [ ! -f ${CONF_EXTRA} ]; then sudo tee ${CONF_EXTRA} /dev/null EOF listen_addresses ${MASTER_IP} wal_level replica max_wal_senders 10 wal_keep_size 1GB hot_standby on EOF else echo 已存在 ${CONF_EXTRA}跳过写入 fi echo [4/6] 写入 pg_hba 复制许可规则 if ! grep -q ${REPL_USER} ${HBA_FILE}; then echo host replication ${REPL_USER} 192.168.1.20/32 scram-sha-256 | sudo tee -a ${HBA_FILE} else echo pg_hba 已包含复制规则跳过 fi echo [5/6] 重启服务 sudo systemctl restart postgresql echo [6/6] 验证主库状态 sudo -u postgres psql -c SELECT pg_is_in_recovery();写这个脚本时有几个容易出问题的地方必须单独说明。第一复制用户用 DO 块做幂等创建。直接执行CREATE USER遇到已存在时会报错而set -e会让脚本当场退出。用pg_roles检查一次就不存在这个问题。这是我在脚本里特意加的“防御式”写法。第二pg_hba.conf的认证方式。PG15 之后默认password_encryption是scram-sha-256所以我在规则里明确写scram-sha-256。如果你在 PG14 及更早版本上沿用同样的规则需要确认主库的password_encryption是否兼容。为了避免旧版本不适配脚本可以做一个小改进把认证方式改成md5或保留scram-sha-256视版本而定。个人建议统一用scram-sha-256安全性和兼容性都更好。第三独立配置文件conf.d/replication.conf。Ubuntu 的 PostgreSQL 包会在postgresql.conf最后一行include_dir conf.d所以放进conf.d下的文件会自动加载。这样做的好处是我的复制配置跟发行版默认配置完全分离将来无论是升级还是回滚都干净。手动编辑postgresql.conf的坑在于——升级安装包时可能被覆盖或冲突而独立文件不会。第四listen_addresses写成具体 IP。如果写成localhost从库远程连不上如果写成*安全性略差。具体 IP 是折中方案。如果机器有多块网卡可以根据需要改成逗号分隔的列表。主库脚本执行完可以顺手确认一下pg_is_in_recovery()返回f表示主库没有处于恢复模式状态正确。3.3 从库自动化部署脚本拆解从库脚本setup_pg_standby.sh是整个流程里最容易“半路翻车”的部分#!/bin/bash set -e # 配置区 MASTER_IP192.168.1.10 STANDBY_IP192.168.1.20 PG_VERSION16 REPL_USERrepl_user REPL_PASSWORDYour_Strong_Passwd_2024 DATA_DIR/var/lib/postgresql/${PG_VERSION}/main CONF_DIR/etc/postgresql/${PG_VERSION}/main CONF_EXTRA${CONF_DIR}/conf.d/replication.conf # echo [1/7] 安装 PostgreSQL ${PG_VERSION} sudo apt install -y postgresql-${PG_VERSION} echo [2/7] 停止 PostgreSQL 服务 sudo systemctl stop postgresql echo [3/7] 清空数据目录 sudo rm -rf ${DATA_DIR} sudo install -d ${DATA_DIR} -o postgres -g postgres echo [4/7] 拉取主库基础备份 sudo -u postgres pg_basebackup -h ${MASTER_IP} -U ${REPL_USER} -p 5432 \ -D ${DATA_DIR} -P -R -X stream echo [5/7] 写入 standby 扩展配置 if [ ! -f ${CONF_EXTRA} ]; then sudo tee ${CONF_EXTRA} /dev/null EOF hot_standby on EOF else echo 已存在 ${CONF_EXTRA}跳过写入 fi echo [6/7] 启动从库 sudo systemctl start postgresql echo [7/7] 验证从库状态 sudo -u postgres psql -c SELECT pg_is_in_recovery();这个脚本里每一步都值得掰开讲。pg_basebackup是关键命令它的参数含义-h主库地址-U复制用户-D备份目标目录也就是从库的数据目录-P显示进度-R自动生成standby.signal并写入primary_conninfo到postgresql.auto.conf-X stream备份过程中产生的 WAL 用流复制方式传输而不是单独拷贝文件这里要特别强调-R这个参数。很多人手动配置从库时还在手动创建standby.signal、手动编辑primary_conninfo。其实pg_basebackup -R一条命令把这些全干了。自动生成的postgresql.auto.conf里会包含类似这样的内容primary_conninfo userrepl_user passwordxxx host192.168.1.10 port5432 sslmodeprefer唯一的问题是自动生成的primary_conninfo会带上明文密码。这从安全角度不太理想。所以生产环境建议部署完成后把密码改写成引用外部文件的方式或者用pg_ident/ 其他认证手段。但作为自动化部署脚本的默认行为明文密码换取的是部署速度是否进一步优化取决于你的安全要求。还有一个容易踩的坑从库数据目录的属主必须是postgres用户。脚本第 3 步我用install -d -o postgres -g postgres先建好空目录并设好属主避免因属主不对导致启动失败。如果你直接从/var/lib/postgresql/16/main里删掉数据后不处理属主后续pg_basebackup以postgres用户执行时可能因为目录所有者不对而报错。另外systemctl stop postgresql之后再执行备份本质上是做“离线后再在线”的备份——其实pg_basebackup不需要停库停库的目的是确保数据目录处于可清空状态。上面的脚本在删数据目录前停掉服务是安全的。从库启动后pg_is_in_recovery()应该返回t表示它已进入恢复模式正在追主库的 WAL。3.4 从主库侧验证复制状态光看从库说“我是 recovery 状态”还不够还必须回到主库侧确认一个关键指标WAL 发送进程是否真的建立。在主库执行SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;正常情况你会看到类似输出client_addr | state | sync_state | sent_lsn | replay_lsn ------------------------------------------------------- 192.168.1.20|streaming|async | 0/3000060 | 0/3000060其中state streaming表示流复制正在工作sync_state async表示异步复制。sent_lsn和replay_lsn如果保持一致说明从库已经追上主库的最新写入位置。这个验证步骤脚本里可以写成一条自动化的检查命令我这里把它放到脚本之外执行是因为有时需要人工观察两个 LSN 的变化情况判断复制是否在持续追着走。在部署脚本里我倾向于在最后输出一段提示信息告诉操作者“去主库跑pg_stat_replication确认状态”比脚本自动判断更稳妥。毕竟自动判断很难覆盖所有异常情况。4. 部署中的常见问题与排查技巧实录自动化脚本解决的是“重复劳动”的问题但脚本跑完之后你仍可能遇到一些需要人工介入的情况。这部分是我运维 PostgreSQL 过程里积累的真实排查经验整理成速查表供参考。4.1 “从库一直追不上主库”怎么办现象是pg_stat_replication里能看到state streaming但replay_lsn长期落后于sent_lsn。排查方向查看主库wal_keep_size是否太小。如果主库 WAL 产生速度快从库短暂离线后主库保留的 WAL 不够从库追上从库会直接从复制状态变成waiting然后需要重新做pg_basebackup。解决方法是加大wal_keep_size或者配置 WAL 归档到从库可达的地方。检查主库磁盘 IO。主库 WAL 写入速度是整个复制链路的上限如果主库本身 IO 压力大从库自然追不上。看从库 CPU 负载。从库重放 WAL 是 CPU 密集型操作大量索引更新时尤其明显。可以从库调大max_parallel_apply_workers_per_partition等参数但一般场景不需要过度调优。4.2 从库连接被拒pg_hba.conf 的细节坑连接被拒是最常见的错误之一日志里会出现FATAL: no pg_hba.conf entry for replication connection from host 192.168.1.20, user repl_user看到这个先去主库检查pg_hba.conf。重点看两点CIDR 是否正确、认证方式是否匹配。我踩过的坑是写成了192.168.1.0/24以为没问题结果发现主库和从库在同一个网段的其他 VLAN 里IP 根本对不上。正确做法是明确写/32的单机地址。另外如果主库 PostgreSQL 版本较老可能不支持scram-sha-256需要把规则改成md5。改完pg_hba.conf记得 reload 而不是 restartsudo systemctl reload postgresqlreload不会断开现有连接对生产环境更平滑。4.3 pg_basebackup 报错目录非空 / 权限不足执行pg_basebackup时报pg_basebackup: error: directory /var/lib/postgresql/16/main exists but is not empty原因很简单目标目录里有残留文件。脚本里我会先rm -rf再建但如果你手动操作时没有清理干净就会遇到。还有一类权限错误FATAL: could not create directory /var/lib/postgresql/16/main: Permission denied说明当前执行pg_basebackup的用户对目标目录没有写权限。Ubuntu 下建议始终用sudo -u postgres执行不要用root。4.4 从库启动后一直显示 “starting” 不进入 streaming这是一个比较隐蔽的情况。从库启动后pg_stat_replication里看不到该从库而从库日志里显示一直在恢复。可能原因primary_conninfo里的主机地址写的是主库的localhost而不是实际 IP。手动配置时容易犯这个错。因为从库自己回环地址连不上主库复制自然建立不起来。检查postgresql.auto.conf里的主机地址确保是主库的 IP。另一个可能主库max_wal_senders已经满。如果你之前挂过其他从库或残留的 WAL 发送进程没释放新的从库就进不来。可以查看SELECT * FROM pg_stat_replication;如果发现很多废弃连接可以在主库执行SELECT pg_terminate_backend(pid) FROM pg_stat_replication WHERE state startup AND pid pg_backend_pid();清理后从库会自动重连。4.5 PostgreSQL 版本差异速查不同大版本的参数或行为差异是排查问题时的另一大坑。我整理了一张速查表变更项PG13 及更早PG14PG15PG16WAL 保留参数wal_keep_segmentswal_keep_sizewal_keep_sizewal_keep_sizerecovery.conf存在移除移除移除默认密码加密md5md5可切scram-sha-256scram-sha-256scram-sha-256数据目录默认路径/var/lib/postgresql/13/main/var/lib/postgresql/14/main/var/lib/postgresql/15/main/var/lib/postgresql/16/main如果你把脚本从 PG14 迁移到 PG16注意变量PG_VERSION对应的数据目录路径也要跟着变脚本的DATA_DIR变量已经考虑到这一层。5. 自动化部署脚本的扩展用法一主多从与高可用虽然标题只提“一主一从”但实际把脚本的思路理清楚后扩展成“一主多从”几乎零成本。你只需要在从库脚本里重复执行“安装、停服、清空、basebackup、启动”这几步每台新从库都能通过同一个脚本拉起。唯一的前提是主库的max_wal_senders要大于从库数量。如果是三台从库把max_wal_senders改成10就完全够用。不要把数字改得太大因为每个 WAL sender 进程都会占用主库的内存和文件描述符。10对绝大多数场景都充足。如果你要进一步做高可用比如主库故障自动切换那就不是单纯部署脚本的范畴了。你会用到repmgr或 Patroni 这类工具。它们本质上也是在帮你做主从配置、监控、自动 promote。到那个阶段本文提到的基础复制原理和参数理解仍然是地基不会白学。6. 脚本落地后的三条个人经验总结关于这套部署脚本有几个经验值得单独写出来也是我在实际项目中反复验证过的第一永远不要让脚本静默执行所有步骤。我见过很多自动化脚本所有输出都用/dev/null 21吞掉美其名曰“干净”。真出问题时你连在哪一步挂的都不知道。我自己的脚本里保留了足够多的关键输出甚至最后还会弹出一段“下一步操作提示”。自动化是为了省力不是为了制造黑盒。第二双节点环境里务必记录“当前哪个是从库”。这个听起来像废话但我真遇到过——某次主库磁盘损坏需要立刻 promote 从库结果几个人排查了半天才确认哪台是从库。建议在你的部署脚本里把主从 IP、复制用户、部署日期写成一份DEPLOY_INFO文件放在两台机器上。这不是什么高深操作但关键时刻能救命。第三部署完成后马上做一次“切换演练”。脚本自动部署完成后别着急把业务切上去。手动把主库停掉让从库 promote 成主库看看业务是否恢复正常再把这个“新主库”重新配置成从库把架构拉回一主一从。这个演练能提前暴露很多配置问题而且演练完你对这套系统的掌控力会完全不同。很多人觉得这步骤麻烦但多节点数据库最怕的就是“没演练过切换”。我在实际使用这套脚本时还会在从库部署完成后立刻在主库创建一个测试表insert 几条数据然后到从库查询确认数据同步。这个过程虽然手动做也不复杂但把它纳入部署脚本的收尾环节能在第一时间确认复制链路整体可用比只看pg_is_in_recovery()可靠得多。PostgreSQL 的主从复制搭建本身不难但把它做成一键完成、可复制、可验证的脚本才是真正提升运维效率的地方。希望这份 Ubuntu 实战脚本和背后的原理拆解能让你在下次搭 PostgreSQL 主从时少踩几个坑。
返回列表