2214 字
11 分钟
Terraform 基础设施即代码完全指南 2026:核心语法 + 状态管理 + 模块化 + CI/CD 集成
手动在 AWS 控制台点点点创建资源,下次根本不记得当时的配置;团队协作时,谁也不知道 VPC 的安全组规则是谁改的、为什么改。基础设施即代码(IaC)解决这些问题——用 Git 管理基础设施,像代码一样 review、版本控制、自动化部署。
Terraform 是目前最流行的 IaC 工具,本文覆盖从入门到生产级实践的完整路径。
快速决策表
| 场景 | 推荐方案 |
|---|---|
| 创建云资源(VM / VPC / 数据库) | Terraform |
| 服务器内部配置(安装软件 / 配置文件) | Ansible |
| 状态文件存储(团队协作) | S3 + DynamoDB 远程 Backend |
| 多环境管理(大型团队) | 目录分离 + Terragrunt |
| 迁移已有手动创建的资源 | terraform import |
| 偏好用编程语言写 IaC | Pulumi |
一、HCL 核心语法
# ── Provider 配置 ──────────────────────────────────────────────terraform { required_version = ">= 1.9.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.60" # 允许 5.60.x,不允许 6.x } }}
provider "aws" { region = var.aws_region profile = "myprofile" # ~/.aws/credentials 中的 profile
default_tags { # 所有资源自动加上这些标签 tags = { Project = var.project_name Environment = var.environment ManagedBy = "terraform" } }}
# ── 变量 ────────────────────────────────────────────────────────variable "aws_region" { type = string description = "AWS 部署区域" default = "ap-northeast-1"}
variable "environment" { type = string description = "部署环境" validation { condition = contains(["dev", "staging", "production"], var.environment) error_message = "environment 必须是 dev / staging / production 之一" }}
variable "instance_type" { type = string default = "t3.micro"}
variable "project_name" { type = string default = "myapp"}
# ── Locals(本地计算值)──────────────────────────────────────────locals { name_prefix = "${var.project_name}-${var.environment}"
common_tags = { CreatedAt = timestamp() }
# 根据环境选择不同配置 instance_config = { dev = { type = "t3.micro", count = 1 } staging = { type = "t3.small", count = 1 } production = { type = "t3.medium", count = 3 } } current_config = local.instance_config[var.environment]}
# ── 数据源(读取已有资源,不创建)──────────────────────────────data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-*-x86_64"] }}
data "aws_vpc" "default" { default = true}
data "aws_subnets" "public" { filter { name = "vpc-id" values = [data.aws_vpc.default.id] }}
# ── 资源 ────────────────────────────────────────────────────────resource "aws_security_group" "web" { name = "${local.name_prefix}-web-sg" description = "Web server security group" vpc_id = data.aws_vpc.default.id
ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }}
resource "aws_instance" "web" { count = local.current_config.count ami = data.aws_ami.amazon_linux.id instance_type = local.current_config.type subnet_id = data.aws_subnets.public.ids[count.index % length(data.aws_subnets.public.ids)]
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(templatefile("${path.module}/scripts/init.sh.tpl", { environment = var.environment app_name = var.project_name }))
tags = { Name = "${local.name_prefix}-web-${count.index + 1}" }}
# ── 输出 ────────────────────────────────────────────────────────output "instance_ids" { description = "Web 服务器实例 ID 列表" value = aws_instance.web[*].id}
output "public_ips" { description = "公网 IP 列表" value = aws_instance.web[*].public_ip}
output "security_group_id" { description = "安全组 ID" value = aws_security_group.web.id sensitive = false}二、远程 Backend(团队协作必备)
terraform { backend "s3" { bucket = "mycompany-terraform-state" key = "myapp/production/terraform.tfstate" region = "ap-northeast-1" encrypt = true # 加密存储
# DynamoDB 状态锁(防止并发 apply) dynamodb_table = "terraform-state-lock" }}# 创建 S3 Bucket 和 DynamoDB 表(一次性手动执行)aws s3api create-bucket \ --bucket mycompany-terraform-state \ --region ap-northeast-1 \ --create-bucket-configuration LocationConstraint=ap-northeast-1
aws s3api put-bucket-versioning \ --bucket mycompany-terraform-state \ --versioning-configuration Status=Enabled
aws s3api put-bucket-encryption \ --bucket mycompany-terraform-state \ --server-side-encryption-configuration \ '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
aws dynamodb create-table \ --table-name terraform-state-lock \ --attribute-definitions AttributeName=LockID,AttributeType=S \ --key-schema AttributeName=LockID,KeyType=HASH \ --billing-mode PAY_PER_REQUEST \ --region ap-northeast-1三、变量文件与多环境管理
# 推荐目录结构(目录分离方案)infrastructure/├── modules/ # 可复用模块│ ├── vpc/│ ├── ec2/│ └── rds/└── environments/ ├── dev/ │ ├── main.tf # 调用 modules │ ├── variables.tf │ ├── terraform.tfvars # 非敏感变量值 │ └── backend.tf # dev 专用 state ├── staging/ │ ├── main.tf │ ├── terraform.tfvars │ └── backend.tf └── production/ ├── main.tf ├── terraform.tfvars └── backend.tfaws_region = "ap-northeast-1"environment = "production"project_name = "myapp"instance_type = "t3.medium"# 切换环境操作cd environments/productionterraform initterraform plan -var-file="terraform.tfvars"terraform apply -var-file="terraform.tfvars"四、模块化封装
variable "name_prefix" { type = string }variable "instance_type" { type = string }variable "ami_id" { type = string }variable "subnet_ids" { type = list(string) }variable "sg_ids" { type = list(string) }variable "instance_count" { type = number; default = 1 }
resource "aws_instance" "this" { count = var.instance_count ami = var.ami_id instance_type = var.instance_type subnet_id = var.subnet_ids[count.index % length(var.subnet_ids)] vpc_security_group_ids = var.sg_ids tags = { Name = "${var.name_prefix}-${count.index + 1}" }}
output "instance_ids" { value = aws_instance.this[*].id }output "private_ips" { value = aws_instance.this[*].private_ip }# environments/production/main.tf(调用模块)module "web_servers" { source = "../../modules/ec2" # 本地模块路径
name_prefix = "myapp-prod" instance_type = "t3.medium" ami_id = data.aws_ami.amazon_linux.id subnet_ids = data.aws_subnets.public.ids sg_ids = [aws_security_group.web.id] instance_count = 3}
# 也可以使用 Terraform Registry 的公共模块module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0"
name = "myapp-prod-vpc" cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true single_nat_gateway = false # 每个 AZ 一个 NAT(生产推荐)}五、for_each 与 dynamic 高级用法
# ── for_each:用 map 创建多个相似资源 ─────────────────────────variable "buckets" { type = map(object({ versioning = bool lifecycle_days = number })) default = { "assets" = { versioning = true, lifecycle_days = 365 } "logs" = { versioning = false, lifecycle_days = 30 } "backups" = { versioning = true, lifecycle_days = 180 } }}
resource "aws_s3_bucket" "buckets" { for_each = var.buckets bucket = "mycompany-${each.key}-${var.environment}" tags = { Name = each.key, Purpose = each.key }}
resource "aws_s3_bucket_versioning" "buckets" { for_each = { for k, v in var.buckets : k => v if v.versioning } bucket = aws_s3_bucket.buckets[each.key].id versioning_configuration { status = "Enabled" }}
# ── for 表达式:列表/映射转换 ─────────────────────────────────locals { # 从实例列表生成 id → ip 的映射 instance_map = { for inst in aws_instance.web : inst.id => inst.public_ip }
# 过滤并转换 large_instances = [ for inst in aws_instance.web : inst.id if inst.instance_type != "t3.micro" ]}
# ── dynamic 块:动态生成嵌套块 ────────────────────────────────variable "ingress_rules" { type = list(object({ port = number protocol = string cidr_blocks = list(string) description = string })) default = [ { port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"], description = "HTTP" }, { port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"], description = "HTTPS" }, { port = 22, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"], description = "SSH 内网" }, ]}
resource "aws_security_group" "dynamic_sg" { name = "dynamic-sg" vpc_id = data.aws_vpc.default.id
dynamic "ingress" { for_each = var.ingress_rules content { from_port = ingress.value.port to_port = ingress.value.port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks description = ingress.value.description } }
egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }}六、导入现有资源
# ── terraform import(Terraform 1.5+ 支持 import 块)──────────
# 传统命令行方式(仍可用)terraform import aws_instance.web i-0a1b2c3d4e5f67890
# ── 推荐:import 块(1.5+,可在 plan 中预览,更安全)──────────import { id = "i-0a1b2c3d4e5f67890" to = aws_instance.web}
# 可以先用 terraform plan 确认 import 内容,再 apply# terraform plan → 查看将要导入的资源# terraform apply → 执行导入,将资源纳入 state 管理# ── moved 块:重命名/移动资源(不销毁重建)──────────────────────# 将 aws_instance.old_name 重命名为 aws_instance.new_namemoved { from = aws_instance.old_name to = aws_instance.new_name}
# 将模块内资源移动到另一个模块moved { from = module.legacy.aws_s3_bucket.data to = module.storage.aws_s3_bucket.data}七、GitHub Actions CI/CD 集成
name: Terraform CI/CD
on: push: branches: [main] paths: ["infrastructure/**"] pull_request: branches: [main] paths: ["infrastructure/**"]
permissions: contents: read pull-requests: write # 在 PR 上写 plan 评论 id-token: write # OIDC 认证 AWS
env: TF_VERSION: "1.9.4" AWS_REGION: "ap-northeast-1" WORKING_DIR: "infrastructure/environments/production"
jobs: terraform: name: Terraform Plan & Apply runs-on: ubuntu-24.04 defaults: run: working-directory: ${{ env.WORKING_DIR }}
steps: - uses: actions/checkout@v4
# OIDC 认证(无需 Access Key) - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/terraform-github-role aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: ${{ env.TF_VERSION }}
- name: Terraform Init run: terraform init
- name: Terraform Format Check run: terraform fmt -check -recursive
- name: Terraform Validate run: terraform validate
# Plan(每次 PR 触发) - name: Terraform Plan id: plan run: | terraform plan \ -var-file="terraform.tfvars" \ -no-color \ -out=tfplan continue-on-error: true
# 将 plan 结果评论到 PR - name: Comment Plan on PR uses: actions/github-script@v7 if: github.event_name == 'pull_request' with: script: | const output = `### Terraform Plan \`\`\` ${{ steps.plan.outputs.stdout }} \`\`\` *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: output })
# 仅 main 分支 push 时自动 Apply - name: Terraform Apply if: github.ref == 'refs/heads/main' && github.event_name == 'push' run: terraform apply -auto-approve tfplan八、常用命令速查
# ── 基础工作流 ──────────────────────────────────────────────────terraform init # 初始化(下载 Provider 和模块)terraform init -upgrade # 升级 Provider 到最新版terraform fmt -recursive # 格式化所有 .tf 文件terraform validate # 验证语法和逻辑terraform plan # 预览变更(不执行)terraform plan -out=tfplan # 保存 plan 到文件terraform apply # 执行变更(会提示确认)terraform apply -auto-approve # 跳过确认(CI/CD 中使用)terraform apply tfplan # 执行指定 plan 文件terraform destroy # 销毁所有资源(⚠️ 危险)
# ── State 管理 ──────────────────────────────────────────────────terraform state list # 列出所有 state 中的资源terraform state show aws_instance.web # 查看特定资源详情terraform state mv src dst # 重命名资源(不推荐,用 moved 块)terraform state rm aws_instance.web # 从 state 移除(不销毁资源)terraform state pull # 下载并显示当前 stateterraform state push terraform.tfstate # 上传 state(⚠️ 谨慎)
# ── 调试 ────────────────────────────────────────────────────────terraform output # 查看所有输出值terraform output public_ips # 查看特定输出TF_LOG=DEBUG terraform plan # 开启详细日志terraform graph | dot -Tsvg > graph.svg # 生成资源依赖图
# ── 清理 ────────────────────────────────────────────────────────rm -rf .terraform/ # 删除本地 Provider 缓存(重新 init)rm -f .terraform.lock.hcl # 删除 lock 文件(强制重新解析版本)相关文章:
- GitHub Actions CI/CD 完全指南 2026:自动化构建 + 测试 + 部署
- Kubernetes 入门完全指南 2026:核心概念 + kubectl + Helm
- Linux 服务器监控完全指南 2026:Prometheus + Grafana + Alertmanager
- Docker 完全指南 2026:Compose 多服务编排与生产部署
- Nginx 进阶完全指南 2026:反向代理 + HTTPS + 负载均衡
本文基于 Terraform 1.9.x / AWS Provider 5.60.x 编写。
import块和moved块需要 Terraform 1.5+。Terraform Cloud 免费版支持最多5个用户和无限工作区,是小团队远程 Backend 的好选择。
Terraform 基础设施即代码完全指南 2026:核心语法 + 状态管理 + 模块化 + CI/CD 集成
https://971918.xyz/posts/docs/terraform-infrastructure-guide/