基础设施变更比应用代码变更的风险更高——一次错误的
terraform apply可能销毁生产数据库。IaC 自动化因此在「自动化」之上叠加了「审阅与门禁」:用 CI 执行fmt/validate/plan,把 plan 结果贴回 PR 供人审阅,通过后再由受控流程执行apply。本文以 Terraform 为主线,覆盖远程状态、Atlantis、CloudFormation 与 OIDC 安全,构建可审计的基础设施变更流水线。
一、Terraform 工作流概览
1.1 标准生命周期
代码提交 → format 校验 → validate 语法 → plan 变更预览
→ 人工审阅 plan → apply 执行变更 → 状态回写远程存储
| 阶段 | 命令 | 是否修改基础设施 | 是否可安全反复执行 |
|---|---|---|---|
| 格式化 | terraform fmt -check | ❌ | ✅ |
| 语法校验 | terraform validate | ❌ | ✅ |
| 变更预览 | terraform plan | ❌ | ✅ |
| 执行变更 | terraform apply | ✅ | ⚠️ 需门禁 |
| 销毁 | terraform destroy | ✅ | ❌ 极高风险 |
1.2 分层环境策略
├── environments/
│ ├── dev/ # 每次 PR 自动 plan + apply
│ ├── staging/ # main 分支 plan,审批后 apply
│ └── prod/ # Release/tag 触发,多层审批 apply
二、fmt / validate / plan 自动门禁
2.1 完整 CI 配置
name: Terraform CI
on:
pull_request:
paths:
- 'infra/**'
jobs:
validate:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/prod
permissions:
contents: read
pull-requests: write # 用于写 PR 评论
id-token: write # OIDC 云认证
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
terraform_wrapper: true # 开启 wrapper 以便捕获 plan 输出
- name: Terraform fmt
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
id: plan
run: terraform plan -no-color
continue-on-error: true # plan 失败不阻断,便于在评论中展示
- name: Post plan to PR
uses: actions/github-script@v7
env:
PLAN: "${{ steps.plan.outputs.stdout }}"
with:
script: |
const plan = process.env.PLAN || '';
const body = `#### Terraform Plan 📖\n\`\`\`hcl\n${plan}\n\`\`\``;
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
一句话:
terraform_wrapper: true让 plan 的 stdout 可以作为 step 输出被捕获,再通过github-script以评论形式贴回 PR——「plan 进 PR」是 IaC 审阅的起点。
2.2 plan 失败的处理
- name: Plan status
if: steps.plan.outputs.exitcode != 0
run: |
echo "::error::Terraform plan 存在错误,请检查评论内容"
exit 1
三、远程状态与锁
3.1 为什么需要远程状态
Terraform 将「期望状态 vs 实际资源」的映射保存在 state 文件中。本地 state 在多人与 CI 场景下必然冲突——远程 state 是团队协作的前提。
3.2 S3 + DynamoDB 后端
# infra/prod/backend.tf
terraform {
backend "s3" {
bucket = "my-org-tfstate"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks" # 锁表,防止并发 apply
}
}
3.3 锁与并发
| 场景 | 无锁 | 有锁(DynamoDB) |
|---|---|---|
| 两个 CI 并发 apply | 状态损坏、资源覆盖 | 后到者等待/失败 |
| plan 与 apply 冲突 | 读到过期状态 | 锁保护 |
| 人机并发 | 同上 | 同上 |
CI 中通过 concurrency 在 workflow 层再加一道防线:
concurrency:
group: terraform-prod-apply
cancel-in-progress: false
一句话:S3 存状态、DynamoDB 加锁、workflow
concurrency串行化——三层并发防线让「多人 + CI + 手动」同时操作也不会破坏状态。
四、plan 审阅门禁
4.1 把审阅做成流程
仅把 plan 贴在 PR 评论区还不够,必须让「审阅通过」成为 apply 的前置条件。推荐两种模式:
| 模式 | 机制 | 优点 | 缺点 |
|---|---|---|---|
| 环境审批门禁 | apply job 挂 environment,required reviewers 审批 | 原生、可审计 | 审批粒度是整个 job |
| 分支保护 + 评论确认 | 要求 terraform plan 检查通过 + 显式 /apply 评论 | 灵活 | 需 Atlantis 或自定义脚本 |
4.2 环境门禁示例
jobs:
apply:
runs-on: ubuntu-latest
environment:
name: production-tf
concurrency:
group: tf-apply-prod
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- name: Terraform Init & Plan
run: |
terraform init
terraform plan -out=tfplan
- name: Terraform Apply
run: terraform apply tfplan -auto-approve
4.3 apply 使用 plan 文件
-out=tfplan 是审阅门禁的关键配套:apply 的不是「重新计算的最新计划」,而是审阅过的那一份,避免「审阅后到 apply 之间状态又变化」造成偏差。
terraform plan -out=tfplan # 生成可审阅计划
terraform apply tfplan # 只执行该计划
五、Atlantis 机器人集成
5.1 Atlantis 是什么
Atlantis 是专为 Terraform 设计的 GitHub 机器人:监听 PR 评论,将 /atlantis plan、/atlantis apply 转换为真实执行,并把结果回贴到 PR。它天然把「审阅」与「执行」绑定在代码评审上下文里。
5.2 自托管 Atlantis + GitHub Actions 的两种模式
| 模式 | 说明 | 适用 |
|---|---|---|
| 独立 Atlantis 服务 | 自托管服务器 + webhook,独立于 Actions | 团队已有 Atlantis 基础设施 |
| Actions 模拟 plan 评论 | 用 Actions 执行 plan,用评论实现类 Atlantis 交互 | 不想引入额外服务 |
5.3 atlantis.yaml 配置
# atlantis.yaml(仓库根目录)
version: 3
projects:
- dir: infra/dev
workspace: default
autoplan:
enabled: true # PR 自动 plan
- dir: infra/prod
workspace: default
autoplan:
enabled: true
apply_requirements:
- approved # 需要 PR approval 才能 apply
- mergeable # 需要 PR 可合并
5.4 交互流程
开发者提交 PR → Atlantis 自动执行 plan 并评论
开发者评论 /atlantis plan → 重新 plan
审阅者 Approve PR
开发者评论 /atlantis apply → 执行 apply,评论结果
一句话:Atlantis 把「plan/apply」变成 PR 评论级的 ChatOps——
apply_requirements: [approved, mergeable]保证只有通过审阅且可合并的 PR 才能动基础设施。
六、CloudFormation 替代方案
6.1 CloudFormation + GitHub Actions
AWS 原生的 IaC 方案不需要独立 state 管理,直接用 Actions 编排:
name: CloudFormation Deploy
on:
push:
branches: [main]
paths: ['cloudformation/**']
jobs:
deploy-cfn:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/CFNDeployRole
aws-region: ap-southeast-1
- name: Validate template
run: |
aws cloudformation validate-template \
--template-body file://cloudformation/app.yaml
- name: Deploy stack
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: my-app-stack
template: cloudformation/app.yaml
capabilities: CAPABILITY_NAMED_IAM
no-fail-on-empty-changeset: "1"
6.2 Terraform vs CloudFormation 选型
| 维度 | Terraform | CloudFormation |
|---|---|---|
| 云厂商 | 多云(AWS/Azure/GCP) | 仅 AWS |
| 状态管理 | 自管(S3/锁) | 托管(ChangeSet) |
| 审阅体验 | plan 输出友好 | ChangeSet 查看需控制台 |
| 生态 | 巨大 Provider 生态 | AWS 原生函数完整 |
| 团队技能 | 需学 HCL | 需学 YAML/JSON + 伪参数 |
6.3 ChangeSet 审阅门禁
CloudFormation 对应「审阅计划」的概念是 ChangeSet:先生成、审阅通过后再执行:
- name: Create ChangeSet
run: |
aws cloudformation create-change-set \
--stack-name my-app \
--change-set-name review-$(date +%s) \
--template-body file://cloudformation/app.yaml
七、审批与回滚
7.1 回滚策略
| 层级 | 方式 | 适用 |
|---|---|---|
| Terraform 状态 | 恢复旧 state + apply | 可逆资源变更 |
| 镜像/代码 | 重新部署上一个版本 | 应用层问题 |
| 数据库 | 手动还原(需 DBA) | 数据迁移出错 |
7.2 用 tag 触发可回滚的发布
on:
push:
tags: ['release-*']
jobs:
tf-apply:
runs-on: ubuntu-latest
environment: production-tf
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }} # 精确 checkout tag 对应代码
- uses: hashicorp/setup-terraform@v3
- run: |
terraform init
terraform plan -out=tfplan
terraform apply tfplan -auto-approve
一句话:每次发布对应一个不可变 tag + 一个审阅过的 plan 文件——需要回滚时 checkout 旧 tag 重新 apply,即「基础设施版本化」。
7.3 危险操作双人确认
- name: Require explicit confirm
run: |
echo "确认销毁/重建操作?请在下游审批后再执行"
八、OIDC 临时凭证安全
8.1 为什么 IaC 最需要 OIDC
基础设施权限极大(可删库、可改网络),把 AWS Access Key 存为长期 Secrets 的风险不可接受。OIDC 让 workflow 通过短期 JWT 换取云凭证,凭证几分钟即过期。
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GHATerraformRole
aws-region: ap-southeast-1
role-duration-seconds: 900
8.2 IAM 信任策略收紧到 repo/分支
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:my-org/infra-repo:ref:refs/heads/main"
}
}
}
]
}
8.3 安全对比
| 凭证方案 | 生命周期 | 泄露影响 | 推荐度 |
|---|---|---|---|
| IAM 用户 Access Key | 长期 | 极大(可删基础设施) | ❌ |
| Secrets 存 AK/SK | 长期 | 极大 | ❌ |
| OIDC Role | 15 分钟 | 有限(单次会话) | ✅✅ |
8.4 IaC 安全清单
- 远程 state 加密 + 开启锁表
- 所有云凭证走 OIDC 短期凭证
- IAM trust policy 限定到
refs/heads/main - plan 产物必须经人审阅后才能 apply
- apply job 挂 environment + required reviewers
- 危险操作(destroy)单独高权限环境
- 生产 apply 设置
concurrency串行化 - 保留完整 plan/apply 审计日志
总结
IaC 自动化是对「自动化」与「审阅」的平衡艺术。
| 环节 | 手段 | 保障 |
|---|---|---|
| 质量门禁 | fmt + validate | 语法与格式正确 |
| 变更预览 | plan → PR 评论 | 变更可见可审 |
| 执行门禁 | environment + required reviewers | 审批后执行 |
| 状态安全 | S3 + DynamoDB 锁 + concurrency | 无并发破坏 |
| 凭证安全 | OIDC + 细粒度 trust policy | 短期、最小权限 |
| 回滚能力 | tag + 审阅过的 plan 文件 | 可版本化回退 |
一句话:让机器做 plan 把变更讲清楚,让人做 approve 把风险关住,让 apply 只执行审阅过的那一份计划——这就是基础设施变更的完整治理闭环。
延伸阅读:
- GitHub Actions OIDC 云认证 — AWS/Azure/GCP 免密认证
- GitHub Actions 环境保护与部署门禁 — apply 审批门禁
- GitHub Actions 安全与 Secret 管理 — 长期凭证加固
- GitHub Actions 自托管 Runner — Atlantis 独立服务部署
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。