我刚学 Terraform 时干过一件蠢事:看 terraform.tfstate 像个临时文件就顺手删了,下一次 apply,它把整套 VPC、交换机和 ECS 原封不动又建了一遍,还因重名卡在半空。
这篇把安装、云插件、第一个 ECS 与 init/plan/apply/destroy 四个命令串成一条闭环。走完你会明白 Terraform 到底在「对比」什么,也能拿到一份可反复创建、干净销毁的最小配置。
一、场景:手工点控制台,迟早要还的
删状态文件之所以是事故,根子在于我压根没理解 Terraform 靠什么记住资源------这个状态(State)就是它对真实世界的全部记录。要讲清这件事,得先回到一个更前置的问题:明明控制台点几下就能开机器,为什么还要写代码?
手工运维的痛点,和基础设施即代码(Infrastructure as Code,IaC)的价值,恰好一一对应:
| 手工点控制台 | Terraform 声明式配置 |
|---|---|
| 操作只在某个人脑子里,换人就断线 | 配置即代码,能进 Git、能 Review、能回滚 |
| 十台机器点出十种状态,环境漂移 | 同一份配置在任何环境跑出相同结果 |
| 改了什么全凭记忆,出事无法追溯 | 每次变更先出 diff,看清改什么再执行 |
Terraform 是 HashiCorp 出品的基础设施即代码工具,用声明式配置描述「想要的最终状态」,再由它把云资源创建、修改、销毁成这个状态。它不关心操作顺序,只关心「最终应该长什么样」。
工具定位先分清,才不会拿它干不擅长的活:
| 工具 | 定位 | 关注对象 |
|---|---|---|
| Terraform | 资源编排,调云厂商 API | 资源从生到死的生命周期 |
| Ansible | 配置管理,登录机器执行任务 | 机器内部的状态 |
| CloudFormation | 资源编排,能力近似 | 绑定 AWS 单一云 |
| Pulumi | 资源编排,用通用语言写配置 | 灵活但门槛更高 |
适用边界也很清楚:
- 适合:计算、网络、存储、数据库、DNS、权限等资源生命周期,多云统一编排与环境批量复制。
- 不适合:机器内部的精细化配置(交给 Ansible 或 cloud-init),以及一条命令就能搞定的一次性临时操作。

一句话记住这套心智模型:描述状态、对比差异、执行变更。手工脚本的创建与删除往往要各写一套逻辑,而 Terraform 一份配置双向可用。
二、一个二进制文件,装起来也有三道坎
Terraform 没有依赖库,安装就是解压、放进 PATH,卸载就是删文件。既然这么简单,为什么我敲 terraform 还是提示 command not found?问题几乎都出在「放进 PATH」这一步。
你先到 releases.hashicorp.com 确认最新版本号,替换下面的 1.16.5,再按平台选一种方式。
| 平台 | 推荐方式 | 架构包 |
|---|---|---|
| Linux | 二进制 / apt / yum | x86_64 用 amd64,ARM 用 arm64 |
| macOS | Homebrew / 二进制 | Intel 用 amd64,M 系列用 arm64 |
| Windows | 手动解压 / Chocolatey | 统一 windows_amd64 |
Linux:通用二进制
任意发行版都能用,下载、解压进 PATH、清理安装包:
bash
wget https://releases.hashicorp.com/terraform/1.16.5/terraform_1.16.5_linux_amd64.zip
sudo unzip terraform_1.16.5_linux_amd64.zip -d /usr/local/bin/
rm terraform_1.16.5_linux_amd64.zip
Linux:包管理器(便于后续升级)
Debian/Ubuntu 添加官方 apt 源,以后 apt upgrade 自动跟随新版本:
bash
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
RHEL/CentOS/Alibaba Cloud Linux 添加官方 yum 源:
bash
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum install -y terraform
macOS
有 Homebrew 就直接装,它会自动处理架构:
bash
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
不装 Homebrew 就手动下载二进制。Apple Silicon(M1/M2/M3)选 arm64,Intel 芯片把 arm64 换成 amd64:
bash
curl -LO https://releases.hashicorp.com/terraform/1.16.5/terraform_1.16.5_darwin_arm64.zip
unzip terraform_1.16.5_darwin_arm64.zip
sudo mkdir -p /usr/local/bin
sudo mv terraform /usr/local/bin/
Windows
手动安装以管理员身份打开 PowerShell:下载 zip、解压到固定目录、写入系统 PATH。
powershell
$version = "1.16.5"
$installDir = "C:\Program Files\Terraform"
Invoke-WebRequest "https://releases.hashicorp.com/terraform/$version/terraform_${version}_windows_amd64.zip" -OutFile terraform.zip
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
Expand-Archive -Path terraform.zip -DestinationPath $installDir -Force
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($machinePath -notlike "*$installDir*") {
[Environment]::SetEnvironmentVariable("Path", "$machinePath;$installDir", "Machine")
}
不想敲命令也可以走图形界面,到 官方安装页 下载 Windows 版 ZIP 包:解压出 terraform.exe 放进固定目录(如 C:\Program Files\Terraform),再到「设置 → 系统 → 关于 → 高级系统设置 → 环境变量」,把该目录追加到系统变量 Path。已安装 Chocolatey,则以管理员身份运行 PowerShell 后执行:
powershell
choco install terraform
无论哪种方式,装完都要重开一个终端,PATH 才会生效。
多版本管理
多个项目用不同版本时,用 tfenv 切换,避免「这台能跑、那台跑不了」的版本漂移:
bash
tfenv install 1.16.5
tfenv use 1.16.5
验证安装
bash
terraform version
latex
Terraform v1.16.5 on linux_amd64
装不上时的报错基本是下面四类,按报错对号入座:
| 报错信息 | 原因 | 处理 |
|---|---|---|
command not found |
解压目录没进 PATH,或终端没重开 | 加 PATH 后重开终端 |
Exec format error |
架构包下错(amd64/arm64 不符) | 重新下载对应架构包 |
Permission denied |
二进制缺执行权限 | chmod +x terraform |
unzip: command not found |
系统没装 unzip | apt install unzip 或 yum install unzip |
注意:架构包必须和 CPU 对应------x86_64 用 linux_amd64,鲲鹏、树莓派等 ARM 机器用 linux_arm64,下错了不会在安装时报错,只会在运行时扔下一句 Exec format error。
版本号能打印了,但此时的 Terraform 还只是一台空转的机器。它凭什么能去操作阿里云的 API?
三、原理:提供者才是它操作云的那只手
答案是提供者(Provider)。提供者是 Terraform 与云厂商之间的插件,把阿里云几千个 API 封装成 Terraform 能理解的资源类型。没有提供者,Terraform 就只剩一台空转的「状态对比机」。
新建一个空目录 ~/tf-first,在里面创建 main.tf,在 terraform 块里声明要用的提供者:
plain
terraform {
required_version = ">= 1.5"
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.278.0"
}
}
}
provider "alicloud" {
region = "cn-hangzhou"
}
三个字段各有职责:
| 字段 | 含义 | 不锁定的后果 |
|---|---|---|
source |
提供者在 Registry 上的定位,格式「命名空间/名称」 | 找不到插件 |
version |
版本约束,~> 1.278.0 允许 1.278.x 补丁升级 |
大版本升级常带破坏性变更 |
provider 块 |
连接参数,region 指定资源默认所在地域 |
资源落错地域 |
source 这一项有个时效性很强的坑:阿里云提供者已于 2026 年 3 月从 hashicorp/alicloud 迁移到 aliyun/alicloud,新配置一律用后者;旧配置迁移只需改 source 后重新 init,继续写旧 source 会在 init 阶段报找不到插件。
一台机器上要管理多个账号或地域,用 alias 声明多个实例,再在资源里显式指定:
plain
provider "alicloud" {
alias = "bj"
region = "cn-beijing"
}
resource "alicloud_instance" "web" {
provider = alicloud.bj
}
连接云需要凭据,AccessKey 的给法按安全等级分三档:
| 方式 | 做法 | 适用场景 |
|---|---|---|
| 环境变量 | export ALICLOUD_ACCESS_KEY=... |
本地练习、临时操作 |
| RAM 角色 | 给 ECS 挂角色,ecs_role_name 免密钥 |
云上生产主机 |
| OIDC 临时凭证 | CI 通过 OIDC 换取短期凭证 | 流水线,根上消灭常驻密钥 |
环境变量注入凭据的命令:
bash
export ALICLOUD_ACCESS_KEY="你的 AccessKey ID"
export ALICLOUD_SECRET_KEY="你的 AccessKey Secret"
AccessKey 绝对不能写进 .tf 文件------文件要进 Git,硬编码等于公开泄露,没有补救,只能吊销。
init 要从 registry.terraform.io 下载提供者,国内网络可能超时。在 ~/.terraformrc 配置阿里云官方镜像即可提速:
plain
provider_installation {
network_mirror {
url = "https://terraform-mirror.aliyuncs.com/"
}
}
资源参数不需要背:打开 registry.terraform.io,找到 aliyun/alicloud 文档页,左侧 Resources 栏列出全部资源类型,点进去就有完整参数表、必填项和官方示例,照文档填是正常姿势。
手已经接上了,却还没告诉它「想要什么」。一台 ECS,到底该怎么声明出来?
四、方案:把「想要的状态」写进 resource
声明一台 ECS,靠的是资源(resource)块;同样一份配置能部署开发、测试、生产三套环境,前提是先把「环境相关」和「逻辑相关」分离------这是基础设施即代码的基本素养,也是 Terraform 环境复现能力的根基。
标准单模块布局用三个文件各司其职:
latex
tf-first/
├── main.tf # 资源定义(逻辑相关)
├── variables.tf # 可变输入(环境相关)
└── outputs.tf # 对外暴露的值
你在 variables.tf 里声明交换机 ID 这类随环境变化的值:
plain
variable "vswitch_id" {
description = "目标交换机 ID,可在 VPC 控制台查看"
type = string
}
你在 main.tf 追加资源定义,这是全篇最核心的一段:
plain
resource "alicloud_instance" "web" {
instance_name = "my-first-ecs"
instance_type = "ecs.t6-c1m1.large"
image_id = "aliyun_2_1903_x64_20G_alibase_20230911.vhd"
vswitch_id = var.vswitch_id
internet_charge_type = "PayByTraffic"
internet_max_bandwidth_out = 5
password = var.instance_password
}
你在 outputs.tf 声明建完后想看到的值:
plain
output "instance_id" {
value = alicloud_instance.web.id
}
output "public_ip" {
value = alicloud_instance.web.public_ip
}
resource "alicloud_instance" "web" 是最核心的三段式语法:
latex
resource "alicloud_instance" "web"
│ │ │
│ │ └─ 本地名称:当前配置内的唯一标识,用于互相引用
│ └─────────────────── 资源类型:提供者给出的能力,格式「云产品_资源」
└───────────────────────────────── 关键字:声明一个资源
块内 var.vswitch_id 引用变量,alicloud_instance.web.id 引用另一资源的属性。Terraform 会自动分析引用关系、构建依赖图,按正确顺序创建,不需要你手动排先后。
几个参数的真实含义:
| 参数 | 含义 |
|---|---|
instance_type |
实例规格,决定 CPU/内存 |
image_id |
镜像,决定操作系统 |
internet_charge_type = "PayByTraffic" |
按流量计费 |
internet_max_bandwidth_out = 5 |
出网带宽峰值 5 Mbps,不设则默认不分配公网 IP |
两个细节别踩:镜像 ID 不要手抄,用数据源(data source)按名称动态查;密码用 sensitive 标记,apply 时交互输入、输出日志自动打码:
plain
data "alicloud_images" "ubuntu" {
owners = "system"
name_regex = "^ubuntu_24_04"
}
variable "instance_password" {
type = string
sensitive = true
}
多数依赖靠属性引用自动推导,个别「有顺序要求但无属性引用」的关联用 depends_on;生命周期(Lifecycle)块则控制资源变更策略:
| 生命周期参数 | 作用 | 典型场景 |
|---|---|---|
prevent_destroy = true |
含删除该资源的计划直接失败 | 数据库等有状态资源保命 |
create_before_destroy = true |
先建新再删旧 | 无状态应用零中断滚动替换 |
ignore_changes = [tags] |
忽略指定属性差异 | 容忍控制台手工改动 |
注意:给数据库这类有状态资源加上 prevent_destroy,是最便宜的后悔药------误跑 destroy 时它会在确认前直接拦下。
配置写完了,你怎么知道它这次会建什么、会不会顺手删掉没打算删的东西?
五、验证:先看 diff 再动手
Terraform 敢让你放心 apply,靠的是把「看清楚」和「动手」拆成两个命令。日常迭代的主循环就是这四板斧:

| 命令 | 作用 | 是否改动云资源 | 常用时机 |
|---|---|---|---|
init |
下载提供者、生成锁文件、初始化状态后端 | 否 | 新项目、新增依赖后 |
plan |
对比配置与状态,输出执行计划 | 否(只读) | 每次 apply 前 |
apply |
按计划调用 API 创建/变更资源 | 是 | 核对计划无误后 |
destroy |
按依赖逆序删除全部资源 | 是 | 清理练习环境 |
init:初始化
你首次执行 init,它做三件事:下载提供者插件到 .terraform/、生成 .terraform.lock.hcl 锁文件(Lock File)、初始化状态后端。锁文件必须提交 Git,.terraform/ 目录则写进 .gitignore。
bash
terraform init
plan:只读预览
执行计划(plan)对比配置与当前状态,用三种符号标出动作:+ 创建、~ 更新(附字段级 diff)、- 删除,一致时输出 No changes。它不调用任何写操作,是应用前唯一的免费安全检查。
你最好把计划固化成文件,apply 时严格按这份计划执行,杜绝「plan 之后配置又被改」的竞态:
bash
terraform plan -out=tfplan
计划末尾的汇总数字要逐一核对,多一个删除、少一个创建都和预期不符:
latex
Plan: 1 to add, 0 to change, 0 to destroy.
apply:执行
你按固化的计划执行,首次会要求输入 yes 确认;自动化场景才用 -auto-approve 跳过确认:
bash
terraform apply tfplan
terraform apply -auto-approve
apply 默认按依赖图并行创建互不依赖的资源(并行度 10)。多人共用远程状态后端时会加锁,你看到 Error acquiring the state lock 就等他人完成再重跑,不要强行删锁文件。失败了也不用慌:修正配置后直接重跑,Terraform 会基于状态继续完成剩余工作,而不是推倒重来。
destroy:销毁
bash
terraform destroy
销毁顺序与创建相反:先删依赖别人的资源(实例),再删被依赖的资源(交换机、VPC),保证不报「资源仍被占用」。日常练习建议你练完随手 destroy,账单不心疼,也验证了配置「可生可灭」的完整性。
现在可以解开开头那个删状态文件的坑了。apply 成功后目录里的 terraform.tfstate 是 Terraform 的记忆,记录「真实世界当前是什么样」,plan/apply 都基于它做 diff:
- 它是唯一事实来源:删了它,Terraform 会认为所有资源都不存在,再 apply 就全部重建,这正是开头重复建资源的原因。
- 它不能进 Git:可能含密码、IP,且多人并发提交会互相覆盖。
- 团队协作必须用远程状态后端:阿里云用 OSS,同时提供存储与锁,在
terraform块声明后重新 init 即迁移:
plain
terraform {
backend "oss" {
bucket = "my-tf-state"
key = "prod/ecs.tfstate"
region = "cn-hangzhou"
}
}
其余高频命令构成日常排障工具箱:
| 命令 | 作用 |
|---|---|
terraform fmt |
自动格式化,提交 Git 前必跑 |
terraform validate |
静态校验语法与引用合法性 |
terraform state list |
查看状态管理着哪些资源 |
terraform show |
查看资源当前属性 |
terraform console |
交互式测试表达式 |
注意:状态文件不进 Git、不手删,单机练习无所谓,团队协作必须上 OSS 远程状态后端加锁,否则并发 apply 会互相覆盖、损坏状态。
四板斧跑通了,剩下的坑,能不能在动手前用一张表全部避开?
六、总结延伸:避坑清单与术语速查
把全篇的坑和正解汇成一张清单,贴在工位上比临时翻文档快:
| 坑 | 正解 |
|---|---|
| 版本漂移 | 锁 required_version 与提供者版本,提交 lock 文件,tfenv 管本机 |
| 凭据硬编码 | 一律环境变量、RAM 角色或 OIDC,绝不进 .tf |
| 状态文件裸奔 | 多人协作上 OSS 远程状态后端加锁 |
| 删状态文件重建 | 状态是唯一事实来源,不手删、不进 Git |
| 乱跑 destroy | 先 plan 看清波及范围,有状态资源加 prevent_destroy |
滥用 -auto-approve |
只在 CI/脚本用,手敲保留交互确认 |
| 控制台手工改资源 | 下次 apply 会被「纠正」回去,一切变更走 Terraform |
滥用 -target |
跳过依赖图部分节点,生产环境易建不全,慎用 |

至此你已经走通最小闭环:安装 → 声明提供者 → 定义资源 → init/plan/apply/destroy。「描述状态、对比差异、执行变更」这套心智模型,就是基础设施即代码的全部精髓。
进阶建议按这个顺序走:
- 变量与 locals 表达式
- 远程状态后端 OSS
- module 模块化复用
- CI/CD 流水线集成
- 远程状态跨模块共享
把第一台 ECS destroy 掉,再随手建一个,手感就出来了。
术语速查表:
| 术语 | 英文 | 一句话解释 |
|---|---|---|
| 基础设施即代码 | Infrastructure as Code | 用代码而非手工操作管理基础设施 |
| 提供者 | Provider | Terraform 与云厂商 API 之间的插件 |
| 资源 | Resource | 一个被管理的云对象,如一台 ECS |
| 数据源 | Data Source | 只读查询已有信息,如按名称查镜像 |
| 状态 | State | 记录真实世界当前状态的文件,diff 的基准 |
| 执行计划 | Plan | 变更前的只读预览,含增/改/删符号 |
| 生命周期 | Lifecycle | 控制资源创建、替换、销毁策略的块 |
| 锁文件 | Lock File | 锁定提供者版本与校验和,保证构建一致 |
参考链接: