本节摘要:无代理(Agentless)、声明式(Declarative)、幂等性(Idempotency)是理解 Ansible 的三个支点概念。本节把它们放回 2012 年前后的配置管理工具竞争格局中,讲清每个概念是对什么痛点的回应,以及它们如何共同构成"以状态为中心"的运维范式。
"把运维动作写成剧本"——在 Ansible 之前,社区已经在做类似的事,但各家的路线差别很大。要理解 Ansible 为什么选了现在的形态,得先看它的两位前辈各自卡在了哪里。这不是历史课的怀旧,而是因为这三条路线至今并存,选型时的很多争论本质上还是当年那几个架构分歧的延续。
第一代主流是 Puppet、Chef 代表的代理式(Agent-based)架构:被管理机器上常驻一个客户端进程,周期性向中心服务器拉取配置。优点是持续收敛、不依赖连接始终在线;代价是代理本身的安装、升级、排障成了一项独立工程——版本碎片化、代理失联、证书过期,这些"管理管理工具的负担"消耗了大量精力。第二代以 Fabric 为代表,走向另一个极端:纯 SSH 推送,灵活轻巧,但它是命令式的——脚本描述"怎么做",执行一遍完事,跑两次就可能出两个不同的结果。
Ansible 在两条路线的夹缝里给出的答案,是三个概念的组合。
受管机器上不装常驻客户端,唯一的前提是 SSH 可达、Python 可用(Linux 上几乎总是成立)。控制节点通过 SSH 把模块代码临时推送过去执行,执行完即删。这个决定直接把"上线一个新配置管理工具"的成本从数周压缩到数小时:不需要审批常驻进程的安全合规,不需要规划代理的升级窗口,不需要处理代理与中心服务器的证书信任链。
代价也要说清楚。无代理意味着没有常驻连接,也就没有真正的"持续收敛"——Puppet 的代理每半小时自动拉取一次配置,漂移会被周期性纠正;Ansible 默认只在有人运行剧本时才工作,两次运行之间的漂移它管不着。工程上的补偿手段是定时任务加差异检查:用计划任务周期性执行带检查模式的剧本,把"持续收敛"重建在调度层。理解这一点很重要,因为它决定了 Ansible 在"长期 desired state"与"按需变更"两种哲学里的位置——它天然更适合后者。
命令式脚本说"执行 yum install nginx",声明式剧本说"nginx 这个包应当处于已安装状态"。两者的差别在第一次执行时看不出来,在第二次、第一百次执行时天差地别:命令式每次都会跑一遍安装命令(包管理器会帮你兜底,但文件写入、服务重启这类动作不会),声明式先检查目标状态是否已经满足,满足就什么都不做。
# 命令式的思路:做一件事 # shell: yum install -y nginx && systemctl restart nginx # 声明式的思路:描述一个状态 - name: 确保 nginx 已安装且在运行 ansible.builtin.yum: name: nginx state: present - name: 确保 nginx 服务处于运行状态 ansible.builtin.service: name: nginx state: started enabled: true
上面的例子里有细节值得咀嚼:state: present 与 state: started 是两个独立的状态声明,包的存在性和服务的运行性分开表达,这正是"状态"思维的体现——运维关心的从来不是"重启服务"这个动作,而是"服务在跑"这个事实。
幂等性借自数学与函数式编程:一个操作执行一次与执行多次,效果相同。对运维而言它意味着一件极实用的事——剧本可以放心重跑。发布中途失败了?修好问题从头再跑,已经完成的任务会被跳过。网络抖动导致一半机器没执行到?再跑一遍,只补齐落下的那部分。这个性质把"失败恢复"从精细的断点续传设计简化成"再跑一次"。
# 同一个剧本连续运行两次,第二次的输出 PLAY [web 服务器配置] ********************************************************* TASK [确保 nginx 已安装] ****************************************************** ok: [web-01] # 第二次运行:状态已满足,标记 ok 而不是 changed TASK [确保 nginx 服务运行] **************************************************** ok: [web-01] PLAY RECAP ******************************************************************** web-01 : ok=2 changed=0 unreachable=0 failed=0
changed=0 这行就是幂等性的直观体现。反过来说,凡是出现"每次跑都 changed"的任务,都值得怀疑:要么写法破坏了幂等(比如用 shell 模块做了模块该做的事),要么目标状态真的在漂移(比如有别的进程在改配置)。第 3.6 节会专门解剖这类问题。
这三个概念不是一夜之间凑齐的。2012 年 Michael DeHaan 发布首版时,Ansible 的卖点只有无代理加 YAML 可读性;2015 年随 Red Hat 收购进入企业视野;2019 年前后的 2.8、2.9 版本完成模块命名空间规范化;2020 年分拆为 ansible-core 与集合(Collections)体系,内容与引擎解耦;此后 Ansible Automation Platform 把执行器、调度、权限管理打包成企业产品。时间线上每一步都值得单独回看,细节留在第 7.1 节展开,这里只需要带走一个判断:Ansible 的演进方向一直是"把易用性从个人语法层下沉到组织流程层"。
下图把三个概念放到竞争格局里对照,帮助建立空间感:

初学者最常见的混淆,是把"无代理"理解成"什么都不需要装"。受管节点仍然需要 Python 解释器(2.6 及以上版本,取决于模块)和 sudo 权限体系;网络设备这类没有 Python 的目标则要走另一种连接方式。另一个高频误区是把声明式当成"不用写逻辑"——条件、循环、失败处理都在,只是它们组织在"状态收敛"的框架之内,而不是过程式脚本的框架之内。
把三个概念合起来复述一遍:无代理解决"推得过去",声明式解决"说得清楚",幂等性解决"跑得放心"。下一节我们看这三个概念落到真实场景里,分别兑换成哪些具体能力,以及哪些场合它们兑换不出来。
三者的依赖关系也值得留意:无代理是通道前提,声明式是表达框架,幂等性是前两者的质量保障——没有幂等的声明式只是"长得像声明的脚本",没有声明式的幂等则退化为手工比对。三个支点缺一,整个范式的收益都会打折,这也是后面所有章节反复回指它们的原因。