有一类运维事故特别典型:某台机器上跑得好好的服务,换一台机器部署却起不来,排查半天发现是两台的某个配置文件差了一行;或者半夜有人手动改了生产机的参数,没记录、没通知,下次发布时这个改动被覆盖,问题复发。这类问题的根源都是同一个——配置漂移,也就是机器之间、机器和"应该的样子"之间,悄悄产生了偏差。
配置管理(Configuration Management)要解决的就是这个问题:把服务器的配置用代码描述出来,让机器状态始终收敛到描述的样子。这篇文章讲清楚配置管理的核心理念(声明式、幂等、基础设施即代码),解释配置漂移是怎么产生的、怎么发现和消除,对比 Ansible、Puppet、Chef、SaltStack 这几个主流工具的定位差异,最后讲配置怎么模板化、怎么纳入 Git 版本管理。理解了这些,你就能把运维从"手工敲命令"升级成"代码驱动"。
从命令式到声明式
先理解一个关键的区别。传统运维是命令式的:你告诉机器"执行这个命令、那个命令",关心的是过程。
useradd -m deploy
mkdir -p /opt/app
chown -R deploy:deploy /opt/app
echo "port=8080" > /opt/app/config.ini
systemctl restart app
问题在于:如果 deploy 用户已经存在,useradd 就会报错;如果目录已经存在,也可以重复执行;如果 config.ini 已经被改过,echo 会覆盖掉别人的改动。这些命令不知道"目标状态",只知道"执行动作",重复执行的结果不确定。
配置管理是声明式的:你描述"这台机器的最终状态应该是什么样",工具负责想办法达成,并且保证重复执行结果一致。
- name: 确保 deploy 用户存在
user:
name: deploy
state: present
- name: 确保应用目录存在且属主正确
file:
path: /opt/app
state: directory
owner: deploy
group: deploy
- name: 确保配置文件内容正确
template:
src: config.ini.j2
dest: /opt/app/config.ini
这段 YAML 描述的是"状态":用户要在、目录要在且属主对、配置内容要对。至于它是已经满足还是需要创建,工具自己判断。这就是声明式的威力——你写"要什么",不写"怎么做"。
幂等:重复执行也安全
声明式带来的直接好处就是幂等(idempotent)。幂等的定义是:同一个操作执行一次和执行多次,结果完全相同。
| 操作 | 是否幂等 | 重复执行结果 |
|---|---|---|
useradd deploy |
否 | 第二次报"用户已存在" |
user: name=deploy state=present |
是 | 第二次显示 ok,不做任何事 |
echo x > file |
否 | 每次都重写文件,覆盖他人改动 |
copy: content=x dest=file |
是 | 内容一致就跳过,不重写 |
service nginx start |
否 | 已启动时可能报错 |
systemd: name=nginx state=started |
是 | 已运行就跳过 |
幂等的意义在运维里非常实际:你不用先查"这台机器上有没有",直接套用配置即可,工具会判断需不需要动手。这让配置可以无脑重复执行,也是"配置即代码"能被安全地反复应用到成千上万台机器上的前提。
验证幂等性的方法很简单:一个 playbook 第一次跑会有一堆 changed,第二次跑应该几乎全是 ok。如果第二次还有大量 changed,说明有不幂等的任务,需要修。这是评价自动化代码质量最直接的指标。
配置漂移:一致性的最大敌人
配置漂移(configuration drift)指的是服务器实际状态逐渐偏离了它应有的状态。产生原因主要有几个:
手动改配置。 出故障时,值班的人登录服务器临时改个参数救急,改完忘了记录,也忘了同步到配置代码里。下次自动化部署一跑,这个改动被覆盖,故障复现。
不同人不同时间操作。 张三装的机器和李四装的机器,装的包版本、配的参数不一样,看起来都能跑,但环境已经分叉。
自动化工具执行失败被忽略。 某台机器上次部署时中途失败了,配置没推全,但没人注意到,就成了"特殊机器"。
依赖版本漂移。 半年后重装机器,从仓库拉到的是新版依赖,和当初的版本不同,行为可能变化。
漂移的危害在于它是累积的、隐蔽的。今天差一行,明天差一个包,等到出故障时,机器之间的差异多到无法快速定位。"在我机器上是好的"这句话,就是漂移的典型症状。
发现和消除漂移的手段有两个层面。一是定期做配置核查,用自动化工具对每台机器跑一遍配置,看有多少项不一致(Ansible 的 --check 模式就是干这个的):
ansible-playbook -i inventory/production site.yml --check --diff
输出的 changed 项就是漂移项,--diff 还会显示具体差在哪一行。二是禁止手工改配置,把生产机的配置变更统一走自动化流程。做不到完全禁止时,至少要保证手工改动后续会反向固化进配置代码里。
一个实用的落地做法是"配置基线 + 定期巡检":把所有机器应有的状态写成一份配置基线(一个 playbook 或 role),每天跑一次 apply,任何漂移都会被自动纠正,同时在日志里留下"改了什么"的记录。这样漂移还没累积就被抹平了。
四大工具怎么选
配置管理工具不止一个,它们的定位确实不同。
| 工具 | 架构 | 配置语言 | 学习曲线 | 特点与适合场景 |
|---|---|---|---|---|
| Ansible | 无 Agent,SSH | YAML | 平缓 | 上手快,适合中小规模、渐进式引入 |
| Puppet | C/S,需 Agent | 自有 DSL | 陡峭 | 强约束,适合大规模、规范化组织 |
| Chef | C/S,需 Agent | Ruby | 陡峭 | 灵活强大,适合开发主导、复杂场景 |
| SaltStack | C/S 或 SSH | YAML + Python | 中等 | 高并发、秒级下发,适合超大规模 |
Ansible 的最大优势是无 Agent 加 YAML 易读,任何有 Linux 基础的人一两天就能上手,从零开始做配置管理的最佳起点。它的短板在大规模时 SSH 逐台连接的开销。
Puppet 是配置管理的老牌选手,采用声明式的自有 DSL,强调"模型驱动"。它有个 master 定义期望状态,agent 定期向 master 拉取并强制收敛。对大规模、需要强一致约束的环境很合适,但学习成本高,报错信息对新手不友好。
Chef 用 Ruby 写配置,表达力最强,能写出很复杂的逻辑。适合规模大、需求复杂的组织,但要求团队有编程能力,维护成本也高。
SaltStack 兼顾了性能和易用,用 ZeroMQ 做通信通道,几千台机器下发命令能做到秒级。它既支持常驻 agent 模式,也支持 SSH 模式,是超大规模场景的常见选择。
选择建议落到具体场景:几十台以内、团队运维经验一般,直接选 Ansible;几百台且需要强规范约束,考虑 Puppet;需要秒级下发、机器数以千计,考虑 SaltStack(或 Ansible 加优化);已经在用某个工具、迁移成本高,就不要为了"新"而换。工具本身的差别没有"用与不用配置管理"的差别大。
配置模板化
配置管理的核心手段是模板。同一份配置,不同机器要有不同的值(IP、端口、域名、内存),这些值又不该硬编码在每台机器上,模板就是解法。
以 Nginx 配置为例,模板文件 templates/nginx.conf.j2(Jinja2 语法):
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_worker_connections }};
}
http {
server {
listen {{ nginx_listen_port }};
server_name {{ nginx_server_name }};
root {{ nginx_root }};
location / {
proxy_pass http://{{ app_backend }};
}
}
}
变量值不写在模板里,而是按"组"或"主机"提供。放在 group_vars/web.yml:
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_listen_port: 80
nginx_server_name: www.example.com
放在 host_vars/web-01.yml:
app_backend: 192.0.2.20:8080
引用模板:
- name: 下发 Nginx 配置
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: reload nginx
模板化的好处是"一份模板、百台机器、按需取值"。改一次模板,所有机器同步更新;某台机器要特殊值,只改它的 host_vars。变量分层(全局 → 组 → 主机)让配置既有统一基线,又保留必要的灵活性。
模板里要小心两个坑:一是不要把时间戳、随机数写进模板,否则每次渲染内容都不同,文件每次都变,破坏幂等,handler 会反复触发;二是注意转义,配置里的 $ 在 Nginx、PHP 配置里很常见,Jinja2 里 {{ }} 是变量,如果原文就有 {{ 需要用 {% raw %}...{% endraw %} 包起来。
配置纳入版本管理
配置管理最重要的一条实践经验是:所有配置文件都要纳入 Git 版本管理。这是它叫"基础设施即代码"的原因——代码就该有版本控制。
一个典型的配置仓库结构:
infra-config/
├── ansible.cfg
├── inventory/
│ ├── production/hosts
│ └── staging/hosts
├── group_vars/
│ ├── all.yml
│ └── web.yml
├── host_vars/
│ └── web-01.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ └── mysql/
├── site.yml # 主入口 playbook
└── README.md
纳入 Git 的价值:
可追溯。 每次配置变更都有提交记录,谁改的、什么时候改的、为什么改,一目了然。出故障时能快速定位"最近改了什么"。
可回滚。 发现问题,git revert 回到上一个版本,重新应用即可恢复。这比手工撤销安全得多。
可评审。 变更走 Pull Request,让别人看一眼再合并,避免低级错误进生产。配置变更和代码变更享受同一套质量流程。
可复现。 新机器、新环境只要 clone 仓库、跑一遍 playbook,就能得到一套一致的配置,不用凭记忆复刻。
几个实操要点:inventory 里的敏感信息(密码、密钥)不要明文提交,用 Ansible Vault 加密:
ansible-vault create group_vars/all/secrets.yml
ansible-vault encrypt host_vars/web-01/secrets.yml
ansible-vault edit group_vars/all/secrets.yml
已加密的文件可以安全提交。运行 playbook 时用 --ask-vault-pass 或 --vault-password-file 提供密码。另外给仓库设好分支策略:主分支对应生产,变更走特性分支加 PR,合并后触发自动化部署(这部分和 CI/CD 相关,可以另开话题)。
常见问题
已经在用的老机器配置很乱,怎么引入配置管理?
不要想着一步到位全接管。建议从"新机器用配置管理装"开始,老机器先做只读的配置核查(用 --check 看漂移),把差异大的机器逐步重建或对齐。强制重构存量机器风险高,渐进式引入更现实。
配置里有些值每台机器都不同,比如 IP,也要模板化吗?
要,但取值方式不同。IP 这类信息适合放在 host_vars 或由 Ansible fact 自动获取(比如 ansible_default_ipv4.address)。原则是:能自动获取的不要手写,会变的不要硬编码,全组一致的放 group_vars,单机差异的放 host_vars。
幂等性怎么保证?有哪些常见的不幂等陷阱?
优先用模块(package、file、template、systemd)而不是 shell/command;确实要用命令时加 creates/removes 参数;模板里别放时间戳;文件 mode、owner 要对齐,否则每次都判定为变化被重写。定期用"第二次跑应该全 ok"来检验。
配置管理与 CI/CD 是什么关系?
配置管理负责"机器环境是什么样",CI/CD 负责"把应用代码构建、测试、发布"。二者配合使用:CI 产出制品,CD 触发 Ansible 把制品和配置一起部署到机器。它们是互补的,不是二选一。
总结
- 配置管理用声明式描述"机器最终状态",而非命令式描述"执行什么动作",工具负责判断如何达成。
- 幂等是核心,同一个配置执行一次和多次结果一致,这让你能安全地重复应用配置到大量机器。
- 配置漂移是一致性的大敌,靠禁止手工改配置和定期跑配置核查来发现与消除。
- 工具选择看场景:中小规模选 Ansible,强约束大规模看 Puppet,超大规模秒级下发看 SaltStack,不要为换而换。
- 配置用模板加变量分层实现"一份模板、多台机器、按需取值",模板里别放时间戳,避免破坏幂等。
- 配置必须纳入 Git,获得可追溯、可回滚、可评审、可复现的能力,敏感信息用 Vault 加密。
企业QQ咨询




