本节摘要:一份值班记录体的排错实录:一次例行的定时配置巡检引发的连锁告警,背后是两个相互纠缠的缺陷——shell 写法破坏幂等、清单重构导致变量未定义。实录还原从告警到根因的每一步推理,途中调用全书各章的机制知识。建议的读法:每一步先自问"换我怎么查",再看记录。
"星尘在线"团队的定时巡检:每晚对全部应用机跑一轮配置核对剧本,正常输出应当是 changed=0——巡检只检查不修改,任何非零都是漂移信号(这正是 5.2 节"漂移变监控"的日常形态)。某个周四早晨,巡检记录显示连续三个晚上 changed=42,恒定不变。当天上午,又有一台新上线的机器在人工执行发布剧本时报错退出。两件事看似无关。
按 4.4 节的排障流程走第一步:定位站点。巡检不是秒退、不是渲染错、不是连接问题——任务是执行了的,只是结果恒为 changed。那么问题在任务层:某个任务的"检查-比对"逻辑永远判定不一致。找出是哪个任务:给巡检运行加上 diff 输出(--check --diff 干跑),42 台机器上同一个任务全部显示同一段配置差异。
--- before: /etc/stardust/limits.conf +++ after @@ -3,4 +3,5 @@ appuser soft nofile 65535 appuser hard nofile 65535 +appuser soft core unlimited
差异指向一个新增的配置行:core 转储限制。追查变更历史,三天前合入了一个"崩溃转储调优"的提交,任务写法如下:
- name: 启用 core 转储限制 ansible.builtin.shell: "echo 'appuser soft core unlimited' >> /etc/security/limits.conf"
用 shell 追加——3.6 节反面教材的翻版。它为什么能通过评审与连续两天没被发现?复盘时找到了完整链条:该提交的作者本地测试跑的是一台新机器,追加一次后看起来"成功且符合预期";评审者看到的是一行注释完整的 shell 任务,幂等审问三问当时没有执行(后来补的流程);巡检剧本连续两天 changed=42 无人警觉,因为值班台把"巡检有变更"当成了"配置还在收敛"的正常现象——这正是幂等被破坏的隐藏代价:changed 信号失真后,真漂移与假漂移混在一起,信号系统失灵。
修复很直接(lineinfile 改写加幂等验证),但实录真正想记录的是修复后的第 4.4 节式追问:为什么连续两晚恒定 changed 没有触发告警?巡检的告警规则写的是"changed 大于零告警",而值班流程里告警的处置动作是"第二天看一眼"——流程上没有"连续恒定 changed 必须当天排查"的条款。巡检的规则与流程同步修订:恒定不变的非零值升级为当日响应。
第二件事接踵而来。上午十点,新上线的 stard-app-13 执行发布剧本,渲染配置任务报错:
TASK [渲染运行配置] ****************************************************************** fatal: [stard-app-13]: FAILED! => { "msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'app_port'" }
按 3.4 节的四病因排查法走。病因一,优先级误判?用 ansible-inventory --host stard-app-13 查看引擎合成的变量视图——输出里根本没有 app_port 这个键。不是优先级问题(低优先级也会出现在视图里),是这台机器的作用域里压根没人定义过它。病因四,拼写?全文搜索确认主机行、组变量、host_vars 三处都没有任何形式的 app_port——既不是拼错,也不是层级放错。结论收敛到病因之外的第五种:定义缺失。
追查定义本该在哪。对照清单目录,发现三天前的一次"清单规范化"提交把机器从旧的单文件清单迁到了分环境目录结构(3.1 节的布局),迁移脚本按机器名单生成 host_vars 骨架文件——而 stard-app-13 是迁移之后新加的机器,创建者按老习惯把连接参数写进了旧文件位置的机器行,应用参数则根本没人补。也就是说,前十二台机器的 app_port 住在各自 host_vars 里,第十三台无处可依。前十二台为什么一直正常?它们走的是"清单目录加载",第十三台的连接参数写在旧文件里恰好被新目录结构忽略——连接参数有默认值(默认用户、默认端口)兜底,所以 SSH 还能通;app_port 没有默认值,渲染时才炸。
修复分两层。即时层:给第十三台补 host_vars,发布重跑成功。结构层(更有价值的部分):给模板里的 app_port 加 default 过滤器兜底吗?不——团队讨论后决定不加,理由值得记录:app_port 属于"每台机器必须显式声明"的核心参数,静默兜底会把配置缺失从"发布时崩溃"变成"运行时用错端口",后者更难排查。真正的修复是防缺失:发布剧本开头加 assert 断言 app_port is defined 且类型合法(3.5 节的断言模式),把错误从渲染中段前移到剧本第一秒,报错信息从"渲染引擎的内部抱怨"变成"这台机器缺必要参数"。
两件事分开看各有解释,合起来看暴露同一个组织性根源:三天前的"清单规范化"与"配置调优"两个提交各自通过评审,但都绕过了"新流程落地期"的薄弱带——前者让新机器的变量来源出现断档,后者赶上了幂等审问流程尚未执行的时刻。第四幕的复盘会上,团队的结论落在两处制度:一是任何涉及清单结构的变更必须附带"新机器准入自检清单"(连接参数、业务参数、host_vars 骨架三步);二是幂等审问进入合并前强制卡点(lint 规则实现,shell 任务无 changed_when/creates 即红)。
把两幕的推理压缩成一棵可复用的决策树——它整合了 4.4 节的站点定位与 3.4 节的病因排查:
读实录的目的不是记住这两个具体故障,而是长出三条排错本能。本能一,恒定的 changed 是幂等被破坏的头号指纹——看到它先怀疑写法,再怀疑漂移。本能二,变量报错先查"有没有"再查"层级对不对"——视图里没有的键,谈优先级没有意义。本能三,两个"无关"异常同时出现时,找它们共同的时间邻域——变更历史上总有那个共同的提交。全书至此完成从概念到本能的最后一跳:第 1 章那次手忙脚乱的手工发布,与今天这份条理清晰的排错记录之间,隔着的正是中间五章建立的机制与纪律。愿你的自动化剧本,永远只在演练里失败。