从点点点到基础设施即代码
最早的云资源都是在控制台里手动点出来的:开一台虚拟机、配一块云盘、拉一条负载均衡。资源少的时候没问题,一旦到了几十台机器、三四个环境(开发/测试/预发/生产),「谁在什么时候改了什么」就彻底成了谜。同事离职带走了脑子里的那张资源拓扑图,新环境要照着旧环境复刻一遍只能靠截图。
基础设施即代码(IaC)要解决的就是这件事:把服务器、网络、数据库这些云上资源,也写成代码放进 Git。Terraform 是这个方向最主流的工具之一——它和 Ansible 思路相通(都是声明式),但 Ansible 管的是「机器里面装了什么」,Terraform 管的是「云上面开了什么」。
点点点出来的基础设施是一团没有图纸的迷雾;写成代码的基础设施,每一次变更都有版本、有评审、可回滚。
- 声明式你只写「我要几台什么规格的机器」,Terraform 负责把现实对齐到这个描述。
- 状态文件Terraform 用一个 state 文件记录「代码里的资源」和「云上真实资源」的对应关系。
- plan/apply先 dry-run 预览会改什么,确认无误再真的执行,是它最安全的工作流。
- 模块化把一套常用的基础设施打包成模块,多环境复用。
声明式:描述你要的最终样子
Terraform 用自己的语言 HCL 写配置。下面是一段最小编排:开一台云服务器、挂一块云盘。
provider "alicloud" {
region = "cn-shenzhen"
}
resource "alicloud_instance" "web" {
instance_type = "ecs.g6.large"
image_id = "ubuntu_22_04"
count = 2
}
resource "alicloud_disk" "data" {
size = 100
count = 2
}
注意这里写的不是「第一步创建机器,第二步创建硬盘」,而是「我要两台 g6.large 规格的机器、每台配 100G 云盘」。至于先建谁后建谁、依赖怎么处理,是 Terraform 自己根据资源间的引用关系推导的。这就是声明式和命令式脚本最大的区别:你描述目标,它负责过程。
再加新环境时,你不需要从零写一遍,只要改改 count、规格这些变量,再 apply 一次就行。想要销毁整套环境,一条命令 terraform destroy 就把这套代码开过的所有资源原路清掉——再也不用在控制台里一个个找。
状态文件:Terraform 的记忆
Terraform 怎么知道「这台云服务器是我管的、那台不是」?答案是一个 state 文件(默认叫 terraform.tfstate)。它是 Terraform 的记忆:把你代码里的每一个 resource,和云平台上真实的那台资源 ID 一一对应起来。
# 项目目录里长这样
main.tf # 你写的资源声明
terraform.tfstate # 自动生成的状态记录
.terraform/ # 本地插件缓存
state 文件带来两条铁律,都是踩过坑才记住的:第一,绝对不要把 state 文件提交进 Git,更不要手动改它——它里面有敏感信息,而且一旦损坏,Terraform 就认不出自己管过的资源了。第二,多人协作时,state 必须存在远端(对象存储或 Terraform Cloud),并上锁——两个人同时 apply,一个人的变更会把另一个人的状态覆盖掉。
| 状态存放方式 | 适用场景 | 风险 |
|---|---|---|
| 本地 state 文件 | 个人练习、单机器小项目 | 不能多人协作,易丢 |
| 远端对象存储 + 锁 | 团队协作、生产环境 | 需正确配置后端 |
| Terraform Cloud | 想免运维 state 后端 | 数据在第三方托管 |
plan 与 apply:先预览再动手
Terraform 最让人安心的设计,就是它从不偷偷改东西。整个工作流分三步,第二步是强制的刹车:
# 1. 初始化:下载 provider 插件、配置后端
terraform init
# 2. 预览:它会算出「代码和现状的差异」,列出会新增/修改/销毁哪些资源
terraform plan
# 3. 确认无误后,真的执行
terraform apply
plan 的输出会明确告诉你:哪些资源会新增(绿色)、哪些会修改(黄色)、哪些会被销毁(红色)。看到红色的 destroy 行,一定要停下来想清楚——是不是变量改错了、是不是引用关系断了,才导致 Terraform 以为要把这台机器删了重建。很多生产事故,就是 apply 前没认真看 plan 里那几行红色。
把这套流程接进 CI/CD:合并代码到主分支时自动跑 plan 并把结果贴到评审里,运维同学 review 通过后才在流水线里自动 apply。基础设施变更从此和代码变更走同一条评审流,这是 IaC 真正的价值兑现。
模块化:把可复用基建打包
当你发现「每次开新环境都要重写一遍那套 VPC、子网、安全组」,就该抽 模块 了。一个模块就是一个目录,对外暴露输入变量、输出关键信息:
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
az_count = 2
}
module "web_cluster" {
source = "./modules/web-cluster"
vpc_id = module.vpc.vpc_id
instance_count = 3
instance_type = "ecs.g6.large"
}
用起来就像调函数:告诉它要几条可用区、要几台机器,它把 VPC、子网、安全组、负载均衡一整套都搭好,再把生成的 VPC ID 输出出来给下一个模块用。模块让你把「踩过坑的最佳实践」沉淀成别人一行就能复用的东西。小团队起步时别追求抽象,先把最稳定、最常复用的那部分(网络、基础机器规格)包出来就够了。
多云与工作区管理
用 Terraform 的一个现实好处是多云:阿里云、AWS、腾讯云只是不同的 provider 插件,资源声明的写法几乎一致。同一个团队想把部分业务从 A 云迁到 B 云,换一段 provider 配置,而不是重学一套控制台。
多环境(dev/staging/prod)怎么隔离?推荐用 workspace:同一份代码,靠 workspace 切成多套独立的状态。
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# 切到生产环境再 apply,状态互不干扰
terraform workspace select prod
terraform plan
环境之间的差异(机器台数、规格、是否开公网),通过变量文件 dev.tfvars、prod.tfvars 区分。一份代码,多环境变量,既保证了环境一致性,又保留了各自差异——这正是它比「手动复制三套配置」靠谱的地方。
一套能落地的 Terraform 目录
落到能上手的最小仓库,推荐这个目录骨架:
infra-repo/
├── modules/
│ ├── vpc/ # 网络:VPC、子网、安全组
│ └── web-cluster/ # 一组机器 + 负载均衡
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── dev.tfvars
│ └── prod/
│ ├── main.tf
│ └── prod.tfvars
└── README.md # 写清楚怎么 init/plan/apply
几个新人最容易踩的坑提前记好:第一,生产环境的密钥和凭证别写进代码,用环境变量或专门的密钥管理服务注入 provider。第二,刚接手一个线上环境时,别一上来就 terraform apply——先用 terraform import 把已有的手工资源纳管进来,否则 Terraform 会以为这些资源不存在,给你新建一份重复的。第三,plan 里出现你不认识的 destroy,停下来问,别手快敲 yes。
Terraform 的学习曲线比想象中平缓:把它当成「云上资源的 Git」就对了。先挑一个非核心的小环境,把那几台手动开的机器用代码重建一遍,尝到「改代码就是改基础设施、评审过了才生效」的甜头,再逐步把生产纳管。基础设施即代码的终点,是让开环境这件事从一次运维事故,变成一次普通的代码合并。