Terraform 基础设施即代码
让 Agent 按远程状态后端、工作区或目录分环境、可复用模块与变量校验编写 Terraform,并强调 plan 评审与破坏性变更标注。
SKILL 定义 provider 版本锁定、required_version、以及 .tfvars 与 CI 注入敏感变量的方式;禁止在仓库中提交明文密钥。
模块接口:输入输出文档、moved/import 块在重构时的使用;lifecycle 与 prevent_destroy 对关键资源的保护策略写清。
与策略即代码(OPA/Sentinel)或 cost 估算工具的集成若存在,说明 Agent 需在 PR 描述中附带 plan 摘要要点。
- 格式化:
terraform fmt、validate、tflint 在流水线中的顺序。 - 状态:锁、漂移检测与手动
state rm的审批流程。 - 多云/多 region:provider alias 与模块调用的目录约定。
IaC 主流程(skill-flow-block)
[ 锁定:terraform / provider / 模块 source 版本 ]
│
▼
[ 变量:类型约束、敏感标记、.tfvars / CI 密钥注入 ]
│
▼
[ fmt → validate →(可选 tflint)→ plan -out=tfplan ]
│
┌────────┴────────┐
▼ ▼
[ 人类或策略:审 plan 摘要、标 destroy/replace ] [ 合并阻塞项:未批准不改状态 ]
│ │
└────────┬────────┘
▼
[ apply 限定:指定 plan 文件 / 受控环境 / 回滚说明 ]
-destroy、replace、或敏感资源变更时,必须在说明中引用对应资源地址与风险。
状态:远程后端、锁与漂移
-
远程后端:在 SKILL 中写清 bucket / table / workspace 前缀或等价资源名规则;禁止多人共用会互相覆盖的本地
terraform.tfstate作为事实来源。 - 状态锁:说明 CI 与本地并发时的锁超时、失败重试与「谁可 force-unlock」的审批;Agent 不应默认建议强制解锁而不经人工确认。
-
漂移:约定周期或发布前的
plan无变更基线;发现非 Terraform 管理侧修改时,记录是 import、刷新还是人工对齐。 -
危险操作:
state rm、state mv、替换 provider 或后端迁移须附检查清单与回滚路径。
远程 backend 配置(S3 + DynamoDB 锁)与变量类型约束示例:
# backend.tf — S3 远程状态 + DynamoDB 分布式锁
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # 锁定大版本,小版本可升级
}
}
backend "s3" {
bucket = "mycompany-terraform-state"
key = "services/myapp/production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "mycompany-terraform-locks" # 防并发写
# 角色假设(OIDC + role)替代长期 key
role_arn = "arn:aws:iam::123456789012:role/TerraformStateRole"
}
}
# variables.tf — 类型约束与 validation block
variable "environment" {
type = string
description = "部署目标环境(dev/staging/production)"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "environment 必须是 dev、staging 或 production 之一。"
}
}
variable "instance_count" {
type = number
description = "EC2 实例数量(1-20)"
default = 1
validation {
condition = var.instance_count >= 1 && var.instance_count <= 20
error_message = "instance_count 必须在 1 到 20 之间。"
}
}
variable "allowed_cidr_blocks" {
type = list(string)
description = "允许访问的 CIDR 列表"
default = []
validation {
condition = alltrue([
for cidr in var.allowed_cidr_blocks :
can(cidrhost(cidr, 0))
])
error_message = "allowed_cidr_blocks 中的每个值必须是合法的 CIDR 格式。"
}
}
模块:接口、版本与重构
-
接口:在
variables.tf/outputs.tf用描述字段文档化必填项、默认值与破坏性变更;跨团队模块建议发布语义化版本或固定 Git ref。 - 组合:根模块编排环境差异(dev/stage/prod)时,优先目录或 workspace 策略二选一并写死,避免同一套 root 隐式混用。
-
重构:使用
moved、import块减少无意义 destroy/create;大范围重命名在 PR 中附 state 迁移或分步计划。 -
关键资源:对数据库、证书、生产入口等使用
lifecycle { prevent_destroy = true }等护栏时,在 SKILL 中列出例外审批流程。
Terraform module 完整示例(inputs / outputs / main.tf):
# modules/ec2-service/variables.tf
variable "service_name" {
type = string
description = "服务名称,用于资源命名前缀"
}
variable "environment" {
type = string
description = "部署环境(dev/staging/production)"
}
variable "instance_type" {
type = string
description = "EC2 实例类型"
default = "t3.micro"
}
variable "subnet_ids" {
type = list(string)
description = "部署的子网 ID 列表(至少 2 个可用区)"
}
# modules/ec2-service/main.tf
resource "aws_security_group" "this" {
name = "${var.service_name}-${var.environment}"
description = "Security group for ${var.service_name}"
lifecycle {
create_before_destroy = true # 先创新 SG,再删旧的,保证零停机
}
tags = {
Name = "${var.service_name}-${var.environment}"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_instance" "this" {
ami = data.aws_ami.al2023.id
instance_type = var.instance_type
subnet_id = var.subnet_ids[0]
vpc_security_group_ids = [aws_security_group.this.id]
lifecycle {
prevent_destroy = true # 生产关键资源护栏;例外审批流程见 runbook
ignore_changes = [ami] # AMI 更新由重建流程管理,不在 plan 中体现
}
}
# modules/ec2-service/outputs.tf
output "instance_id" {
description = "EC2 实例 ID"
value = aws_instance.this.id
}
output "security_group_id" {
description = "安全组 ID,供上层模块引用"
value = aws_security_group.this.id
sensitive = false
}
# 根模块调用示例(environments/production/main.tf)
# module "web_service" {
# source = "../../modules/ec2-service"
# # 版本控制:固定 Git ref 或发布 tag
# # source = "git::https://github.com/myorg/tf-modules.git//ec2-service?ref=v1.2.0"
#
# service_name = "web"
# environment = "production"
# instance_type = "t3.medium"
# subnet_ids = module.vpc.private_subnet_ids
# }
流水线:fmt / validate / plan
- 顺序建议:
terraform fmt -check→terraform validate→(可选)tflint / tfsec → 非交互plan(只读凭证或 mock)。 - PR 工件:保存 plan 文本摘要或结构化输出,便于审查机器人与人类对齐「变更条数 / destroy 计数」。
- 敏感输出:对含密钥的 plan 使用 CI 密文区或脱敏展示;禁止将完整 state 或明文 secret 贴进评论。
Terraform plan 的 CI 集成与 infracost 成本估算:
# .github/workflows/terraform.yml
jobs:
plan:
runs-on: ubuntu-24.04
permissions:
id-token: write
contents: read
pull-requests: write # 用于评论 plan 摘要
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.0"
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.TF_PLAN_ROLE_ARN }} # 只读角色
aws-region: us-east-1
- name: Terraform fmt check
run: terraform fmt -check -recursive
- name: Terraform validate
run: terraform validate
- name: tflint
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.50.0
- run: tflint --recursive
- name: Terraform plan
id: plan
run: |
terraform plan \
-out=tfplan \
-no-color \
-input=false \
2>&1 | tee plan.txt
# 提取摘要:变更数与 destroy 数
echo "summary=$(grep -E 'Plan:|No changes' plan.txt | tail -1)" >> "$GITHUB_OUTPUT"
- name: Infracost cost estimate
uses: infracost/actions/setup@v3
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
- run: |
infracost breakdown --path tfplan --format json > infracost.json
infracost comment github \
--path infracost.json \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--pull-request ${{ github.event.pull_request.number }} \
--behavior update
- name: Comment plan on PR
uses: marocchino/sticky-pull-request-comment@v2
with:
message: |
### Terraform Plan Summary
${{ steps.plan.outputs.summary }}
<details><summary>Full plan</summary>
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
</details>
Workspace 与资源地址校验
左侧输入 workspace 名(常用字母、数字、-、_)或 资源地址(如 module.vpc.aws_subnet.private[0])。解析在浏览器本地完成,不上传。
Workspace:非空、长度 ≤ 256、仅 [A-Za-z0-9_-]。资源地址:无点号时按 workspace 校验;含点号时为 类型.本地名、module…类型.本地名 或恰好三段的 data.类型.本地名;各段可为标识符加可选的 [非负整数]。
---
name: terraform-iac
description: 模块化 Terraform、远程状态与 plan/apply 安全习惯
tags: [terraform, iac, devops, aws]
---
# 状态管理
1. 远程后端:S3 bucket + DynamoDB table 实现状态存储与分布式锁
2. 后端 role_arn 通过 OIDC 获取,禁止长期 AWS Access Key 进 CI
3. 每个环境(dev/staging/prod)使用独立 state key,禁止共用
4. 漂移检测:周期或发布前 plan,无变更为基线;非 TF 修改必须 import 或对齐
# 模块设计
5. variables.tf:description 文档化必填项、类型约束与 validation block
6. outputs.tf:sensitive = true 标记敏感输出,不在 plan 中明文显示
7. lifecycle.prevent_destroy = true 保护生产关键资源(DB、证书)
8. moved 块重命名资源,避免 destroy/create;import 块纳管已有资源
# CI 流水线
9. 顺序:terraform fmt -check → validate → tflint → plan -out=tfplan
10. plan 摘要(变更数/destroy 数)评论到 PR,人工审批后才能 apply
11. infracost breakdown 估算成本变化,自动评论到 PR
12. apply 必须指定 plan 文件(terraform apply tfplan),禁止无 plan apply
# 安全与治理
13. 禁止明文 secret 在 .tf 文件或 plan 输出中出现
14. sensitive = true 变量不进 plan 文本,敏感 state 字段通过 CI 密文展示
15. state rm/mv 须附检查清单与回滚路径,经过人工审批
16. tfsec / checkov 扫描安全配置问题(如公开 S3 bucket、未加密存储)