2214 字
11 分钟

Terraform 基础设施即代码完全指南 2026:核心语法 + 状态管理 + 模块化 + CI/CD 集成

手动在 AWS 控制台点点点创建资源,下次根本不记得当时的配置;团队协作时,谁也不知道 VPC 的安全组规则是谁改的、为什么改。基础设施即代码(IaC)解决这些问题——用 Git 管理基础设施,像代码一样 review、版本控制、自动化部署。

Terraform 是目前最流行的 IaC 工具,本文覆盖从入门到生产级实践的完整路径。


快速决策表#

场景推荐方案
创建云资源(VM / VPC / 数据库)Terraform
服务器内部配置(安装软件 / 配置文件)Ansible
状态文件存储(团队协作)S3 + DynamoDB 远程 Backend
多环境管理(大型团队)目录分离 + Terragrunt
迁移已有手动创建的资源terraform import
偏好用编程语言写 IaCPulumi

一、HCL 核心语法#

main.tf
# ── 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(团队协作必备)#

backend.tf
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"
}
}
Terminal window
# 创建 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.tf
environments/production/terraform.tfvars
aws_region = "ap-northeast-1"
environment = "production"
project_name = "myapp"
instance_type = "t3.medium"
Terminal window
# 切换环境操作
cd environments/production
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars"

四、模块化封装#

modules/ec2/main.tf
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"]
}
}

六、导入现有资源#

Terminal window
# ── terraform import(Terraform 1.5+ 支持 import 块)──────────
# 传统命令行方式(仍可用)
terraform import aws_instance.web i-0a1b2c3d4e5f67890
# ── 推荐:import 块(1.5+,可在 plan 中预览,更安全)──────────
import.tf
import {
id = "i-0a1b2c3d4e5f67890"
to = aws_instance.web
}
# 可以先用 terraform plan 确认 import 内容,再 apply
# terraform plan → 查看将要导入的资源
# terraform apply → 执行导入,将资源纳入 state 管理
Terminal window
# ── moved 块:重命名/移动资源(不销毁重建)──────────────────────
moved.tf
# 将 aws_instance.old_name 重命名为 aws_instance.new_name
moved {
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 集成#

.github/workflows/terraform.yml
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

八、常用命令速查#

Terminal window
# ── 基础工作流 ──────────────────────────────────────────────────
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 # 下载并显示当前 state
terraform 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 文件(强制重新解析版本)

相关文章

本文基于 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/
作者
九所长
发布于
2026-08-12
许可协议
CC BY-NC-SA 4.0