ARTICLE DETAIL

资讯详情

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

PostgreSQL安全版流复制故障排查:从WAL告警到证书过期

PostgreSQL安全版流复制故障排查:从WAL告警到证书过期 生产环境里最怕的不是故障本身而是故障发生在“看起来不该出问题”的地方。半夜两点被值班电话叫醒说主库到备库的流复制断了快三个小时主库WAL目录已经把磁盘撑到了90%再拖下去整个业务都得停。我登录上去看了一眼复制状态确实是down。这个“安全版数据库”是我们在等保整改时做过全面加固的版本当时所有操作都按最小权限、强制认证、白名单访问的标准来做的结果恰恰是这个安全版环境让流复制问题比普通环境复杂了好几倍。这篇内容就把这次排障的完整过程写下来包括流复制原理、安全加固环境里的特殊坑、逐层排查步骤以及日常怎么避免同类问题给同样维护加固环境的朋友一个参考。1. 故障现场与整体排查思路1.1 第一眼看到的现象先交代一下环境。主备都是PostgreSQL 14部署在独立的内网区操作系统是CentOS 7数据库本身套了一层比较严的加固策略pg_hba.conf里只允许特定网段的IP访问认证方式强制scram-sha-256SSL也要求开启复制账号用的是单独创建的专用账号权限只给了replication。这套配置上线运行了小半年一直挺稳直到这次出问题。我登录主库先查复制状态pg_stat_replication里一个active备库都没有一条记录都查不到。再到备库去看日志里全是连接主库失败的记录报错信息倒是很明确但当时我还没意识到这个报错背后会牵出一串环境问题。主库侧的WAL文件在pg_wal目录里越堆越多因为备库不在线旧的WAL段没法被回收磁盘眼看着就要爆了。这里先提醒一句不管是什么数据库流复制断掉之后第一优先级永远是防止主库磁盘被WAL撑爆。如果主库磁盘真的满了主库会直接拒绝写入业务马上就挂到那时就不是复制故障而是生产事故了。所以登录后的第一件事是先确认磁盘水位必要时可以手动清理已经确认不需要的WAL段但清理之前一定要确认备库不是断线恢复的临界状态否则会把备库需要的WAL也删掉导致备库彻底重建。1.2 先从逻辑上给故障分个类流复制断连的原因总结起来其实就是四类网络层不通、认证层被拒、配置层不对、数据层冲突。这四类问题在普通环境里都比较直观但在安全版环境里每一层都有可能被加固策略放大。我自己的排查习惯是先不急着翻配置文件而是想清楚故障“像”哪一类。比如这次备库日志里明确出现了认证相关的字眼那第一反应应该是认证层但我不可能只看一条日志就下结论因为安全版环境里网络访问控制、SSL握手失败、密码算法不兼容最终表现出来的日志都可能长得差不多都会在备库侧记成“无法连接主库”或“连接被拒绝”。所以我的办法是把这四类问题按“成本从低到高”的顺序排一遍先测网络连通性再确认认证策略然后核对复制参数最后再看数据目录和恢复状态。这样每一步都有明确的结论不会在原地打转。后面几节就说清楚我实际是怎么一层层查下去的每一层发现了什么最后又是怎么解决的。1.3 安全版数据库的“特殊背景”先说清楚很多做运维的朋友一说起安全版就头疼因为加固策略通常只考虑“最小权限”和“最大限制”很少会替流复制这种长连接机制着想。比如安全基线里经常要求数据库账号密码定期更换、要求ssl连接强制开启、要求所有连接必须来源固定网段这些策略单拎出来都没问题但合在一起就很容易搞出组合拳式的故障。另外安全版数据库在安装时往往会关掉一些默认开启的扩展或者限制了超级用户的登录方式。之前就碰到过一个环境安装时为了安全把超级用户登录限制成了“仅本地socket”结果主备之间的流复制复制账号在远程登录时总是被拒排查了半天才发现不是密码错而是登录方式本身被限制死了。这类细节普通环境下根本遇不到但在安全版环境里就是常见的坑。2. 流复制原理与安全版环境的关键差异2.1 流复制到底在复制什么要排查流复制底层原理还是要有一点底子。PostgreSQL流复制的本质就是主库持续产生的WAL日志Write-Ahead Logging预写日志被传送到备库备库拿到WAL之后在自己的数据目录里重放从而实现数据同步。主库这边有wal sender进程负责把WAL段投递给备库备库那边有wal receiver进程负责接收并写入到备库本地的WAL文件里再通过startup进程做恢复。整个过程是“推”的模式主库主动往外推备库被动接收。主备之间通过primary_conninfo参数建立连接这个连接本质上就是一条普通的PostgreSQL连接只不过它走的是replication协议。连接建立之后备库会告诉主库“我已经收到了哪个LSN位置”主库就从那个位置开始继续发送WAL。所以流复制对网络的要求很高不仅是连通性带宽和时延也会直接影响复制效率和断连概率。时延过大时主库发送WAL的进程会一直等待备库确认累积到一定程度就可能触发超时中断。这里补一个生活化类比流复制就像两个人接力传文件主库是发件人备库是收件人WAL就是每一页文件。发件人每发出一页都要等收件人说一声“收到了”才继续发下一页。如果中间传话的人网络出问题或者收件人突然说自己不认识发件人认证失败整个接力就卡住了。2.2 安全加固环境比普通环境多出来的变数普通环境排查流复制无非就是看配置、看网络、看权限。安全版环境里同样的环节会多出好几个变数。第一是防火墙。安全版环境往往在数据库服务器和业务服务器之间还要串一道专门的防火墙策略端口只开5432而且可能只对特定IP开放。这本身没问题但有些加固基线会把数据库端口限定为“仅业务网段可访问”库与库之间的复制流量反而被忽略了。这次故障环境就是类似情况复制端口策略被改过导致主备之间的复制连接在防火墙这一层就被拦掉了。第二是SSL强制策略。安全版要求数据库连接必须使用SSL流复制连接同样也得走SSL。如果主库端的ssl参数配置有问题比如证书路径不对、证书过期、协议版本不兼容备库连接时就会在SSL握手阶段失败。这类报错不会直接说“ssl error”而是会表现为连接超时或协议错误挺迷惑人的。第三是认证方式。普通环境用md5甚至trust的都见过安全版基本强制scram-sha-256。主备之间的复制连接如果password_encryption参数不匹配或者密码存储格式不兼容备库就会一直报认证失败。更隐蔽的是安全版常常会通过role属性来控制谁能登录如果复制账号被误加上了“不能登录”的属性或者被禁用了也会导致同样的失败现象。第四是审计策略。安全版环境普遍开启了审计日志这些日志量特别大有时候一天能写几个GB。如果审计日志和数据库数据放在同一块磁盘上日志暴涨会挤占磁盘空间WAL目录跟着遭殃。这个虽然不是直接导致流复制断开的根因但会放大故障影响让问题变得更难收拾。2.3 为什么安全版配置更容易“悄悄”出问题还有一个更隐蔽的问题安全版环境里的很多配置是通过配置管理工具统一下发、统一覆盖的。比如用某个自动化平台批量管理数据库参数每次下发基线配置时会把数据库参数表整体刷新一遍。个别人写的模板里没有包含max_wal_senders、wal_keep_size这些复制相关参数下发完之后复制参数就被重置回默认值但重启数据库时又不会报错等到备库积压到一定程度才开始断连。这种故障的排查难度特别大因为它从日志上看就是普通的复制中断不看配置变更记录根本想不到是参数被静默重置了。我在这次排障里就专门检查了配置下发时间点和复制断连时间点发现两者非常接近这才确认了方向。所以后面在做变更时凡是涉及安全加固参数调整的我都会把复制相关参数单独列一个白名单确保自动化下发时不会被覆盖掉。2.4 复制关系里的“主备视角不对称”问题排查流复制时最容易犯的一个错误是只看主库视角。主库侧只能看到自己有没有在向备库发送WAL看不到备库为什么没接收备库侧能看到自己尝试连接主库的过程但看不到主库拒绝时的完整原因。两边日志如果不放在一起看很容易各说各话抓不到真正的交集点。我这次的做法是在主库和备库同时开一个日志追踪窗口先从备库的postgresql.log看连接尝试再到主库的日志里找对应的连接记录。如果主库日志里根本没有来自备库IP的连接记录说明请求根本没到达主库问题在网络层或防火墙层如果主库有连接记录但在认证环节打了拒绝那问题在认证如果认证过了但复制启动失败再往下看。这种“对日志”的做法在整个排障过程中帮了大忙效率比盲目重启备库高得多。3. 逐步排查一次完整的流复制故障处理记录3.1 先看日志定方向备库日志是我最先打开的东西。在备库上执行tail -n 200 $PGDATA/log/postgresql.log这里$PGDATA是数据目录的路径。日志里能看到很多条重复的连接失败记录关键的几行是备库尝试连接主库的IP和端口然后紧接着报错。报错内容明确提到了密码验证失败。当时我以为就是密码过期或者密码被改动了但登录主库查了一下复制账号的密码状态发现密码是正常的。这时候我意识到问题可能不在密码本身而在认证方式上。顺便说一个查日志的小技巧安全版数据库的日志通常做了轮转老的日志会被归档。排查时要确认你看的是当前时段的日志而不是归档里的旧记录。我习惯先执行ls -lht $PGDATA/log/按时间排一下找到最新的那个日志文件再做tail。3.2 检查网络连通性和安全策略日志里虽然报的是认证失败我还是先按“成本从低到高”的原则把网络层过了一遍。在备库上用psql直接测试主库端口psql host主库IP port5432 userrepl_user dbnamepostgres connect_timeout5能连上是第一步。如果这一步都连不上那就是网络或防火墙问题。我测试时发现psql连接没问题能正常走到密码验证阶段说明网络层是通的。接着我用nc测试了复制端口在特定源地址下的连通情况确认防火墙策略没有把复制流量拦掉。这一步有个很容易被忽略的细节安全版环境的防火墙可能是“白名单制”即只放行已登记的IP和端口。虽然psql测试能通但如果复制连接源IP和测试IP不一致结果就不一样。所以测试时最好把备库的真实IP作为源地址来测而不是在备库本机上测试。我最保险的做法是在备库上执行curl -v telnet://主库IP:5432或者用ncnc -vz -w 5 主库IP 5432如果这两个都通那网络层基本可以排除。3.3 检查流复制配置项网络层没问题接着看配置。在主库上执行show max_wal_senders; show wal_keep_size; show max_replication_slots; show synchronous_standby_names;max_wal_senders决定主库最多能启动多少个wal sender进程如果这个值太小备库连接时可能分不到sender进程表现为连接超时或直接拒绝。wal_keep_size是主库保留WAL段的数量备库断线太久后需要从更早的位置开始追如果主库已经回收了那个位置的WAL备库就算重新连上也追不上只能重建。synchronous_standby_names如果配了名字而备库的名字不匹配同步复制模式下备库连上也不会被识别为同步备库。我在这次排查中确认max_wal_senders是16wal_keep_size是1GB都没有问题。然后我到备库检查primary_conninfo参数cat $PGDATA/postgresql.auto.conf这个文件里保存了pg_basebackup设置的primary_conninfo内容包括host、port、user、password等。我发现连接信息里的user名称和主库实际创建的复制账号一致port也没错。但这时的日志还是报认证失败。3.4 账户权限和认证方式认证失败的重点就在pg_hba.conf。我在主库上查看了这个文件里面关于replication的配置长这样hostssl replication repl_user 备库IP/32 scram-sha-256单看这一行理论上没问题但注意到有个“hostssl”前缀说明这条规则只匹配SSL连接。如果备库发起连接时没有走SSL这条规则就匹配不上会被后面的规则拦掉或者落到默认拒绝规则上。备库的primary_conninfo里我没有看到明显的sslmode参数默认的PostgreSQL客户端行为是“prefer”也就是优先尝试SSL但如果主库SSL配置有问题客户端会回退到非SSL从而错过hostssl规则。再往下一查主库的ssl配置文件postgresql.conf里ssl是on但ssl_cert_file和ssl_key_file路径指向的证书文件已经过期了。这就是一个典型的“安全加固反向坑”强制SSL理论上更安全但证书过期后不仅业务连接会受影响流复制连接同样被卡住。普通环境没那么讲究证书过期可能不会被发现但安全版环境里你一旦强制ssl证书生命周期管理就变成了必须日常维护的事项。解决办法分两步。第一步先临时把备库的认证方式调整为同时兼容SSL和非SSL在pg_hba.conf里增加一行host replication条目让备库先用非SSL连上恢复复制。第二步是更新主库SSL证书并把scope配置回强制SSL。这样既保证业务连续性又不牺牲安全要求。3.5 数据目录和恢复状态认证问题处理完之后流复制连接恢复了但备库并没有马上进入正常的恢复状态。我在备库日志里看到恢复进程仍然报错内容指向时间线timeline不一致。流复制有一个时间线的概念可以让数据恢复到任意时间点。主备在运行时各自的时间线ID必须一致如果备库是从旧备份恢复的或者主库发生过提升failover备库的时间线和主库就对不上了。这种错位在普通环境中通常不会出现但在安全版环境里因为运维加固会定期做备份和恢复演练很容易留下一个时间线不一致的备库。我确认了备库的standby.signal文件存在这是PostgreSQL 12之后标识备库的方式然后对比了主备的当前时间线在主库执行select timeline_id from pg_control_checkpoint();在备库执行同样的查询发现备库的时间线确实落后于主库。这说明备库在断连期间主库发生过至少一次时间线切换或者备库曾经被错误地以恢复模式启动过。时间线不一致的处理比较简单就是重新做一次基础备份让备库从新的时间线开始追。我用pg_basebackup重新同步了备库pg_basebackup -h 主库IP -p 5432 -U repl_user -D /var/lib/pgsql/14/data --wal-methodstream -R-R参数会生成standby.signal文件和primary_conninfo配置属于最省事的做法。重新同步之后备库日志开始出现“consistent recovery state reached”复制恢复正常pg_stat_replication也能查到备库记录了。4. 常见问题与排查技巧速查4.1 典型报错与对应解决方法这里的表格可以大幅缩短你在故障现场“猜原因”的时间。下面这些报错文本是我在实际维护中经常碰到的同一类文本背后可能是完全不同的原因我按概率从高到低排列报错现象常见原因快速排查方法password authentication failed密码不匹配、认证方式不兼容在主库确认密码检查pg_hba.conf认证类型could not connect to server: Connection refused端口未监听、防火墙拦截用nc/telnet测端口检查listen_addressesFATAL: no pg_hba.conf entry for replicationpg_hba.conf缺少replication规则查看pg_hba.conf是否包含host replication条目requested WAL segment has already been removedwal_keep_size太小、备库落后太多确认主库WAL保留量必要时重建备库timeline mismatch主备时间线不一致对比pg_control_checkpoint()重建备库could not receive data from WAL stream: SSL errorSSL证书过期或协议不兼容检查主库ssl_cert_file和ssl_key_filehot standby is not enabled备库未开启hot_standby确认standby.signal存在且hot_standbyon笑着补一句很多人在现场看了“password authentication failed”就去改密码结果改完还是报错最后发现是pg_hba.conf里写的是“scram-sha-256”而密码存储格式还是md5。记住一点PostgreSQL的认证方式和密码存储格式是两码事认证方式用来验证密码密码本身也要按对应格式存储。不安全的环境里最好统一用scram-sha-256同时确认password_encryption参数也是scram-sha-256。4.2 几个实际踩坑经验第一个坑主备时钟不一致。流复制本身不严格要求主备时钟同步但安全版环境里通常会用ntp做时钟同步。如果备库时钟比主库慢很多备库在连接时发送的时间戳会让主库认为连接已过期直接拒绝。我碰到过备库时钟慢了十几分钟连接总是断排查到最后发现是ntp服务停了。所以别忘了先date查看两边时间。第二个坑复制账号密码被安全策略定期轮换。安全版环境会定期要求改密但如果只改了业务账号没同步更新主库上复制账号的密码而备库primary_conninfo里存的是旧密码断连就来了。修改复制账号密码后一定要记得同步更新备库postgresql.auto.conf里的password字段并且重启备库使配置生效。第三个坑cron任务里日志清理脚本误删WAL。安全版环境普遍有日志清理需求有人会写一个find命令定时删文件路径写错就会把pg_wal目录里的WAL当普通日志删掉。备库一旦落后主库发现WAL文件被删直接断连这个故障主库日志不会报错只是备库追不上。我看到这种情况时整个人是懵的最后查了cron脚本和文件系统改动记录才定位到。第四个坑主库和备库间的网络设备做了TCP连接超时限制。有些防火墙或负载均衡设备默认会切断长时间空闲的TCP连接流复制如果是异步模式备库可能很长时间不发送确认包连接被中间设备静默断掉。表现为复制短暂断开又自动重连反复出现。解决办法是开启tcp_keepalives_idle等参数在主库的postgresql.conf里做相应配置让连接保活。4.3 工具与小技巧排查流复制时我最常用的几个命令记在这里可以直接抄作业。在主库查看复制状态select client_addr, state, sent_lsn, replay_lsn, write_lsn, flush_lsn, sync_state from pg_stat_replication;state是streaming说明备库正在正常接收sent_lsn和replay_lsn的差距代表主备数据差了多少差距越大说明备库越落后。sync_state如果是async就是异步复制如果是sync就是同步复制。安全版环境如果要求强一致性一般会配成sync但同步复制对网络时延更敏感断连风险也更高这又是个安全与业务连续性之间的取舍问题。在备库查看接收状态select status, receive_start_lsn, received_lsn, latest_end_lsn from pg_stat_wal_receiver;备库还可以观察恢复进度select pg_is_in_recovery(); select pg_last_wal_replay_lsn();如果pg_is_in_recovery返回true说明备库还在恢复模式。如果备库日志里出现“consistent recovery state reached”说明备库已经能启动查询了hot standby生效。还有一个查询可以看主库WAL目录的增长情况du -sh $PGDATA/pg_wal这个命令是判断磁盘风险最直接的方式。如果WAL目录体积异常增大而又没有新的备库请求那主备链路八成已经出问题了。4.4 日志没有报错但复制仍然断开的情况有一类特别恶心的故障主备日志都没有明显报错pg_stat_replication偶尔能看到备库但复制就是时断时续。这种情况我通常是往资源竞争上想。备库在做恢复重放时如果也承担了很大的查询负载磁盘IO和CPU被大量占用wal receiver进程饿死连接会表现为超时或卡死。安全版环境如果还同时开着审计日志、定时备份、安全扫描等任务资源竞争问题更加严重。一个排查思路是看备库的IO等待iostat -x 5如果wa指标偏高或者磁盘util长期在90%以上那大概率是备库资源瓶颈。解决办法有几种把备库的查询负载分流到其他只读节点、错开备份时间、给备库提升磁盘IO能力。这个可能在“安全版”语境下不容易直接想到但确实是最常见的隐藏坑。5. 防止再次翻车的运维建议5.1 给流复制链路建立独立的监控项不要等到页面告警了才发现流复制断了。我现在的习惯是每分钟跑一次监控脚本检查三件事备库是否处于recovery模式、pg_stat_replication里是否能查到备库记录、主备的LSN差距是否超过阈值。如果连续两次查询都查不到备库记录立即告警。下面这个简单的监控SQL可以直接做成定时任务select count(*) from pg_stat_replication where state streaming;返回值是0的时候就说明没有备库在正常复制。另外还可以对WAL目录大小做监控du -sb $PGDATA/pg_wal超过一定大小就触发告警。这两个监控项配合起来基本能在磁盘被撑满之前发现链路问题。5.2 把安全加固项与复制参数放进变更评审安全版环境的变更管理特别关键。我建议所有加固相关的变更无论是改pg_hba.conf、调整ssl参数还是统一下发基线配置都要有一个专门针对复制的复查环节。具体的做法是变更之前先记录当前复制状态变更之后立刻核对复制状态是否正常如果出现异常马上回滚。这个道理跟改电路一样改之前先拍照改完再对一遍照片哪里对不上就说明哪里改出了问题。普通环境也许能靠“事后修”安全版环境的操作面大、审计严格最好把“变更前后对比复制状态”写进标准操作流程里。5.3 定期做切换演练和重建演练安全版数据库的流复制配置不是配置完就一劳永逸的。证书会过期密码会轮换时间线会因为failover而改变这些都会让备库渐渐失去作用。定期做一次主备切换演练能提前暴露这些问题而不是等到生产故障时才发现备库根本不可用。我强烈建议每季度至少做一次“备库重建演练”用pg_basebackup从主库重新拉一份备库数据验证整个过程是否顺畅验证监控告警是否正常触发再验证备库是否能正常升主。这个演练成本不高但在关键时刻能救命。5.4 安全策略与业务连续性取平衡最后想聊一点观点。安全版环境的初衷是好的但安全策略不能变成“一禁了之”。强制SSL、强制scram认证、最小权限这些措施都必须配套相应的生命周期管理流程。一个不能登录的复制账号、一份过期的证书、一条被防火墙误杀的策略看起来都是小事叠加在一起就是一次大半夜的紧急故障。如果你也在维护安全版数据库建议把这几个点纳入日常巡检SSL证书有效期、复制账号密码轮换后的同步情况、防火墙策略变更记录、定时清理脚本对WAL目录的影响。这些点都盯着了流复制出问题的概率会大幅下降。6. 这次排障留给我的几个体会事后复盘时我一直在想为什么这个问题拖了这么长时间才定位到根因。最直接的原因是证书过期这个变量它在日志里被“认证失败”四个字掩盖了。如果没有把pg_hba.conf、主库日志、备库日志、证书有效期这几项放在一起看恐怕还要折腾更久。我现在做任何数据库链路排查都会先建立一个“时间线意识”什么时间做了变更、什么时间开始报错、什么时间备库状态发生变化把几个时间点连起来往往能快速缩小范围。这个习惯帮我解决了不少疑难问题建议你也试试。另外安全版环境的运维光会写SQL和看日志是不够的还得懂安全策略背后的意图。只有知道了每一项加固措施为什么要存在你才能在排查时反向推导出“哪个策略可能导致这个故障”。这不是一朝一夕的功夫但每排查一个问题就是一次积累。最后再分享一个小技巧如果主备之间有专职的网络运维人员建议在流复制排障时拉上他们一起看。很多时候所谓的数据库故障根子其实在网络设备上比如防火墙会话超时、负载均衡器强制断开长连接。我在一次故障里折腾了一个多小时也没结果网络同事过来看了一会儿直接指出防火墙策略问题两分钟解决。数据库上下游的协作比单兵作战有效得多。
返回列表