当你手里的 VPS 从一台变成五台、从一家云厂商迁到另一家、还要反复搭建"几乎一样的环境"时,问题就从"怎么装一款软件"升级成了"怎么让几十台机器的配置保持一致、还可追溯"。手工在控制台点选、或者写一堆只在当下有效的 shell 脚本,时间一长就会失控:谁改了什么、为什么这么配、哪台配置漂移了,全都说不清。Terraform 换了一种思路:把"基础设施长什么样子"用代码描述出来,让服务器的创建、网络、域名、甚至应用编排都变成一份可版本管理、可评审、可回滚的声明。这篇讲清 Terraform 的核心思想、最小上手、状态管理,以及把 IaC 真正用好的几个关键习惯。
一、为什么要"基础设施即代码":声明式带来的三个改变
传统运维(包括很多人目前用 shell 脚本)是"命令式"的:我一步步告诉它"先建 VPS,再装 Nginx,再改配置",中间任何一步失败就要重来。Terraform 是"声明式"的:我描述"最终状态应该长这样"——有 3 台实例、开放 80/443 端口、DNS 指向某个地址,Terraform 自己去算出"从当前状态到目标状态需要做哪些操作",只做有差异的部分。
这个差异带来三个直接影响。一是可重复:同一份代码在任意环境跑,结果一致,不会因为"上次手抖点了两下"产生偏差。二是可评审:代码进了 Git,每次改动都是一次 PR,能 review、能回溯"这台机器是谁、何时、因为什么改的"。三是可回滚:声明式让"把环境恢复到某个以前的状态"成为可能,而手工变更几乎做不到。相比之前聊的 Ansible(它更偏向"先把已有的机器配置好"),Terraform 更偏向"把整片基础设施的蓝图管起来"——两者可以配合,但职责不同。
二、三个核心概念:资源、Provider、State
理解 Terraform 只要抓住三个词。第一个是"资源(resource)":基础设施里每一件能被创建/修改/删除的东西,一台服务器是资源,一个防火墙规则是资源,一条 DNS 记录也是资源。第二个是"Provider":负责"某个云服务商或者某类平台"的资源到底怎么调度的适配层,阿里云、腾讯云、AWS、Kubernetes 甚至是 Cloudflare 都各有官方 Provider,选谁就管理谁的资源。
第三个概念最关键,也是 Terraform 和"跑完就忘的脚本"的本质区别——"状态(state)"。Terraform 会把"当前这个环境实际长什么样"记录在一个 state 文件里,每次执行都是拿"代码描述的目标状态"去对比"state 里记录的当前状态",算出差异再执行。可以这么理解:代码是"你想成为的样子",state 是"你现在的样子",terraform plan 就是"中间那张改造成本清单"。
# 一个最简单的 Terraform 文件(main.tf):声明一台服务器
resource "alicloud_instance" "web" {
instance_name = "web-01"
instance_type = "ecs.t5-lc1m1.small"
image_id = "aliyun_3_x64"
security_groups = [alicloud_security_group.allow_web.id]
}
resource "alicloud_security_group_rule" "web_80" {
security_group_id = alicloud_security_group.allow_web.id
type = "ingress"
ip_protocol = "tcp"
port_range = "80/443"
cidr_ip = "0.0.0.0/0"
}
看懂这段就抓住了资源语法:`resource "类型" "名字"` 定义一块资源,块里的键值就是它的参数;`alicloud_security_group.allow_web.id` 这种写法表示"引用另一个资源的输出",Terraform 据此自动安排创建顺序——先建安全组,再建实例。这正是声明式的魅力:你不用手写依赖顺序,让工具去图编排(dependency graph)。
三、最小上手:init → plan → apply 三部曲
Terraform 的命令行工作流是固定的三步,初学把它当肌肉记忆就好。`terraform init` 下载你声明里用到的 Provider 插件并在本地初始化(在写好 main.tf 的目录里执行);`terraform plan` 只做"预演"——算出将要创建/修改/删除什么,不真改任何东西,这是你 review 改动的地方;`terraform apply` 才真正执行 plan 算出的变更,并更新 state。
# 安装 & 初始化
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp.gpg
# 用官方 apt 仓库安装 terraform(Debian/Ubuntu)
sudo apt-add-repository "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt update && sudo apt install terraform
# 项目三连:先看一眼差异(plan),确认无误再执行(apply)
terraform init
terraform plan
terraform apply -auto-approve
一个很容易忽略却很重要的体重习惯:`apply` 之前永远先看 `plan` 的输出。特别是当你第一次使用某个 Provider 时,plan 会打印"将要创建的每件东西的清单",逐项扫一眼能避免大量"我以为只是加台机器,结果把安全组规则也改了"的意外。无论是加资源、改参数还是加 Provider,养成"plan 先行"的习惯,失误率会明显下降。
四、真正的问题在状态:从本地 state 到远程 state 加锁
很多第一次踩坑的人发现:state 文件默认存在本地(terraform.tfstate),一个人玩没问题,一旦多人协作、或者从 CI 里跑,本地 state 就会出乱子——两个人同时 apply,后写的把先写的覆盖掉。正确做法是把 state 放到"远程后端(backend)",让所有人读写同一份 state,并且后端能提供"加锁(locking)":一个人正在 apply 时,其他人无法同时改,避免竞争。
# backend.tf:把 state 放到一个远程后端(这里是阿里云 OSS)
terraform {
backend "oss" {
bucket = "my-terraform-state"
prefix = "prod/backend"
region = "cn-hangzhou"
}
}
迁移到远程后端之后有几点要同步排查。一是"锁"——OSS 后端用对象锁实现抢占,彼此串行修改,这解决"两个人撞车";二是"版本"——很多后端支持保留 state 的每次版本,出问题时能回滚到上一个版本;三是"别把 state 混进 Git"——state 里可能含有资源的敏感属性(虽然云资源密码通常不会明文出现,但 IP、ID 等也算敏感),默认要把它加进 .gitignore,让远程后端成为唯一权威。
五、让配置可复用:变量、输出与 Module
写死的值会让代码僵硬。Terraform 用"变量(var)"把经常变化的参数抽象出来,用"输出(output)"把创建后的结果(比如新机器的公网 IP)暴露出来供别处使用。一个环境一个 environments 文件,就能让同一份代码跑在 dev / prod 而不改逻辑。
# variables.tf
variable "instance_count" {
description = "要创建多少台 web 实例"
type = number
default = 1
}
# 使用变量:计数组件
resource "alicloud_instance" "web" {
count = var.instance_count
instance_name = "web-${count.index}"
# ...
}
# outputs.tf:把结果交给外面
output "web_public_ip" {
value = alicloud_instance.web[*].public_ip
}
当"同一个用途的资源组合"在多个项目反复出现时,就该抽成"模块(module)"。模块就是"一组可被打包和复用的 Terraform 文件",它有自己的变量和输出,像"一个能传参调用的函数"。你可以在自己项目里建 `modules/web`,也可以直接用社区维护的官方模块。抽模块判断标准:同一段资源定义在 ≥2 个项目里以相近方式出现时,就值得抽,而不是为了抽而抽。
六、变更安全:看守"危险操作"的红线
声明式的另一面是"声明错了就想删"。Terraform 的 apply 会算出"需要删除的东西"——如果一份写错的历史配置要让 destroy 才能改,它真的会删。这是 IaC 最需要规矩的地方,也是我建议所有人都重视的三条红线:
第一条,destructive 操作要可控。`terraform apply` 默认允许删除资源,改动安全边界(比如安全组、VPC)前一定要在 plan 里确认"删掉了什么"。第二条,关键资源加 `lifecycle` 保护。对数据库、存储这类"删了找不回"的东西,用 `prevent_destroy` 让 Terraform 拒绝直接删除它:
# 对"删不起"的资源加保护:禁止 Terraform 直接 destroy
resource "alicloud_disk" "data" {
# ...
lifecycle {
prevent_destroy = true
}
}
第三条,敏感信息不进代码和 state 明文。密码、密钥这类用变量从环境或 secret 管理工具读取,而不是硬编码在 .tf 里;Provider 也支持把敏感参数标记为 sensitive,apply 输出时不打印明文。把这些养成肌肉记忆,"改配置改出生产事故"的概率会小很多。
七、和 Ansible 分工:Terraform 管蓝图,配置管理管内部状态
很多人纠结"Terraform 和 Ansible 是不是二选一"。其实它们是互补的:Terraform 管的是"基础设施本体的蓝图"——要几台机器、什么规格、开哪些端口、DNS 指哪;而一台机器建成之后"内部状态长什么样"(装了什么包、服务怎么配、用户怎么建),是 Ansible 这类配置管理工具的活。典型流水线是:Terraform 创建实例并拿到 IP → 把 IP 交给 Ansible 去在里面装环境和部署应用。
# 可以把 Terraform 的 output 直接交给 Ansible:
# ansible-playbook -i "web-01" deploy.yml (配合 inventory 动态取 IP)
把一个清晰的分层丢进脑子会舒服很多:Terraform answer"世界长什么样",Ansible answer"机器里是什么样"。当你的副本从几台涨到几十台,这个分层会越来越值钱。
结语
Terraform 的价值不在"省去了点控制台的功夫",而在把"基础设施的现状"从"某个人脑记忆 + 一堆命令历史"变成"一份可评审、可追溯、可回滚的代码"。落地要点:抓住资源/Provider/state 三个概念理解它在算什么;把状态放到远程后端并加锁,避免多人协作踩坑;用变量和模块让配置复用;对删不起的资源加 prevent_destroy;敏感信息不进明文。定义好自己的边界——Terraform 管蓝图、Ansible 管机器内状态——然后从"把第一份 main.tf 用 Git 管起来"开始,你的多机运维就正式升级成了可以被协同和回溯的工程而不是手艺。




