Terraform 还是 CDK
选 Terraform
- 团队里运维背景多于开发背景
- 需要管理多云或云外资源(如 DNS、SaaS 配置)
- 偏好声明式配置,不想引入编程语言的复杂度
- 生态成熟,Module 丰富,社区资料多
选 CDK
- 团队以开发为主,熟悉 TypeScript 或 Python
- 需要复杂的条件逻辑与抽象复用
- 主要在 AWS 生态内,深度使用原生服务
- 希望用同一语言写基础设施与应用代码
Terraform 目录结构与模块化
infrastructure/
├── modules/ # 可复用模块,不含环境特定值
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md # 每个模块必须有用法说明
│ ├── eks-cluster/
│ ├── rds-instance/
│ ├── alb-service/
│ └── observability/
│
├── environments/ # 环境入口,只做模块编排与参数注入
│ ├── prod/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ ├── backend.tf # 独立 state,独立账号
│ │ └── providers.tf
│ ├── staging/
│ └── dev/
│
├── global/ # 跨环境共享资源
│ ├── organizations/ # 账号结构与 SCP
│ ├── iam-identity-center/ # SSO 权限集
│ └── route53-zones/ # 公共 DNS
│
├── policies/ # 策略即代码
│ ├── sentinel/ # 或 OPA / Checkov 规则
│ └── checkov-baseline.yaml
│
├── scripts/
│ ├── plan-all.sh # 批量 plan,用于 CI 检查
│ └── drift-detect.sh # 定期检测配置漂移
│
└── .github/workflows/
├── terraform-plan.yml # PR 时自动 plan 并评论
└── terraform-apply.yml # 合并后 apply,需人工批准
关键约定:
1. 每个环境独立 state 文件,独立 AWS 账号,物理隔离
2. state 存 S3 + DynamoDB 锁,开启版本控制与加密
3. 生产环境 apply 必须走 CI,禁止本地执行
4. 模块版本用 git tag 锁定,不用 main 分支
5. 敏感值走 Secrets Manager 或 SSM,不进代码库
6. 每周跑一次 drift 检测,发现手工改动立即告警
CI 中的 plan 与策略检查
name: Terraform Plan
on:
pull_request:
paths: ['infrastructure/**']
permissions:
contents: read
pull-requests: write
id-token: write
jobs:
plan:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
env: [dev, staging, prod]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8 # 锁定版本,避免行为差异
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets[format('TF_PLAN_ROLE_{0}', matrix.env)] }}
aws-region: ap-northeast-1
- name: Format check
run: terraform fmt -check -recursive
working-directory: infrastructure
- name: Init
run: terraform init -input=false
working-directory: infrastructure/environments/${{ matrix.env }}
- name: Validate
run: terraform validate
working-directory: infrastructure/environments/${{ matrix.env }}
- name: Plan
id: plan
run: |
terraform plan -input=false -no-color -out=tfplan \
| tee plan.txt
terraform show -json tfplan > plan.json
working-directory: infrastructure/environments/${{ matrix.env }}
# 静态安全扫描:拦截公开访问、缺失加密等问题
- name: Checkov scan
uses: bridgecrewio/checkov-action@master
with:
file: infrastructure/environments/${{ matrix.env }}/plan.json
framework: terraform_plan
soft_fail: false
config_file: infrastructure/policies/checkov-baseline.yaml
# 检测破坏性变更:删除数据库、S3 桶这类操作需要人工确认
- name: Check destructive changes
run: |
DESTROY_COUNT=$(jq '[.resource_changes[]
| select(.change.actions | index("delete"))
| select(.type | test("aws_(db_instance|s3_bucket|rds_cluster|efs_file_system)"))]
| length' plan.json)
if [ "$DESTROY_COUNT" -gt 0 ]; then
echo "::warning::检测到 $DESTROY_COUNT 个有状态资源将被删除,需要人工确认"
jq -r '.resource_changes[]
| select(.change.actions | index("delete"))
| " - " + .address' plan.json
fi
working-directory: infrastructure/environments/${{ matrix.env }}
- name: Comment plan on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync(
'infrastructure/environments/${{ matrix.env }}/plan.txt', 'utf8'
);
const truncated = plan.length > 60000
? plan.slice(0, 60000) + '\n... (输出过长已截断)'
: plan;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `### Terraform Plan · \`${{ matrix.env }}\`\n\n<details><summary>展开查看</summary>\n\n\`\`\`hcl\n${truncated}\n\`\`\`\n\n</details>`
});
破坏性变更检测这一步很关键。Terraform 的 plan 输出很长,人容易看漏一行「will be destroyed」。