前几年手上服务器不多的时候,每台机器逐个 SSH 登录、敲命令、配环境,虽然烦但还能忍。去年开始业务扩展,从 3 台涨到十几台,每次上线新服务要在每台机器上重复同样的操作——装 Nginx、改配置、重启、检查——一个下午就在不断的 SSH 连接和 Ctrl+C/Ctrl+V 之间耗完了。
最崩溃的一次是给所有服务器更新 Nginx 配置。第一台改完测试通过,第二台漏了一个分号,第三台版本不一样配置文件路径不同……搞到后面我自己都分不清哪台改到哪一步了。半夜躺在床上突然想起来:好像有两台机器的防火墙规则还没同步。
当时也看了一下几个方案:
- Puppet/Chef:太重了,还要装 agent、搞证书,我这几台机器不值得搭那么大的架子。
- SaltStack:性能不错,但架构上需要一个 master 节点,而且配置语法学起来有点别扭。
- Ansible:无 agent,走 SSH,YAML 语法,入门成本最低。
最后选 Ansible 的理由其实很简单——我只需要 SSH 能连上机器就能干活,不需要在客户端装任何东西。这对已经跑着业务的机器来说太友好了,少了很多心理负担。
我的控制端是 Ubuntu 22.04,直接 apt 装:
sudo apt update
sudo apt install ansible -y
ansible --version
装完先把 hosts 文件写好。/etc/ansible/hosts:
[web]
web-01 ansible_host=192.168.1.10
web-02 ansible_host=192.168.1.11
web-03 ansible_host=192.168.1.12
[db]
db-01 ansible_host=192.168.1.20
db-02 ansible_host=192.168.1.21
[monitor]
monitor ansible_host=192.168.1.30
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=/home/deploy/.ssh/id_ed25519
测试连通性:
ansible all -m ping
这里第一次就翻车了——一直返回 UNREACHABLE。排查了半天,发现是新装的机器 SSH 默认不允许密钥登录。得先手动 SSH 上去一次把 PasswordAuthentication no 改成 yes,再把公钥写进 authorized_keys。说白了就是鸡生蛋问题:用 Ansible 自动化之前得先把 SSH 通路的脏活干完。
目标:所有服务器统一时区、DNS、安装常用工具包、更新系统。
---
- name: Init server baseline
hosts: all
become: yes
tasks:
- name: Set timezone to Asia/Shanghai
timezone:
name: Asia/Shanghai
- name: Install common packages
apt:
name:
- htop
- iotop
- net-tools
- lsof
- jq
- tree
- vim
state: present
update_cache: yes
- name: Update all packages
apt:
upgrade: dist
autoclean: yes
autoremove: yes
跑一遍:
ansible-playbook -i /etc/ansible/hosts init.yml
看着十几台机器的输出哗啦啦地往下滚,那种感觉确实挺爽的——以前一根一根手动 SSH 的活,现在一行命令全搞定。
坑 1:幂等 ≠ 不用测试
Ansible 号称幂等——同一个 playbook 跑两次结果一样。这在大方向上是对的,但我遇到过一个坑:
- name: Append line to config
lineinfile:
path: /etc/nginx/nginx.conf
line: "client_max_body_size 100m;"
第一次跑正常。第二次跑——因为 lineinfile 的 regexp 没指定,它默认一行一行匹配,发现已经存在就不写。看起来没问题对吧?
问题出在 第三次。我不小心改了缩进,同样的 playbook 又在每台机器上各追加了一行。最后排查 Nginx 配置时发现 client_max_body_size 出现了两三次,虽然值一样,但这种"重复声明"在某些配置里会导致行为不确定。
教训:lineinfile 一定要加 regexp,明确告诉它匹配什么、替换什么。
- name: Ensure client_max_body_size is set
lineinfile:
path: /etc/nginx/nginx.conf
regexp: "^client_max_body_size"
line: "client_max_body_size 100m;"
坑 2:变量覆盖顺序把我绕晕了
Ansible 的变量优先级有 20 多层(group vars > host vars > play vars > extra vars……)。我踩的一个典型场景:
在 group_vars/web.yml 里写了:
nginx_port: 8080
然后在 host_vars/web-01.yml 里写:
nginx_port: 9090
我以为 group_vars 会覆盖 host_vars(因为"group"听起来更大),结果正好相反——host_vars 优先级高于 group_vars。web-01 跑了 9090,其他两台跑了 8080,排查了半小时。
后来我就乖乖在 playbook 开头用 ansible-inventory --graph 看变量继承关系了:
ansible-inventory -i hosts --graph --vars
坑 3:command 模块 vs shell 模块
- name: Run deploy script
command: ./deploy.sh
返回 command not found。原因:command 模块不走 shell,不解析 $PATH、不支持管道和重定向。用 shell 模块或者给 command 传绝对路径就行。
- name: Run deploy script
shell: ./deploy.sh
反过来,如果不需要 shell 特性,尽量用 command——更安全,也少了一些注入风险。
坑 4:大文件传输用 synchronize 别用 copy
我第一次用 copy 模块传一个 200MB 的 tar 包:
- name: Copy app package
copy:
src: /tmp/app.tar.gz
dest: /opt/app/app.tar.gz
十几台机器逐个传输,慢得离谱。后来换成 synchronize(底层是 rsync):
- name: Sync app package
synchronize:
src: /tmp/app.tar.gz
dest: /opt/app/app.tar.gz
compress: yes
rsync_opts:
- "--progress"
速度快了不止一倍,而且支持增量传输。小文件用 copy,大文件用 synchronize,这是我现在的原则。
坑 5:忘记 become: yes 的后果
大部分系统操作都需要 root 权限。Ansible 默认以连接用户身份执行,如果你没加 become: yes,apt、systemctl、写 /etc 下的文件这些操作全部报错。
- name: Restart nginx
systemd:
name: nginx
state: restarted
become: yes
我一开始图省事直接在 playbook 级别开了 become: yes,后来发现有些普通用户操作(比如 git pull、pip install --user)不该用 root。现在的做法是按需加 become: yes,只在需要提权的 task 上开。
有时候越简单的工具,坑越藏在细节里。上面这 5 个坑是我用 Ansible 半年多翻车翻出来的,分享出来让大家少走点弯路。
我现在的工作流长这样:
# 1. 测试连通性
ansible all -m ping
# 2. 语法检查
ansible-playbook --syntax-check playbook.yml
# 3. 空跑模式看变更
ansible-playbook -C playbook.yml
# 4. 限定批次执行
ansible-playbook playbook.yml --limit web-01
# 5. 全量执行
ansible-playbook playbook.yml
先 --syntax-check 保证语法没错,再 -C 空跑看看会改什么,然后用 --limit 挑一台试跑,确认没问题了再全量跑。这个流程跑了半年多,没出过批量事故。
几个让我觉得"真香"的场景:
- 批量改 SSH 配置:一行 playbook 给所有机器禁用密码登录、改端口号,再也不用一台一台改完怕遗漏。
- 批量部署 Nginx 配置:把虚拟主机配置写好,
template模块传上去,systemctl reload nginx。从改到生效不到 10 秒。 - 批量查看日志:
ansible web -m shell -a 'tail -n 100 /var/log/nginx/access.log',不用再一台一台 SSH 进去。 - 批量巡检:每周跑一个巡检 playbook,检查磁盘空间、内存使用、服务状态,结果汇总到一个 JSON 文件。
当然 Ansible 也不是什么神仙工具,遇到上千台机器的时候它的并行能力和执行效率确实不够看,但对我来说十几台小集群的场景,它刚好够用。
评论一下?