配置管理:Ansible 与不可变基础设施的取舍

配置管理的两条路线之争:Ansible/SaltStack 的收敛式配置管理与不可变基础设施的镜像化部署如何取舍。讲清 Ansible 核心模型、幂等与收敛的语义、Playbook 与 Role 的组织方式、大规模编排、状态漂移检测,以及两类方案的混合策略与迁移路径。

服务器配置到底是「装好之后持续收敛」还是「每次都从镜像重建」?这是配置管理领域最根本的路线之争。Ansible 与 SaltStack 代表收敛式路线,容器与镜像烘焙代表不可变路线。本文不站队,而是把两条路线的适用边界讲清楚,并给出 Ansible 的核心模型、幂等语义、Role 组织与混合策略的落地做法。


目录


1. 配置管理的两条路线

1.1 收敛式:让机器持续趋向目标状态

收敛式配置管理(Ansible、SaltStack、Puppet、Chef)的核心是声明目标状态,由工具反复执行直到机器达到并保持该状态。机器是「活的」,配置会随时间漂移,工具负责把它拉回来。

1.2 不可变式:让机器一次性成形

不可变基础设施(镜像烘焙、容器、NixOS)的核心是「构建一个完整的机器镜像,部署时直接替换」。机器一旦启动就不再修改,要变更就重建一个新版本再切换。

维度收敛式不可变式
变更方式就地修改替换重建
状态来源工具持续收敛镜像即真相
漂移风险高,需持续巡检低,重启即复原
回滚方式反向收敛,易失败切回旧镜像,秒级
启动速度快慢(需构建镜像)
适合场景长生命周期主机、有状态服务无状态服务、弹性伸缩

1.3 不是非此即彼

现实中很少二选一。常见做法是:无状态服务用不可变镜像,有状态服务与数据库主机用收敛式管理,边缘设备与长生命周期主机两者结合。选型的关键是问「这台机器的生命周期和变更频率」。

2. Ansible 核心模型

2.1 无代理与推送式

Ansible 最大的特点是 Agentless:被管理节点不需要安装常驻代理,通过 SSH(Linux)或 WinRM(Windows)推送模块执行。这降低了纳管成本,也让它成为跨异构环境的首选。

2.2 模块、任务、Play

三个层级:模块(module)是执行单元,任务是模块的调用,Play 是一组任务的有序集合,Playbook 是 Play 的列表。所有编排都是这三个概念的组合。

- name: configure web servers
  hosts: webservers
  become: true
  tasks:
    - name: ensure nginx installed
      ansible.builtin.package:
        name: nginx
        state: present
    - name: deploy config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: reload nginx
  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

2.3 Inventory 与变量优先级

Inventory 描述主机分组,变量决定每台机器的具体配置。Ansible 的变量有十多个优先级层级,理解优先级是避免「变量被意外覆盖」的关键。

2.4 执行模式与并发

Ansible 默认按 inventory 顺序执行,可用 forks 提高并发。滚动更新场景要用 serial 控制批次,配合 max_fail_percentage 在失败过多时中止,避免一次性把整个集群改坏。

- hosts: webservers
  serial: 2                # 每批 2 台
  max_fail_percentage: 25  # 失败超过 25% 则中止
  tasks:
    - name: deploy config
      ansible.builtin.template:
        src: app.conf.j2
        dest: /etc/app/app.conf

3. 幂等与收敛

3.1 幂等的定义

幂等指「执行一次和执行多次的结果相同」。幂等是 Ansible 的核心契约:第二次运行一个已满足状态的 playbook,所有任务都应报告 ok 而非 changed。

3.2 changed 与 ok 的语义

Ansible 用三种状态报告结果:ok(状态已满足,未改动)、changed(本次做了改动)、failed(执行失败)。收敛性可以从 changed 的数量观察:稳定后 changed 应趋近于零。

收敛过程观察
  首次运行  :changed 数量多,机器从初始态向目标态靠拢
  再次运行  :changed 趋近 0,说明已收敛
  若持续 changed :说明存在非幂等任务或外部因素在改配置

3.3 破坏幂等的常见写法

  • 用 shell 命令代替专用模块(如用 command: useradd 而非 user 模块)。
  • 无条件追加内容(lineinfile 不指定 regexp)。
  • 每次生成带时间戳的文件名。

4. Playbook 与 Role 组织

4.1 用 Role 做复用单元

Role 是 Ansible 的复用与封装单元,把任务、模板、变量、handler 打包成可被多个 playbook 引用的组件。

roles/
  nginx/
    tasks/main.yml       # 任务入口
    handlers/main.yml    # 处理器
    templates/           # Jinja2 模板
    defaults/main.yml    # 默认变量(可覆盖)
    vars/main.yml        # 高优先级变量
    meta/main.yml        # 依赖声明

4.2 defaults 与 vars 的区别

defaults 是低优先级的默认值,期望被外部覆盖;vars 是高优先级,用于 role 内部的固定值。搞反了会导致「变量覆盖不生效」的经典困惑。

4.3 分层组织

大型项目按「角色分层」组织 playbook:基础层(用户、时区、安全基线)、中间层(运行时、监控代理)、应用层(具体业务)。层与层之间用 tags 分离,支持只跑某一层。

组织方式适合规模特点
单 playbook小规模简单,但难复用
Role 拆分中等规模可复用、可组合
Collection大规模/跨团队可分发、带版本
多仓库 + 依赖超大规模需配套的依赖管理

5. SaltStack 与大规模编排

5.1 SaltStack 的模型

SaltStack 采用 Master-Minion 架构,Minion 常驻在被管理节点上,通过消息总线接收指令。相比 Ansible 的推送式,它在超大规模(数千节点)下的并发与响应更有优势。

5.2 Ansible 与 SaltStack 对比

维度AnsibleSaltStack
架构无代理、推送有代理、消息总线
语言YAML + Jinja2YAML + Jinja2 + Python
规模数百到数千数千到数万
实时性拉取式,按需执行事件驱动,可实时响应
学习曲线平缓稍陡
典型用途通用配置与部署大规模状态管理

5.3 选型建议

中小规模、异构环境、希望零代理,选 Ansible。数万节点、需要实时事件响应与集中状态管理,选 SaltStack。二者都支持声明式状态,理念相通,迁移成本主要在语法细节。

5.4 状态与事实采集

两个工具都支持采集被管理节点的事实(facts/grains):操作系统版本、内存、网络接口、已装包等。事实是编写条件化配置的基础,也是做资产盘点的数据来源。定期把事实汇总入库,就能得到一份动态的资产清单。

6. 不可变基础设施

6.1 镜像烘焙

不可变基础设施把配置前置到镜像构建阶段:用 Packer、Dockerfile 或 Nix 把操作系统、运行时、应用一次性打包成镜像,部署时只做替换,不再就地修改。

不可变流水线
  1. 构建镜像(Packer/Dockerfile/Nix)
  2. 对镜像做安全扫描与合规校验
  3. 推送镜像到制品仓库
  4. 部署新镜像实例,加入负载均衡
  5. 摘除并销毁旧实例

6.2 为什么回滚变简单

不可变的核心收益是回滚可靠:旧镜像还在仓库里,回滚就是「切回旧版本再拉起来」,不需要反向执行一堆配置步骤。收敛式的回滚则要「撤销之前的修改」,往往不可靠。

6.3 不可变也有代价

  • 构建时间:每次变更都要重新构建镜像,比就地改配置慢。
  • 状态处理:有状态服务的数据无法随镜像丢弃,需要外部存储。
  • 调试不便:不能直接 SSH 进去改,得改代码重新构建。

7. 状态漂移与合规

7.1 漂移是怎么发生的

即使有配置管理,机器仍会漂移:有人手工改了配置、某个脚本临时加了文件、依赖自动升级。漂移积累到一定程度就会引发「明明配置对却行为不对」的诡异故障。

7.2 检测漂移

两种手段:定期以 check 模式跑 playbook(--check --diff)看有多少 would-change;或用专门工具(如 Ansible 的 fact 采集、云厂商的配置合规服务)做基线比对。

漂移检测周期建议
  生产环境:每日一次 check 模式巡检
  核心主机:每小时一次关键项巡检
  变更窗口后:立即跑一次全量巡检

7.3 合规基线

把安全基线(如 SSH 配置、审计规则、内核参数)写进 role,定期收敛并出合规报告。合规不是一次性通过审计,而是持续保持在基线上。

8. 混合策略与迁移

8.1 什么时候用哪个

服务类型推荐策略理由
无状态 Web 服务不可变镜像弹性伸缩、秒级回滚
数据库/中间件收敛式有状态、不宜重建
长生命周期主机收敛式无法频繁重建
边缘/IoT 设备混合镜像更新 + 配置收敛
一次性任务容器用完即弃

8.2 渐进迁移路径

从收敛式迁向不可变不要一步到位。可以先「镜像化打包但保留 SSH」,再逐步禁止运行时修改,最后完全关闭登录入口,让镜像成为唯一真相。

8.3 共享的真相来源

无论走哪条路线,配置的真相来源都应该在版本控制里。Ansible 的 playbook 和镜像的构建脚本都是代码,都要走评审、测试与流水线。

9. 案例与最佳实践

9.1 落地 Checklist

□ 按服务生命周期选路线:无状态用镜像、有状态用收敛
□ Ansible 优先用专用模块,避免 command/shell 破坏幂等
□ 用 changed 数量观察收敛,稳定后应趋近 0
□ 用 Role/Collection 做复用,defaults 与 vars 分清
□ 定期跑 check 模式巡检,量化状态漂移
□ 安全基线写进 role,持续收敛并出合规报告
□ 不可变镜像必须过安全扫描与合规校验再入库
□ 配置真相来源统一放版本控制,走评审与流水线

9.2 常见坑与对策

坑现象对策
非幂等任务每次运行都 changed改用专用模块
变量优先级混乱覆盖不生效梳理 defaults 与 vars
手工改生产配置漂移积累出诡异故障巡检 + 禁止手工改
镜像不扫就上漏洞随镜像扩散入库前强制扫描
有状态服务用不可变数据丢失状态外置或用收敛式
一次全量迁移迁移期故障频发渐进迁移,先并行
回滚靠反向收敛回滚失败率高优先设计可替换方案

小结

配置管理的本质问题是「机器的真相从哪里来」:收敛式让工具持续把机器拉向声明状态,不可变式让镜像成为唯一真相。Ansible 以无代理、幂等、Role 复用的模型成为收敛式路线的首选;不可变基础设施则以镜像烘焙和替换重建换取可靠回滚。实战中最优解通常是混合:无状态服务走镜像、有状态服务走收敛、配置真相统一进版本控制,并用定期巡检把状态漂移控制在可接受范围内。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「DevOps」更多文章

  1. 可观测性驱动的发布验证与自动回滚
  2. 策略即代码:OPA、Conftest 与合规门禁
  3. 制品管理与供应链溯源:从仓库到 Provenance