Ansible playbook 声明想要的状态,而不是步骤
Ansible 的 playbook 记录的是机器应该拥有的状态。部署中途崩溃后,下一次运行会先观察真实现状,只补上缺少的部分,而不是从头盲目重放每条命令。它是一份可以执行的契约:inventory 决定对象,模块实现比对,操作者审阅即将发生的变化。 只写终点,不写每一步 命令式 shell 脚本会说“按顺序执行这些命令”;playbook 则说“这个包应该已安装、这个文件应该是这些字节、这个服务应该启用并运行”。模块在动作前比较目标主机与目标状态,已经满足的任务报告 ok,只有不一致才报告 changed。同一份声明可以用于全新的机器、只配置了一半的机器,也可以用于被人手工改过的机器。 这里的目标不能只是模糊意图。state: present、模板的精确内容、所有者和权限、服务的 enabled/running 都是契约的一部分。把多台机器共用的值放到变量或 role 没问题,但最终值应该对审阅者可见。隐藏的默认值,或者绕过模块直接改变状态的命令,都会让这份契约难以推理。每一个 task 都应回答“运行完成后应该看到什么”,而不是“我习惯先敲哪一条命令”。 模块让重跑逐渐收敛 多数内置模块都围绕幂等性设计。ansible.builtin.copy 会先比较文件内容和元数据,只有不同才替换;ansible.builtin.package 向包管理器询问目标状态;ansible.builtin.service 只在启用状态或运行状态不符时调整。第二次运行应该是一份安静的确认,而不是再次重启、追加重复行或再写一条记录。幂等不是“什么都不做”,而是“已经达到目标,所以没有必要再做”。 并非所有动作天然幂等。直接用 command 创建用户、轮换密钥或调用外部 API,若没有条件限制就会重复副作用。优先找专用模块;没有时先 register 查询结果,再用 when 判断是否执行,或者使用精确的 creates/removes 文件条件。changed_when 只决定变更报告和 handler 通知,不控制任务是否执行;设成 false 不能阻止重复变更,还可能隐瞒真实副作用。对外部接口仍要定义超时、重试与部分成功后的恢复方法,不能把网络调用假装成普通文件复制。 Inventory 是地图 playbook 描述结果,inventory 选择主机,变量提供上下文。ansible_user、SSH agent 或 Vault 可以提供凭据而不把秘密写进仓库。组变量让同一份声明覆盖整支机器队伍,同时保留端口、区域等主机差异。在 CI 中使用显式 inventory,避免“localhost”意外指向执行器本身。执行前打印解析后的主机集合,确认别名、组继承和变量优先级都与审阅时一致。 ...