手动登录云控制台创建服务器、数据库、网络——这种方式在资源少时还能勉强应付,一旦环境变复杂,就会出现"改不动、删不清、说不明"的困境。基础设施即代码(Infrastructure as Code, IaC)用代码描述基础设施,让资源的创建、修改和销毁变得可版本化、可审查、可复用。Terraform 是目前最主流的 IaC 工具,从理念到实战全面梳理。

参考来源:Terraform 官方文档

一、IaC 理念:声明式 vs 命令式

IaC 的核心思想是:用代码文件描述基础设施的期望状态,由工具自动驱动实际状态向期望状态收敛

在 IaC 领域,存在两种截然不同的范式:

维度声明式(Declarative)命令式(Imperative)
核心理念描述"要什么"(期望状态)描述"怎么做"(操作步骤)
代表工具Terraform、CloudFormation、PulumiAnsible(部分)、Shell 脚本、AWS CLI
幂等性天然幂等,重复执行结果一致需自行保证幂等性
状态感知工具追踪当前状态,自动计算差异无状态感知,按步骤执行
可读性接近配置文件,易于理解接近编程逻辑,灵活但复杂

Terraform 采用声明式范式。你只需声明"我需要 3 台 EC2、1 个 RDS、1 个 VPC",Terraform 会自动对比当前状态和期望状态的差异,生成并执行变更计划。

声明式的核心优势:幂等性。无论执行多少次 terraform apply,最终状态一致。这意味着你可以把基础设施代码纳入 Git 版本控制,通过 PR 审查变更,实现基础设施的"代码化治理"。

二、Terraform 核心概念

Terraform 的运作围绕四个核心概念展开:

开发者编写 .tf 文件
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   Provider    │ ←──→ │   Resource   │      │    State     │
│  云厂商插件    │      │  资源声明     │      │  状态文件     │
└──────────────┘      └──────────────┘      └──────────────┘
                                            ┌──────────────┐
                                            │    Module     │
                                            │  模块化复用    │
                                            └──────────────┘

2.1 Provider(提供者)

Provider 是 Terraform 与云厂商 API 之间的适配层。每个云厂商(AWS、GCP、Azure、阿里云等)都有对应的 Provider 插件,Terraform 通过它完成资源的 CRUD 操作。

2.2 Resource(资源)

Resource 是基础设施的最小描述单元。一台 EC2、一个 S3 桶、一条 DNS 记录,都是一个 Resource。每个 Resource 有类型、名称和属性块。

2.3 State(状态)

State 是 Terraform 的"记忆"。它记录了当前管理的所有资源的真实状态(ID、属性、关系)。每次 terraform apply 后,State 文件会被更新,用于下一次变更时的差异计算。State 是 Terraform 最关键也最容易出问题的组件,后文会重点讲解 State 管理策略。

2.4 Module(模块)

Module 是一组 Resource 的封装,类似于编程中的"函数"。你可以把通用的基础设施(如标准 VPC 架构)封装为 Module,在不同环境中复用。HashiCorp 官方维护了一个 Terraform Registry,提供大量社区 Module。

三、HCL 语法基础

HCL(HashiCorp Configuration Language)是 Terraform 专用配置语言。它的设计目标是"人类可读、机器可解析"。核心语法块如下:

3.1 完整的 HCL 文件结构

# ============================================
# versions.tf — Provider 版本约束
# ============================================
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# ============================================
# providers.tf — Provider 配置
# ============================================
provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Project   = "sre-wang"
      ManagedBy = "Terraform"
    }
  }
}

# ============================================
# variables.tf — 输入变量
# ============================================
variable "environment" {
  description = "部署环境名称"
  type        = string
  default     = "staging"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment 只能是 dev/staging/prod。"
  }
}

variable "instance_count" {
  description = "EC2 实例数量"
  type        = number
  default     = 2
}

# ============================================
# main.tf — 资源声明
# ============================================
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name = "web-${var.environment}-${count.index + 1}"
  }
}

# 数据源:查询最新 Amazon Linux AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

# ============================================
# outputs.tf — 输出值
# ============================================
output "instance_public_ips" {
  description = "EC2 公网 IP 列表"
  value       = aws_instance.web[*].public_ip
}

output "rds_endpoint" {
  description = "RDS 连接地址"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

3.2 关键语法要素

语法要素作用示例
resource声明基础设施资源resource "aws_instance" "web" {}
data查询已有资源(只读)data "aws_ami" "amazon_linux" {}
variable定义输入参数variable "environment" {}
output暴露输出值供外部引用output "instance_public_ips" {}
locals定义局部变量locals { common_tags = {} }
count / for_each资源批量创建count = 3
dynamic动态生成嵌套块dynamic "ingress" {}

HCL 支持丰富的内置函数(concatmergejsonencode 等)和表达式(三元运算、for 表达式、splat 运算符 [*]),完整列表参考 Terraform Functions 文档

四、State 管理策略

State 文件是 Terraform 的命脉——它记录了资源 ID、属性映射和依赖关系。如果 State 丢失或损坏,Terraform 将无法管理已创建的资源。

4.1 为什么不能用本地 State

默认情况下,Terraform 将 State 写入本地文件 terraform.tfstate。这在团队协作中会导致严重问题:

  • 冲突:多人同时 apply 会互相覆盖 State
  • 丢失:本地文件被误删后无法恢复
  • 泄露:State 文件中可能包含密码、密钥等敏感信息

4.2 远程后端:S3 + DynamoDB 锁

生产环境必须使用远程后端(Remote Backend)存储 State。AWS 上最经典的方案是 S3 存 State 文件 + DynamoDB 实现分布式锁

# ============================================
# backend.tf — 远程 State 配置
# ============================================
terraform {
  backend "s3" {
    bucket         = "sre-wang-terraform-state"
    key            = "infra/staging/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

对应的 S3 桶和 DynamoDB 表需要提前创建(这是"引导问题"——第一批基础设施总得手动创建):

# bootstrap.tf — 手动执行一次,创建 State 后端本身
resource "aws_s3_bucket" "terraform_state" {
  bucket = "sre-wang-terraform-state"

  lifecycle {
    prevent_destroy = true  # 防止误删 State 桶
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"  # 开启版本控制,支持回滚
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"  # 服务端加密
    }
  }
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

这套架构的工作流程:

terraform apply
┌─────────────────┐    获取锁    ┌──────────────┐
│  Terraform CLI   │ ──────────→ │   DynamoDB    │
│                  │             │  LockID 写入   │
└────────┬─────────┘             └──────────────┘
         │ 读取/写入 State
┌─────────────────┐
│   S3 Bucket      │
│ terraform.tfstate│  ← 版本控制 + 加密
└─────────────────┘
组件职责关键配置
S3存储 State 文件版本控制、服务端加密、prevent_destroy
DynamoDB分布式锁,防止并发写入LockID 作为分区键
encryptState 文件加密传输encrypt = true
keyState 文件路径,支持环境隔离infra/staging/terraform.tfstate

通过不同的 key 路径,可以为不同环境(dev/staging/prod)维护独立的 State 文件,实现环境隔离。

4.3 State 操作常用命令

# 查看 State 中所有资源
terraform state list

# 查看某个资源的详细信息
terraform state show aws_instance.web[0]

# 将资源从 State 中移除(不删除云上资源)
terraform state rm aws_instance.web[0]

# 将外部已存在的资源导入 Terraform 管理
terraform import aws_instance.web i-0123456789abcdef0

# 强制解锁(慎用!仅在锁卡住时使用)
terraform force-unlock <LOCK_ID>

terraform force-unlock 是危险操作,只有在确认没有其他 Terraform 进程在运行时才能使用。错误解锁会导致 State 文件损坏。

五、Module 模块化复用实战

当基础设施超过几十个资源时,把所有代码写在一个目录里会变得难以维护。Module 的作用是把相关资源打包成可复用单元。

5.1 目录结构

terraform/
├── modules/
   └── vpc/
       ├── main.tf          # 资源定义
       ├── variables.tf     # 模块输入参数
       ├── outputs.tf       # 模块输出值
       └── versions.tf      # 版本约束
├── environments/
   ├── staging/
      ├── main.tf          # 调用模块 + 环境特有资源
      ├── variables.tf     # 环境变量
      └── backend.tf       # State 后端配置
   └── prod/
       ├── main.tf
       ├── variables.tf
       └── backend.tf

5.2 Module 内部定义

# modules/vpc/variables.tf
variable "cidr" {
  type    = string
  default = "10.0.0.0/16"
}

variable "environment" {
  type = string
}

variable "availability_zones" {
  type    = list(string)
  default = ["ap-northeast-1a", "ap-northeast-1c"]
}
# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name        = "vpc-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_subnet" "public" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.cidr, 8, count.index)
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name = "subnet-public-${var.environment}-${count.index + 1}"
  }
}
# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

output "igw_id" {
  value = aws_internet_gateway.main.id
}

5.3 调用 Module

# environments/staging/main.tf
module "vpc" {
  source             = "../../modules/vpc"
  cidr               = "10.1.0.0/16"
  environment        = "staging"
  availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
}

使用 Module 后,新增一个环境只需复制目录、修改变量即可,无需重复编写 VPC 代码。这极大降低了环境间配置不一致的风险。

六、实战:AWS VPC + EC2 + RDS 完整部署

下面用一个完整示例,把前面所有概念串联起来——在 AWS 上创建一个包含 VPC、EC2 和 RDS 的 Web 应用基础架构。

# ============================================
# versions.tf
# ============================================
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket         = "sre-wang-terraform-state"
    key            = "infra/webapp/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# ============================================
# variables.tf
# ============================================
variable "environment" {
  type    = string
  default = "staging"
}

variable "db_password" {
  type      = string
  sensitive = true
}

# ============================================
# main.tf — VPC 网络
# ============================================
module "vpc" {
  source      = "../../modules/vpc"
  cidr        = "10.2.0.0/16"
  environment = var.environment
}

# 安全组:允许 Web 流量
resource "aws_security_group" "web" {
  name        = "sg-web-${var.environment}"
  vpc_id      = module.vpc.vpc_id
  description = "Web tier security group"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]  # 仅内网 SSH
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# 安全组:数据库(仅允许 Web SG 访问)
resource "aws_security_group" "db" {
  name        = "sg-db-${var.environment}"
  vpc_id      = module.vpc.vpc_id
  description = "Database security group"

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# ============================================
# main.tf — EC2 Web 服务器
# ============================================
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  count                       = 2
  ami                         = data.aws_ami.amazon_linux.id
  instance_type               = "t3.micro"
  subnet_id                   = module.vpc.public_subnet_ids[count.index]
  vpc_security_group_ids      = [aws_security_group.web.id]
  associate_public_ip_address = true

  user_data = <<-EOF
              #!/bin/bash
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>SRE.Wang Web Server ${count.index + 1}</h1>" > /var/www/html/index.html
              EOF

  tags = {
    Name = "web-${var.environment}-${count.index + 1}"
  }
}

# ============================================
# main.tf — RDS 数据库
# ============================================
resource "aws_db_subnet_group" "main" {
  name       = "db-subnet-group-${var.environment}"
  subnet_ids = module.vpc.public_subnet_ids
}

resource "aws_db_instance" "main" {
  engine                  = "mysql"
  engine_version          = "8.0"
  instance_class          = "db.t3.micro"
  allocated_storage       = 20
  storage_type            = "gp3"
  db_name                 = "webapp"
  username                = "admin"
  password                = var.db_password
  db_subnet_group_name    = aws_db_subnet_group.main.name
  vpc_security_group_ids  = [aws_security_group.db.id]
  skip_final_snapshot     = false
  backup_retention_period = 7

  tags = {
    Name = "rds-${var.environment}"
  }
}

# ============================================
# outputs.tf
# ============================================
output "web_public_ips" {
  description = "Web 服务器公网 IP"
  value       = aws_instance.web[*].public_ip
}

output "rds_endpoint" {
  description = "RDS 连接地址"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

执行流程

# 1. 初始化:下载 Provider 插件、配置后端
terraform init

# 2. 格式化检查
terraform fmt -check -recursive

# 3. 静态检查
terraform validate

# 4. 生成变更计划
terraform plan -out=tfplan

# 5. 应用变更
terraform apply tfplan

# 6. 销毁资源(测试后清理)
terraform destroy
# terraform plan 输出示例
Plan: 9 to add, 0 to change, 0 to destroy.

  # aws_instance.web[0] will be created
  + resource "aws_instance" "web" {
      + ami           = "ami-0d52744d6551d851e"
      + instance_type = "t3.micro"
      ...
    }

  # aws_db_instance.main will be created
  + resource "aws_db_instance" "main" {
      + engine         = "mysql"
      + instance_class = "db.t3.micro"
      ...
    }

terraform plan 是 Terraform 最有价值的能力之一——它在执行前展示完整的变更计划(+ 新建、~ 修改、- 销毁),让你在提交前审查变更,避免意外。

七、好的实践总结

维度实践要点
State 管理必须使用远程后端 + 加密 + 版本控制;按环境拆分 State 文件
Module 设计单一职责、输入参数校验、输出明确;优先复用 Registry 社区 Module
变量安全敏感值标记 sensitive = true,通过环境变量或 Secrets Manager 注入
版本控制required_version 锁定 Terraform 版本;required_providers 锁定 Provider 版本
代码组织versions.tf / variables.tf / main.tf / outputs.tf 分文件管理
CI 集成在 CI 中运行 terraform fmt -check + validate + plan,PR 审查后再 apply
防止误删关键资源加 lifecycle { prevent_destroy = true }
标签策略Provider 级 default_tags 统一标签,便于成本分摊和资源追踪

Terraform 的学习曲线主要在 HCL 语法和 State 管理上。一旦理解了"声明式 + 状态收敛"的核心理念,配合 Module 复用和远程 State,就能构建出可维护、可审计的基础设施代码库。建议从本文的 VPC + EC2 + RDS 示例开始动手实践,逐步把现有手动管理的基础设施迁移为 IaC。

延伸阅读

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Terraform 官方文档 — HashiCorp,参考了Terraform 官方文档相关内容
  2. Terraform Registry — Registry,参考了Terraform Registry相关内容
  3. Terraform Functions 文档 — HashiCorp,参考了Terraform Functions 文档相关内容
  4. Terraform Best Practices — HashiCorp,参考了Terraform Best Practices相关内容