#DevOps

基础设施即代码:Terraform 实战

手点云控制台建资源?慢、不可复现、易出错。基础设施即代码(IaC)用代码描述资源,Terraform 是主力。本文讲清 Terraform 的声明式模型、核心命令(plan/apply/destroy)、state 管理、模块化、以及生产级的 state 后端与团队协作。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年5月9日·更新于 2026年7月23日

基础设施即代码:Terraform 实战

如果你还在登录 AWS 控制台,点十几个表单建一个 VPC、几个子网、一组安全组、一台 RDS——然后下次还要在测试环境照着再来一遍,那你一定体会过那种"明明建过却记不清参数"的绝望。手点控制台的问题不是慢,是不可复现、不可审计、不可回滚。

基础设施即代码(Infrastructure as Code, IaC)就是把云资源用代码描述出来,然后用一个工具帮你建、改、删。Terraform 是这条赛道里最主流、最成熟的一个。这篇文章不讲花活,只讲一个工程师真正要用 Terraform 上生产时必须吃透的那几件事:声明式模型、核心命令、state、模块化、远程后端,以及那些踩过才记得住的坑。

一、为什么需要 IaC:告别手点控制台

先说价值,不然你不会有动力学。手点控制台建资源这种方式,业内有个半正式的贬称叫 ClickOps,它的代价是隐性的,但极其昂贵:

  • 慢:建一套带 VPC + 子网 + NAT + RDS + 安全组 + IAM 的标准环境,熟练的人也要点半小时。Terraform apply 一次大约 3 分钟。
  • 不可复现:你建了个 dev 环境跑得不错,老板说要一套一模一样的 staging。你照着点,结果 staging 的安全组少开了一个端口,RDS 版本差了一个小号,然后线上出 bug 你排查两天才发现是环境不一致。
  • 不可审计:三个月后问"谁在什么时候给这个 S3 桶开了 public 访问",控制台查不到。代码在 Git 里,每一行都有 commit、作者、时间。
  • 不可回滚:手点改坏了配置,你想退回上一个状态——没有"上一个状态"这回事。代码里 git checkout 一下,重新 apply 就回去了。
  • 没法接 CI/CD:基础设施变更要走评审、要走流水线,前提是它是代码。

一句话:代码即文档,代码即审计,代码即回滚。一旦你尝过"改一行代码,环境自动同步"的甜头,就再也回不去 ClickOps 了。

二、Terraform 的声明式模型

Terraform 用一种叫 HCL(HashiCorp Configuration Language)的语言来描述资源。它最核心的思想是声明式——你告诉它"我想要什么状态",它自己算出"怎么从当前状态到达目标"。

对比一下:

  • 命令式:"先建 VPC,再建 3 个子网,再把子网关联到路由表……"——你关心的是步骤。
  • 声明式:"我要一个 VPC,里面包含 3 个子网。"——你关心的是结果,步骤 Terraform 自己排。

看一段最小可用的 .tf:

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

resource "aws_s3_bucket" "assets" {
  bucket = "my-app-assets-prod-9f3k2"   # 全局唯一,记得加随机后缀

  tags = {
    Name        = "assets"
    Environment = "prod"
    ManagedBy   = "terraform"
  }
}

这段代码描述了"我要一个 S3 桶,名字是 xxx,带这几个标签"。你 apply 之后它存在;你再把它改成 bucket = "..." 删掉一行 tag,apply 之后 Terraform 会精确地把那个 tag 移除,别的都不动。这就是声明式的威力——它只做"让现实匹配代码"所必需的变更,不做多余操作。

三、核心命令:init / plan / apply / destroy

四个命令,背下来,日常就是这套循环。

bash
terraform init      # 初始化:下载 provider 插件、配置后端
terraform plan      # 预演:算出要改什么,但不真改(关键!)
terraform apply     # 执行:真改,会再让你确认一次
terraform destroy   # 销毁:把这套资源全删掉(慎用)

init 是一次性的环境准备:它根据 required_providers 下载 AWS/Azure/GCP 的 provider 插件,根据 backend 配置连上远程 state 存储。换一台机器拉代码,第一件事就是 init。

plan 是 Terraform 最有价值的命令,没有之一。它对比"代码描述的目标状态"和"state 文件记录的当前状态",算出一份 diff:哪些资源要新建(+)、哪些要改(~)、哪些要销毁(-),然后打印给你看。任何一次 apply 之前,先 plan,看懂那个 diff 再说。我见过无数事故都是因为 apply 时没看 plan,结果误删了一个生产数据库。

bash
terraform plan
# 输出大概长这样:
#   # aws_s3_bucket.assets will be created
#   + resource "aws_s3_bucket" "assets" {
#       + bucket = "my-app-assets-prod-9f3k2"
#       ...
#     }
# Plan: 1 to add, 0 to change, 0 to destroy.

apply 真正执行变更。它会再弹一次确认提示,输入 yes 才动手。在 CI 里通常用 -auto-approve,但生产环境强烈建议保留人工确认,或者把 apply 拆成单独一道审批关卡。

destroy 把这套代码管理的资源全部删掉。本地实验收尾、环境销毁时用。生产环境轻易不要跑,而且最好把 destroy 的权限单独隔离开,防止误触。

四、state:基础设施的"真相之源"

这是 Terraform 最容易被新手忽略、却最容易出事的部分。

Terraform 怎么知道现实世界里有哪些资源、它们当前是什么状态?它不是每次都去云上 API 实时查(太慢、云也受不了),而是维护一个 state 文件(terraform.tfstate),里面是一份 JSON 格式的资源清单:每个资源在云上的真实 ID、属性、依赖关系。

state 的作用:它是 plan 算 diff 时的"当前状态"基准。Terraform 拿"代码里的目标状态"和"state 里的当前状态"对比,算出 diff,然后去云上执行,执行完再更新 state。

所以 state 极其重要,有两条铁律:

  1. 别丢。丢了 state,你就失去了对那批资源的"遥控"——Terraform 找不到它们了,云上却还真实存在,变成一笔糊涂账(要么手动 import 回来,要么手动去控制台删)。state 要像代码一样进版本控制(或者更安全,放带版本化的对象存储)。
  2. 别冲突。两个人同时改同一份 state,后写的会覆盖先写的。这就是为什么本地开发还行,团队协作一定要上远程后端 + 锁(下一节讲)。

看一下 state 长什么样:

bash
terraform state list                # 列出 state 里所有资源
terraform state show aws_s3_bucket.assets   # 看某个资源的细节
terraform refresh                   # 主动去云上同步一次最新状态(谨慎)

重要观念:state 不是缓存,是事实来源(source of truth)。它记录的是 Terraform 上一次 apply 之后认为的"现实",不一定是云上此刻的真实状态——这就引出了下一节的"漂移"问题。

五、模块化:把可复用资源组合起来

写一阵子你会发现,一个项目的 main.tf 越来越长,VPC + 子网 + NAT + 路由表 + 安全组 + RDS + EC2 + IAM 全堆一起,三四百行,改一处怕动全身。

这时候就该上 module 了。模块就是把一组相关资源打包成一个可复用的单元,暴露输入变量、输出结果。比如"一个标准 VPC(带 2 公 2 私子网 + NAT)"这种组合,每个环境都要用,封成一个模块就一劳永逸。

目录结构通常长这样:

text
modules/
  vpc/
    main.tf       # 资源定义
    variables.tf  # 输入参数
    outputs.tf    # 输出值
environments/
  prod/
    main.tf       # 这里调用 module

modules/vpc/main.tf(简化版):

hcl
resource "aws_vpc" "this" {
  cidr_block           = var.cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = { Name = "${var.name}-vpc" }
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnets)
  vpc_id            = aws_vpc.this.id
  cidr_block        = var.public_subnets[count.index]
  availability_zone = var.azs[count.index]
  tags = { Name = "${var.name}-public-${count.index}" }
}

environments/prod/main.tf 调用它:

hcl
module "vpc" {
  source         = "../../modules/vpc"
  name           = "prod"
  cidr           = "10.20.0.0/16"
  public_subnets = ["10.20.1.0/24", "10.20.2.0/24"]
  azs            = ["ap-northeast-1a", "ap-northeast-1c"]
}

resource "aws_db_instance" "main" {
  allocated_storage = 100
  engine            = "postgres"
  instance_class    = "db.t4g.medium"
  db_subnet_group_name = module.vpc.db_subnet_group_name   # 用模块的输出
}

模块化的真正价值不是"少写代码",而是抽象出稳定的环境骨架,让 dev/staging/prod 共用同一套模板、只差参数。这样环境之间的一致性是结构性的,不靠人盯。

社区已经有大量现成模块,比如 AWS 官方的 terraform-aws-modules/vpc/aws,质量高、坑都踩过,生产环境优先用经过验证的开源模块,别从零造轮子。

六、生产级 state 后端:S3 + DynamoDB 锁

本地开发,state 默认存在当前目录的 terraform.tfstate,够用。但团队协作绝不能这样:文件在谁本地、谁就独占,state 没法共享,也没有并发保护。

生产标配是远程后端 + 状态锁。AWS 上最经典的组合是 S3 存 state + DynamoDB 做分布式锁:

hcl
terraform {
  backend "s3" {
    bucket         = "my-tf-state-prod-9f3k2"
    key            = "prod/network/terraform.tfstate"   # state 在桶里的路径
    region         = "ap-northeast-1"
    dynamodb_table = "tf-locks"                         # 锁表
    encrypt        = true                               # 服务端加密
  }
}

机制是这样的:

  • S3 集中存 state,所有团队成员、所有 CI 节点都从同一处读写,大家看到的是同一份真相。
  • DynamoDB 锁:某人 apply 时,Terraform 先在 DynamoDB 写一条锁记录;这段时间别人 plan / apply 会直接被拦下来,提示"state 正被占用"。apply 完成自动释放锁。这就从根本上杜绝了并发改 state 互相覆盖的事故。
  • encrypt = true:state 在 S3 服务端加密。这一点必须开,因为下一节会讲——state 里可能有密钥。

不想自己维护 S3 + DynamoDB 的小团队,可以直接用 Terraform Cloud(HashiCorp 托管),免费版就提供远程 state + 锁 + 变更历史,零运维。这是个很实际的选项。

七、常见坑:漂移、import、敏感值

这一节是踩过的人才写得出来的。

坑一:state 漂移(Drift)。state 记录的是 Terraform "以为"的状态,但如果有人绕过 Terraform 直接去控制台改了资源(比如手改了一个安全组规则,或者另一套流水线改了同一个 RDS),state 和现实就脱节了。下次 plan 你会看到 Terraform 想把那个资源"改回去",因为代码和 state 还在描述老状态。

应对:生产环境严禁绕过 Terraform 改资源(靠流程和权限隔离),定期 terraform plan 做"漂移检测",发现不一致要么把代码改成现实,要么手动纠正现实回代码。

坑二:已有资源怎么纳管(import)。绝大多数团队的现状是:云上已经有一堆手点出来的资源,现在想用 Terraform 接管。你写好代码直接 apply?Terraform 会以为这些资源不存在、要新建,结果因为名字冲突报错。

正确做法是 import:

bash
terraform import aws_s3_bucket.assets my-app-assets-prod-9f3k2

这会把云上已有的桶"登记"进 state,但它不会生成代码。你还得手写 .tf 把这个桶的属性描述出来,直到 terraform plan 显示 No changes——这就表示代码和现实对齐了,纳管成功。这个过程枯燥但必须做,Terraform 1.5+ 引入了 import block 可以在代码里声明式导入并自动生成配置,能省不少事。

坑三:state 里有敏感值。这是新手最容易栽的大坑。你在 RDS 资源里写了 password = var.db_password,terraform.tfstate 里会原样存下这个密码的明文(Terraform 没法分辨哪些属性算敏感)。如果 state 进了 Git 明文仓库,或者 S3 没加密,密钥就泄露了。

应对:

  • 永远开启 state 的服务端加密(encrypt = true)
  • state 桶设严格 IAM 权限,绝不进 Git(.gitignore 里加 *.tfstate)
  • 代码里给敏感变量加 sensitive = true,这样 plan / apply 输出会打码(但 state 里还是明文!这点要清楚)
  • 真正的密钥从专门的 secret manager(AWS Secrets Manager / Vault)读,Terraform 只引用、不存储

八、选型:Terraform vs Pulumi vs CDK

最后聊聊选型,因为 IaC 不止 Terraform 一条路。

Terraform:HCL 声明式,多云覆盖最全(AWS/Azure/GCP/阿里云 + 上百家 SaaS 都有官方 provider),生态最成熟,文档最全,多云环境、团队希望统一工具链时的默认选择。缺点是 HCL 不是通用编程语言,写复杂逻辑(循环、条件)时会别扭,没法写单元测试。

Pulumi:用真编程语言写基础设施(TypeScript / Python / Go / C#)。能 for 循环、能写函数、能用熟悉的 IDE 和测试框架。对不愿意学 HCL、又已经有强编程能力的团队非常友好。生态不如 Terraform 全,但主流云都覆盖。

AWS CDK / Azure Bicep 等云原生方案:云厂商自己的 IaC 工具,和该云的集成最深、新功能支持最快,但绑死单一云。只用一家云、且深度使用其高级特性的团队可以考虑。

我的实用建议:多云或追求最大生态,选 Terraform;单团队、强工程文化、讨厌 DSL,选 Pulumi;深度绑定单一云,选厂商原生工具。对绝大多数团队,Terraform 仍然是稳妥的默认——它的成熟度、招聘市场、社区模块,都是另两者短期难追的护城河。

小结

  • IaC 的价值是可复现、可审计、可回滚、快。ClickOps 的代价不在当下,在三个月后排查环境差异的那个深夜。
  • Terraform 是声明式:你描述目标状态,它算出 diff 自己执行。plan 看懂变更、再 apply,是基本纪律。
  • state 是真相之源,不是缓存。别丢、别冲突——团队协作必上远程后端(S3 + DynamoDB 锁 / Terraform Cloud)。
  • 模块化是把"环境骨架"抽象出来,让多环境共用模板、只差参数;生产环境优先用社区验证过的开源模块。
  • 三大坑要记住:漂移(定期 plan 检测)、import(纳管已有资源,代码与现实现实对齐才算成功)、敏感值(state 里是明文,加密 + 隔离 + 引用 secret manager)。

一句话收尾:Terraform 的核心不是 HCL 语法,而是"把基础设施当代码管理"的纪律。会写 .tf 不算入门,知道怎么保护 state、怎么避免漂移、怎么在团队里推行,才是真的把 IaC 用对了。

Related

相关推荐