深色模式
基础设施漂移检测:发现手动改动
摘要:有人为救急在控制台改了参数,state 却不知道——这就是漂移,下次 apply 可能把线上搞挂。本文用定时 plan、Ansible check 和 GitOps 自愈三招建立漂移检测机制。
适用环境
- 已用 Terraform/OpenTofu 管理云资源,或已用 Ansible 管理配置
- Kubernetes 场景可选装 Argo CD / Flux
- 有告警通道(邮件 / IM / 监控系统)
操作步骤
1. 定时 plan 巡检(核心手段)
bash
cat > /opt/scripts/tf-drift.sh <<'EOF'
#!/usr/bin/env bash
set -uo pipefail
PROJECTS=(/opt/tf/prod /opt/tf/staging)
for d in "${PROJECTS[@]}"; do
cd "$d" || continue
terraform init -input=false >/dev/null
terraform plan -detailed-exitcode -input=false -no-color > /tmp/plan.$$ 2>&1
rc=$?
case $rc in
0) echo "OK $d: no drift" ;;
1) echo "ERR $d: plan failed"; cat /tmp/plan.$$ ;;
2) echo "DRIFT $d"; cat /tmp/plan.$$ ;;
esac
rm -f /tmp/plan.$$
done
EOF
chmod +x /opt/scripts/tf-drift.sh1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
text
plan -detailed-exitcode 退出码:
0 = 无差异(健康) 1 = 执行出错(凭证/网络/语法) 2 = 存在差异(漂移)1
2
2
2. 接入定时与告警
bash
sudo tee /etc/cron.d/tf-drift > /dev/null <<'EOF'
PATH=/usr/local/bin:/usr/bin:/bin
0 */4 * * * root /opt/scripts/tf-drift.sh | grep -E '^(DRIFT|ERR)' && /opt/scripts/notify.sh
EOF1
2
3
4
2
3
4
notify.sh 里可用 logger -t drift "...",也可换成 curl 推送到 IM webhook。
3. Ansible 与 Kubernetes 侧
bash
ansible-playbook -i inventory.ini baseline.yml --check --diff > /tmp/d.log 2>&1
grep -q 'changed=[1-9]' /tmp/d.log && echo "配置漂移 detected"
argocd app list -o wide | grep -v Synced
flux get kustomizations # READY=False 可能即漂移
kubectl diff -f ./manifests/1
2
3
4
5
6
2
3
4
5
6
--check 不改任何东西,只报告「如果执行会改什么」,天然就是漂移检测。
4. 发现漂移后怎么处理
text
1. 看 diff,判断是「有意修改」还是「误操作」
2. 有意修改 → 把改动写回代码(import 或补 HCL),让代码追上现实
3. 误操作 → terraform apply / ansible-playbook 把现实拉回代码
4. 都不确定 → 先冻结自动执行,人工确认后再处理1
2
3
4
2
3
4
危险
漂移存在时直接 terraform apply,可能销毁被手动创建且未纳入 state 的资源。处理前必须完整阅读 plan 中的 - 与 -/+ 条目。
验证
bash
terraform plan -detailed-exitcode; echo "exit=$?"
# 人为制造漂移:在控制台改一个 tag,再跑一次巡检脚本1
2
2
- [ ] 无漂移时退出码为 0,巡检输出 OK
- [ ] 人为改动云上配置后,下一个巡检周期报 DRIFT
- [ ] 告警能实际送达值班渠道
常见坑
巡检本身成了噪声源
每 5 分钟报同样的漂移,值班人会直接屏蔽。同一漂移只告警一次,区分「新增」与「持续」。
plan 失败被当成漂移
退出码 1 是执行错误(常见于凭证过期),必须和 2 严格区分。
忽略 provider 默认字段
某些资源不写某字段时厂商给默认值,plan 一直显示差异。用 lifecycle { ignore_changes = [...] } 忽略。