1. 为什么 IaC 也需要可观测性
一句话总结: Terraform 管的是「期望状态」,但期望与现实之间会因手工改动、外部事件而漂移,没有持续观测就无法知道基础设施是否还符合代码描述。
写完代码、跑通 apply 之后,工作并没有结束。真正的问题是:三个月后,线上的基础设施还和代码一致吗? 有人手工开了安全组、自动伸缩调整了实例数、云厂商改动了默认行为——这些都让 state 与现实的差距逐渐扩大。
# 一次 plan 就能暴露全部差异
terraform plan -detailed-exitcode
# 退出码 0 = 无变更,1 = 报错,2 = 有变更
可观测性覆盖四个维度:
| 维度 | 问题 | 手段 |
|---|---|---|
| 变更审计 | 谁在何时改了哪条资源 | 审批记录 + 日志 |
| 通知 | 变更发生时谁知道 | Slack / Webhook |
| 漂移 | 现实是否偏离代码 | 定时 plan |
| 健康 | 系统是否在预期状态 | 指标 + 运行手册 |
一句话:Terraform 的可观测性不是「看 Terraform 本身的日志」,而是「用 Terraform 持续回答基础设施是否符合预期」。
2. 变更审计与审批
一句话总结: 审计回答「谁在什么策略版本下、基于哪份 plan、改了哪些资源」,审批回答「关键变更是否有人放行」,二者共同构成变更的可追溯链条。
# 从 plan 中提取本次变更的完整清单
terraform show -json plan.tfplan | \
jq -c '[.resource_changes[]
| select(.change.actions != ["no-op"])
| {address, actions: .change.actions}]'
# 留存审计证据:plan 哈希 + 变更清单 + 审批记录
sha256sum plan.tfplan > audit/plan-$(date +%Y%m%d-%H%M%S).sha256
git rev-parse HEAD > audit/commit.txt
在 CI 中用 environment 保护 apply 阶段,强制人工放行:
# .github/workflows/apply.yml
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: terraform init && terraform plan -out=plan.tfplan
- uses: actions/upload-artifact@v4
with:
name: tfplan
path: plan.tfplan
apply:
needs: plan
runs-on: ubuntu-latest
environment: production # 需人工审批
steps:
- uses: actions/download-artifact@v4
with: { name: tfplan }
- run: terraform init && terraform apply plan.tfplan
| 审计对象 | 留存物 | 用途 |
|---|---|---|
| 代码版本 | git commit / tag | 回溯当时配置 |
| 计划快照 | plan.tfplan + sha256 | 复核实际变更 |
| 审批记录 | PR 评论 / 审批人 | 责任可追溯 |
| 应用日志 | apply 输出 | 事后对账 |
| 策略版本 | 策略仓库 tag | 当时的门禁规则 |
一句话:审计不是「不信任工程师」,而是「让每一次变更都能被复盘」——事故之后能快速定位,本身就是可靠性的组成部分。
3. 通知集成
一句话总结: 把 plan 摘要、apply 结果、漂移告警推送到 Slack 或 Webhook,让团队在变更发生时立刻知情,而不是等到出事才发现。
# 把 plan 摘要格式化成 Slack 消息
terraform show -json plan.tfplan | jq -r '
"计划变更: \(.resource_changes | map(select(.change.actions != ["no-op"])) | length) 个资源\n" +
(.resource_changes
| map(select(.change.actions != ["no-op"]))
| map("• \(.address) [\(.change.actions | join(","))]")
| join("\n"))' > summary.txt
curl -s -X POST -H 'Content-type: application/json' \
--data "$(jq -n --rawfile s summary.txt '{
text: ("Terraform 计划摘要\n" + $s)
}')" \
"$SLACK_WEBHOOK_URL"
# 通用 Webhook 通知(适用于 Teams / 钉钉 / 自建服务)
- name: Notify
if: always()
run: |
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"status\":\"${{ job.status }}\",\"commit\":\"$GITHUB_SHA\"}"
通知内容的设计原则:
| 事件 | 通知渠道 | 内容要点 |
|---|---|---|
| plan 完成 | PR 评论 | 变更数量、销毁资源、成本增量 |
| apply 成功 | Slack 频道 | 环境、变更数、执行人 |
| apply 失败 | Slack + 值班 | 错误摘要、日志链接 |
| 漂移检测 | Slack 告警频道 | 漂移资源清单、首次发现时间 |
| 高危变更 | Slack + @审批人 | 销毁/替换类操作重点标记 |
一句话:通知的价值在「信噪比」——只推值得打断人的事件,否则频道会被静音,告警就失效了。
4. 漂移巡检与定时 plan
一句话总结: 用定时任务周期性执行
terraform plan -detailed-exitcode,把漂移从「某天偶然发现」变成「每天自动上报」。
# .github/workflows/drift.yml
name: drift-detection
on:
schedule:
- cron: "0 6 * * *" # 每天 UTC 06:00
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
strategy:
matrix:
stack: [network, data, app]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
working-directory: stacks/${{ matrix.stack }}
- name: Detect drift
id: plan
continue-on-error: true
run: terraform plan -detailed-exitcode -no-color
working-directory: stacks/${{ matrix.stack }}
- name: Report
if: steps.plan.outputs.exitcode == '2'
run: |
echo "检测到漂移: ${{ matrix.stack }}"
# 推送到告警频道
# 本地或定时任务中手工执行
terraform plan -detailed-exitcode -refresh-only
echo "exit=$?"
# 只看漂移的资源
terraform show -json drift.tfplan | \
jq '[.resource_changes[] | select(.change.actions != ["no-op"]) | .address]'
4.1 漂移的三种处置
| 漂移类型 | 例子 | 处置 |
|---|---|---|
| 预期内 | 自动伸缩调整了实例数 | 更新 ignore_changes 或配置 |
| 意外手工改动 | 控制台开了安全组端口 | 回滚或纳管 |
| 外部系统改动 | 云厂商改了默认参数 | 更新 Provider 或接受差异 |
一句话:漂移巡检的关键不是「发现差异」,而是「分类差异」——不是所有差异都该被自动抹平。
5. 自动化修复与安全边界
一句话总结: 自动修复能收敛简单漂移,但必须先划定安全边界:只允许白名单资源、只允许幂等操作、必须留痕并可回滚。
# 自动化修复脚本:仅处理白名单内的漂移资源
DRIFT=$(terraform show -json drift.tfplan | \
jq -r '.resource_changes[]
| select(.change.actions != ["no-op"])
| .address')
ALLOWED='aws_cloudwatch_log_group|aws_s3_bucket_lifecycle|aws_ssm_parameter'
if echo "$DRIFT" | grep -vqE "^($ALLOWED)"; then
echo "存在白名单外的漂移,跳过自动修复,转人工处理"
exit 1
fi
terraform apply -auto-approve -refresh-only
自动修复的安全边界清单:
| 边界 | 规则 |
|---|---|
| 资源范围 | 白名单,排除有状态资源 |
| 操作类型 | 只做 refresh / 幂等收敛,不做删除 |
| 环境范围 | 仅 dev / stg,prod 一律人工 |
| 执行留痕 | 每次自动修复写审计日志 |
| 熔断机制 | 单次修复超过 N 个资源则中止 |
| 回滚预案 | 修复前 state 备份 |
# 用 prevent_destroy 从代码层面挡住最危险的操作
resource "aws_db_instance" "main" {
lifecycle {
prevent_destroy = true
}
}
一句话:自动化修复的原则是「能自动收敛的自动收敛,可能造成破坏的一律转人工」——省下的人力不值得用事故来换。
6. 运行手册与故障响应
一句话总结: 运行手册把「出事时该做什么」写成可执行的步骤,让任何值班同学都能在压力下按图索骥,而不是依赖某个人的记忆。
运行手册:Terraform apply 失败
[现象]
apply 中断,报错 ResourceInUse / DependencyViolation
[处理步骤]
1. 确认是否有并发 apply:检查锁状态
2. 查看具体失败资源:terraform show plan.tfplan | grep 资源名
3. 判断是否部分成功:terraform state list 对比期望清单
4. 若部分成功:修正配置后重新 apply(Terraform 幂等)
5. 若状态损坏:从备份 state push 恢复
6. 记录事件:时间、影响范围、根因、后续动作
[升级路径]
15 分钟未恢复 → 通知平台负责人
涉及生产数据 → 立即拉群并冻结相关流水线
# 手册中常用的诊断命令
terraform state list
terraform state show aws_instance.web
terraform providers
terraform version
| 故障场景 | 首要动作 | 禁止动作 |
|---|---|---|
| 锁残留 | 确认后 force-unlock | 未确认直接强解 |
| apply 部分成功 | 修正后重跑 | 手工在控制台补资源 |
| 状态损坏 | 从备份恢复 | 手工编辑 tfstate |
| 误删资源 | 用备份/快照恢复 | 反复重试 apply |
| 凭证失效 | 刷新凭证 | 提交凭证到仓库 |
一句话:运行手册的价值在「把人从恐慌中拉回流程」——事故现场最缺的不是聪明,而是秩序。
7. 度量指标与持续改进
一句话总结: 用少量关键指标衡量 IaC 运维的健康度:漂移数量、apply 成功率、变更前置时间、自动修复占比,指标驱动改进而非感觉驱动。
# 从历史 apply 日志中统计成功率
grep -c "Apply complete" apply-*.log
grep -c "Error:" apply-*.log
# 统计当前漂移资源数
terraform show -json drift.tfplan | \
jq '[.resource_changes[] | select(.change.actions != ["no-op"])] | length'
# 把关键指标推送到 CloudWatch,纳入统一看板
resource "aws_cloudwatch_metric_alarm" "drift_count" {
alarm_name = "terraform-drift-count"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "DriftResourceCount"
namespace = "TerraformOps"
period = 86400
statistic = "Maximum"
threshold = 5
alarm_description = "漂移资源数超过 5 个,需要人工收敛"
}
| 指标 | 含义 | 目标 |
|---|---|---|
| 漂移资源数 | 现实偏离代码的规模 | 趋近于 0 |
| apply 成功率 | 流水线稳定性 | > 95% |
| 变更前置时间 | 从提交到上线 | 逐季缩短 |
| 自动修复占比 | 自动化成熟度 | 稳步提升 |
| 回滚次数 | 变更质量 | 逐季下降 |
一句话:指标的意义是「让改进可衡量」——没有指标的运维改进,最后都变成「感觉比以前好了」。
8. 总结
可观测性与运维自动化把 Terraform 从「一次性工具」变成「持续运行的控制面」:
| 环节 | 手段 | 关键点 |
|---|---|---|
| 审计 | plan 快照 + 审批记录 | 每次变更可复盘 |
| 审批 | environment 保护 | 关键变更人工放行 |
| 通知 | Slack / Webhook | 高信噪比,只推值得打断的 |
| 漂移巡检 | 定时 plan + detailed-exitcode | 每日自动上报 |
| 漂移分类 | 预期内 / 手工 / 外部 | 分类后再决定处置 |
| 自动修复 | 白名单 + 熔断 + 留痕 | 破坏性操作转人工 |
| 运行手册 | 步骤化 + 升级路径 | 值班可独立处置 |
| 度量 | 漂移数 / 成功率 / 前置时间 | 指标驱动改进 |
一句话收尾:Terraform 的终局不是「写完代码」,而是「持续保证现实与代码一致」。从审计留痕到通知推送,从每日漂移巡检到带边界的自动修复,再到手册化的故障响应与指标化的持续改进,基础设施才真正从「一次交付」变成了「可运营的系统」。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。