外观
案例:批量运维脚本
现象
需要在 20 台机器上统一修改 client_max_body_size。手工 ssh 逐台改,改完发现漏了两台,一周后大文件上传在这两台机器上 413。
定位过程
不是技术问题,是流程问题。手工操作的问题:
- 20 台 × 3 条命令 = 60 次操作,漏一次没人发现。
- 终端输出刷屏,看不出哪台失败了。
- 改完没有验证,不知道是否生效。
- 没有记录,下次不知道改过哪些。
处置
方案一:shell + xargs 并行(轻量)
bash
#!/usr/bin/env bash
# /opt/ops/update-nginx-size.sh
set -uo pipefail
HOSTS_FILE="${1:?usage: $0 hosts.txt}"
OLD='client_max_body_size 1m;'
NEW='client_max_body_size 20m;'
run_one() {
local h="$1"
ssh -o ConnectTimeout=5 -o BatchMode=yes "$h" bash -s <<EOF
set -e
CONF=\$(nginx -T 2>/dev/null | awk '/# configuration file/{print \$NF}' | grep -v 'nginx.conf$' | head -1)
grep -q '$NEW' "\$CONF" && { echo "SKIP $h"; exit 0; }
sed -i 's/^\(\s*\)$OLD/\1$NEW/' "\$CONF"
nginx -t
nginx -s reload
echo "OK $h"
EOF
}
export -f run_one
cat "$HOSTS_FILE" | xargs -n1 -P8 -I{} bash -c 'run_one "$@"' _ {} 2>&1 | tee /tmp/nginx-update.log
echo "---- summary ----"
grep -c '^OK' /tmp/nginx-update.log || true
grep -c '^SKIP' /tmp/nginx-update.log || true
grep -E 'FAIL|denied|Connection' /tmp/nginx-update.log || echo "no failures"bash
cat > /tmp/hosts.txt <<'EOF'
10.0.0.41
10.0.0.42
10.0.0.43
...
EOF
./update-nginx-size.sh /tmp/hosts.txttext
skipped: nginx reload
OK 10.0.0.41
OK 10.0.0.42
OK 10.0.0.43
...
---- summary ----
20
0
no failures关键设计:
| 设计 | 作用 |
|---|---|
set -e + nginx -t | 配置错了不会 reload |
grep -q 幂等判断 | 重复执行不会重复改 |
BatchMode=yes | 免密码失败时快速报错,不卡住 |
-P8 | 并行,20 台几十秒完成 |
tee 日志 | 有完整记录可查 |
| 汇总统计 | 一眼看到成功/失败数量 |
批量执行的三个必须
- 先 dry-run:把
sed -i换成sed 's/.../.../p'只看不写。 - 先单台验证:抽一台跑通再全量。
- 必须幂等:重复执行结果一致,否则重试会改坏。
方案二:Ansible(有规模后)
yaml
# update-nginx.yml
- hosts: web
become: yes
serial: 5
max_fail_percentage: 0
tasks:
- name: ensure client_max_body_size
lineinfile:
path: /etc/nginx/nginx.conf
regexp: '^\s*client_max_body_size'
line: ' client_max_body_size 20m;'
notify: reload nginx
- name: verify config
command: nginx -t
changed_when: false
handlers:
- name: reload nginx
service:
name: nginx
state: reloadedbash
ansible -i inventory.ini web -m ping
ansible-playbook -i inventory.ini update-nginx.yml --check # dry-run
ansible-playbook -i inventory.ini update-nginx.ymltext
PLAY [web] ****************************************************************
TASK [ensure client_max_body_size] ****************************************
changed: [10.0.0.41]
changed: [10.0.0.42]
...
PLAY RECAP ****************************************************************
10.0.0.41 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
10.0.0.42 : ok=3 changed=1 unreachable=0 failed=0 skipped=0Ansible 的优势:
serial: 5分批,出问题最多影响 5 台。--check天然支持 dry-run。- 每个模块幂等。
- 有
PLAY RECAP明确列出失败主机。
方案三:配置管理(长期)
配置文件纳入 Git,用 Ansible/Chef/Puppet/Salt 保证最终状态一致。人工改服务器视为"漂移",被自动纠正。
验证
不管用哪种方式,必须验证:
bash
cat /tmp/hosts.txt | xargs -n1 -P8 -I{} sh -c \
'echo -n "{} "; ssh -o BatchMode=yes {} "nginx -T 2>/dev/null | grep -c \"client_max_body_size 20m\""'text
10.0.0.41 1
10.0.0.42 1
10.0.0.43 1
...每台都是 1 才算全部生效。有 0 或连接错误的,就是漏掉的。
复盘
| 问题 | 根因 | 改进 |
|---|---|---|
| 漏了两台 | 手工逐台操作 | 脚本遍历 + 汇总 |
| 没发现漏了 | 没有验证步骤 | 改完统一下发验证命令 |
| 不知道改了什么 | 没有日志 | tee 到文件 |
| 无法回滚 | 没备份 | 改前 cp -a 留备份 |
bash
# 改前备份
ssh "$h" "cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%F-%H%M)"批量操作的最低标准
哪怕只用 shell,也要做到:
- 主机清单在文件里,不靠脑子记。
- 每台的输出带主机名前缀。
- 最后有成功/失败汇总。
- 有完整日志。
这四条能挡掉 90% 的"漏了一台"事故。