缘起:从“人肉运维”到“工具选型”的痛
故事得从几年前说起。当时我接手了一个规模在500台左右的混合云环境,有物理机、有虚拟机,操作系统从CentOS 6到Ubuntu 18.04,版本参差不齐。之前的团队全靠人肉SSH登录,改个NTP设置要花一整天,还经常漏改。老板拍桌子说,必须上设置管理工具。于是,我开始了痛苦的选型之旅。
第一站:Ansible——简单是最大的美德,但也是最大的陷阱
Ansible是我第一个深入使用的工具。它的理念“无代理”和“SSH直连”太吸引人了。对于当时环境复杂、不想在每个节点上装Agent的我来说,简直是救星。
我的第一个坑:Playbook幂等性
写第一个Playbook,给所有机器装Nginx。我天真地写了:
- name: Install Nginx
hosts: all
tasks:
- name: Install Nginx
yum:
name: nginx
state: latest
- name: Start Nginx
service:
name: nginx
state: started
结果呢?每次执行,只要Nginx有更新,它就会自动升级并重启服务,导致线上业务闪断。这就是典型的非幂等操作。正确的做法是:
- name: Install Nginx
yum:
name: nginx
state: present # 只确保安装,不升级
- name: Start Nginx
service:
name: nginx
state: started
enabled: yes
我的第二个坑:大规模并发下的SSH性能
当节点数超过200台时,Ansible默认的forks=5简直让人崩溃。一个简单的ping模块都要跑几分钟。我不得不调优:
# ansible.cfg
[defaults]
forks = 50
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
开启SSH pipelining和ControlPersist,性能才勉强能看。但即便如此,对于需要频繁执行的任务,Ansible的“无代理”模式反而成了瓶颈,每次都要建立SSH连接,开销很大。
小结Ansible: 上手快,语法简洁,适合小规模、临时性任务或作为编排工具。但大规模、高频次、强一致性的场景,它显得力不从心。它的“简单”背后,是对网络和SSH性能的依赖。
第二站:SaltStack——性能怪兽,但设置复杂得像天书
被Ansible的性能折磨后,我转向了SaltStack。它的“主从模式”和ZeroMQ消息队列,性能确实强悍。1000台机器,几秒钟就能完成命令下发。
我的第一个坑:Grains和Pillar的混乱
SaltStack的Grains(静态信息)和Pillar(敏感数据)设计得非常好,但新手很容易搞混。我犯过一个低级错误,把数据库密码写到了Grains里,结果所有Minion都能通过grains.item看到,差点酿成数据泄露。
正确的做法是:Grains放主机名、IP、OS版本等不敏感信息;Pillar放密码、密钥等敏感信息,并且通过pillar_roots和top.sls严格控制访问权限。
# /srv/pillar/top.sls
base:
'web*':
- mysql.creds
# /srv/pillar/mysql/creds.sls
mysql:
password: 'SuperSecretPassword'
我的第二个坑:State文件的状态依赖
Salt的State系统非常强大,但状态之间的require、watch、prereq用不好,就会形成复杂的依赖地狱。我曾经写过一个搭建Java应用的State,因为require链写错了,导致每次应用重启时,Nginx都会先重启,造成几十秒的503。
调试这类问题,只能靠state.show_sls和state.sls test=True一步步排查,非常痛苦。
小结SaltStack: 性能是它的绝对优势,适合超大规模集群的实时管理和设置下发。但学习曲线陡峭,State系统、Reactor系统、Event系统概念复杂,运维成本高。一旦出问题,排查难度比Ansible大一个数量级。
第三站:Puppet——声明式编程的典范,但DSL让人头秃
Puppet是设置管理的老前辈,它的声明式理念(“我想要什么状态,而不是怎么达到”)非常先进。我是在一个需要长期维护、严格规范的环境中使用它的。
我的第一个坑:Master/Agent架构的运维负担
Puppet的Master/Agent模式,意味着你需要维护一台或多台Puppet Server。证书管理、环境设置、Passenger或JRuby调优,都是额外的运维工作。有一次,Puppet Server的JRuby池耗尽,导致所有Agent无法正常拉取设置,业务搭建停滞了整整半天。
我的第二个坑:Puppet DSL的“类”与“继承”
Puppet的DSL是一门完整的编程语言,支持类、继承、资源默认值等。但过度使用继承,会导致代码逻辑混乱。我接手过一个项目,一个基础类被继承了5层,修改一个参数,要回溯整个继承链,简直是噩梦。
后来我改用Data in Modules和Hiera来解耦,把参数从代码中剥离出来,才让代码变得清晰。
# manifests/site.pp
node default {
include nginx
}
# modules/nginx/manifests/init.pp
class nginx (
String $package_ensure = 'present',
String $service_ensure = 'running',
) {
package { 'nginx':
ensure => $package_ensure,
}
service { 'nginx':
ensure => $service_ensure,
enable => true,
}
}
小结Puppet: 模型严谨,适合需要长期维护、强规范、大规模的环境。但学习成本最高,Master架构的运维负担重,DSL的灵活性也带来了复杂性。
最终选择与反思
最终,我的团队选择了混合方案:
- Ansible:作为临时任务执行、应用搭建的编排工具,以及小规模(<100台)环境的设置管理。
- SaltStack:作为大规模(>500台)集群的实时命令执行、设置下发和监控告警的底层引擎。
- Puppet:被我们淘汰了,因为其运维成本和DSL的复杂度,对于我们这种快速迭代的互联网团队来说,性价比不高。
给新手的建议:
- 先评估规模:50台以下,无脑Ansible;500台以上,认真考虑SaltStack;需要长期规范,再研究Puppet。
- 幂等性是生命线:无论用哪个工具,写出的设置必须能反复执行而不改变结果。这是设置管理的基石。
- 测试环境不能省:一定要搭建与生产环境一致的测试环境,用
--check、test=True等模式先跑一遍,再上生产。 - 版本控制是底线:所有的Playbook、State、Manifest、Pillar数据,都必须纳入Git管理。没有版本控制,设置管理就是空中楼阁。
设置管理工具没有银弹。理解每个工具的设计哲学和适用场景,结合自身团队的运维能力和业务需求,做出最务实的选择,才是SRE的核心能力。希望我的这些“坑”,能帮你少走一些弯路。
👨💻 运维经验:根据实际生产环境,以上步骤建议先在测试环境验证,并做好备份。参数值需根据服务器设置调整,不要盲目照搬。