大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Resources 生命周期与 import
这一课你将能做到
- 说清楚一个 resource 块在 create / update / replace / delete 四种情况下分别发生什么
- 用
-replace强制重建资源,替代已经废弃的taint命令 - 用 1.7 的 import 块和
generate-config-out把控制台手工创建的存量资源纳管进 Terraform
为什么要学
resource 是 Terraform 配置里绝对的主角:前面几课的 provider、HCL 语法,最终都落在一个个 resource 块上。本站要管的几十种对象——安全组、EC2、RDS、S3——每一个都是一个 resource。初学者常见的困惑是:为什么改个标签是「更新」,改个实例类型有时也是「更新」,改个桶名却变成「删除+新建」?不理解资源属性的「可变性」,就看不懂 plan,也不敢在生产上点 yes。
另一个高频真实场景:接手的账号里早有一台别人在控制台点出来的 EC2、一个老 RDS,直接删了重建风险大,不管它又游离在 IaC 之外。terraform import 就是把「野生资源」收编进代码的正规通道。
先建立图像
每次 apply,Terraform 对每个资源做四象限判断:
配置里有这个资源吗?
│ 有 │ 没有
真实存在吗 ──────┼─────────────────┼────────────
存在(state中) │ 对比差异 │ 从 state 删除
│ → update/replace│ → destroy
不存在 │ create │ 无事发生
关键区分:
update(~) 调云 API 原地改:资源还是同一个,ID 不变,数据还在
replace(-/+) 删旧建新:ID 变化,本地盘数据通常丢失 ← 危险动作
plan 里凡是标了 # forces replacement 的字段,就是不可变属性(如 EC2 的 AMI、RDS 的引擎、S3 桶名)。
跟着做
步骤 1:创建一组有依赖关系的资源
新建 resources-demo/main.tf:
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = { Project = "coderoadmap" }
}
}
resource "aws_security_group" "web" {
name = "coderoadmap-web"
description = "本站 Web 层"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-09c3834b0881dbf81" # 东京区域 Amazon Linux 2023,可能随时间变化
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "coderoadmap-web-dev"
}
}
AMI ID 会随 Amazon Linux 更新而失效。更稳的做法是用下一课学的
data "aws_ami"动态查询,本课先写死以便理解流程。
执行:
terraform init
terraform plan -out=tfplan
terraform apply tfplan
plan -out 把计划存成文件,apply 该文件保证「执行的就是刚才评审的」。注意 apply 的输出顺序:先安全组后 EC2,这是 Terraform 根据引用关系自动排的。
步骤 2:体会 update 与 replace
把实例类型从 t3.micro 改成 t3.small:
terraform plan
会看到 ~ instance_type = "t3.micro" -> "t3.small",EC2 的实例类型支持原地修改(实际是停实例、改配置、再启动,实例 ID 不变),属于 in-place update。再把 ami 换一个:
-/+ resource "aws_instance" "web" {
~ ami = "ami-09c..." -> "ami-0aa..." # forces replacement
这是 replace:旧实例终止、新实例启动。本机盘(instance store)数据会丢,实例 ID、公网 IP 都会变。 看到 -/+ 必须条件反射地评估影响。不想真执行就放弃改动,继续下一步。
步骤 3:用 -replace 强制重建
服务器配置状态损坏(比如手工在机器上改乱了)、想强制换新时:
terraform plan -replace=aws_instance.web -out=tfplan
terraform apply tfplan
等价于旧时代的 terraform taint + apply。taint/untaint 命令在 Terraform 0.15.4 已被移除,现在统一用 plan/apply 的 -replace=资源地址,好处是强制重建同样经过 plan 评审。
步骤 4:用 import 块纳管存量资源(1.7 推荐方式)
假设账号里有一个早年在控制台手工建的 S3 桶 coderoadmap-legacy,现在要纳入 Terraform。先在配置里写 import 块:
import {
to = aws_s3_bucket.legacy
id = "coderoadmap-legacy" # AWS 中该资源的标识,S3 就是桶名
}
1.7 的做法是让 Terraform 帮你生成骨架配置:
terraform plan -generate-config-out=generated.tf
Terraform 会调用云 API 读取该桶真实属性,生成 generated.tf,里面包含一个配置完整的 aws_s3_bucket "legacy" 块。检查并微调(比如把硬编码值改成变量)后:
terraform plan # 应显示 No changes,说明代码与真实资源完全对齐
terraform apply # 什么都不改,只在 state 里建立绑定
确认 state 中已存在:
terraform state list
# aws_s3_bucket.legacy
批量纳管时,可以给 import 块加 for_each(1.7 支持),一次导入一组资源。旧命令式写法 terraform import aws_s3_bucket.legacy coderoadmap-legacy 仍然可用,但不会生成配置、也不适合放进代码做评审,新项目优先用 import 块。
步骤 5:删除单个资源
terraform destroy -target=aws_instance.web
-target 只处理指定资源及其依赖。它是急救工具不是日常手段,Terraform 官方明确提醒:长期依赖 target 会让配置与 state 的关系变得难以推理。常规清理请改配置后整体 apply,或整体 destroy。
讲清楚要点
- resource 的地址 = 资源类型 + 本地名:
aws_instance.web,state、replace、target、import 全都用这个地址定位。 - 可变性由 provider 决定:哪些字段能原地改、哪些 forces replacement,是 provider 根据云 API 能力标注的,查 plan 输出最直接。
- update 不等于「业务无感」:改 EC2 实例类型也要停机,改安全组规则即时生效;是否中断业务取决于云平台实现,要另做功课。
- import 只建立绑定,不改资源:它把真实资源的属性读进 state,资源本身一行不变;之后你的代码与真实配置的差异才会通过后续 plan 收敛。
- 删除是逆序的:Terraform 按依赖图反向销毁(先删实例再删安全组),所以即使 destroy 也尽量交给它而不是手删。
你可能会懵的地方
-
id这个属性是什么?为什么每个资源都有?id是云平台给资源的唯一标识,apply 之后才有,Terraform 用它和云 API 交互。import 时id = "..."就是告诉它「去云里找这个标识的资源」,不同资源的 id 形态不同(S3 是桶名、EC2 是 i-xxxx、安全组是 sg-xxxx)。 -
配置删掉了 resource 块,为什么资源还在云上? 必须再跑一次 apply(或 destroy),Terraform 才会根据新配置去删。保存文件本身不触发任何动作。
-
generate-config-out生成的配置能直接用于生产吗? 它是「现状快照」,会包含一些只该由云管理的只读字段(生成器一般会跳过这些)和硬编码值。要人工审查、参数化后再入库。 -
import 时报
Resource already managed by Terraform? state 里已经有这个绑定了,不能重复导入。先terraform state list确认,想换地址用terraform state mv。 -
手滑在控制台删了资源怎么办? Terraform 不知道,下次 plan 时该资源的 Refresh(Read)会发现它不存在,于是 plan 显示「新建」,apply 即重建。但数据类资源(RDS 没快照)内容无法靠 Terraform 找回——这是备份策略问题,不是 IaC 问题。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
用已删除的 terraform taint | 命令不存在,新人照抄老博客会卡住 | terraform apply -replace=addr |
| 看到 replace 不看 forces replacement 原因就 yes | 可能毁掉有状态资源 | 展开 plan 定位字段,先备份/评估 |
| 控制台手工建资源后只写配置不 import | Terraform 尝试新建,报名称冲突或产生重复资源 | 先 import 建立绑定,再让 plan 归零 |
| import 完不检查 plan 是否 No changes | 代码与现实有差异会被立刻「纠正」 | apply 前必须确认 plan 无意外变更 |
| 日常频繁使用 -target 改资源 | 绕过依赖关系,留下半管理状态 | target 仅作急救,随后补一次完整 apply |
卡点急救
- import 报
Cannot import non-existent remote object:id 写错或区域不对,用 AWS CLI(如aws s3 ls、aws ec2 describe-instances --instance-ids i-xxx)先确认资源真实存在且凭证连的是同一区域。 - plan 一直显示某资源要更新,apply 后再 plan 还是要更新:典型「参数对不上」——常见于证书/策略类 JSON 字段格式差异。用
terraform state show 地址看 state 真实值,与配置逐字对比;必要时用jsonencode()规范化。 - apply 中途失败,资源半建半删:Terraform 会把结果如实写回 state,直接重跑 apply 通常能收敛;若 state 与云不一致,用 import 补绑或
terraform state rm摘除后重新导入。
面试常问
-
Terraform 修改资源时,update 和 replace 的区别?怎么判断会走哪条? update 是原地调 API、资源 ID 不变;replace 是删旧建新。plan 输出中字段标
forces replacement即会触发替换,这类属性通常是云平台不可变的(AMI、引擎类型、资源名)。 -
说一下 terraform import 的过程,导入后要注意什么? import 在 state 中建立「配置地址 ↔ 云资源 ID」的绑定,资源本身不变;导入后要补齐配置并确认 plan 为 No changes,否则首次 apply 就会按代码改/删存量资源。1.7 推荐 import 块 +
-generate-config-out。 -
taint命令现在用什么替代?为什么这么改?plan/apply -replace=地址。替代方案把强制重建纳入 plan 评审和 planfile 流程,避免「标记 taint」和「执行」分离带来的误操作。
自测三问
- 改 EC2 的哪些常见属性会触发 replace?各举一个 update 和 replace 的例子。
- 纳管一个控制台手工建的安全组,从写 import 块到 plan 归零,完整步骤是什么?
- apply 失败中断后,为什么「直接重跑 apply」通常是正确的第一反应?
小结一句话
resource 的生命周期就是 plan 驱动的 create/update/replace/delete 四象限;看懂 forces replacement、善用 -replace 和 import 块,就掌握了 Terraform 最核心的资源操作心法。
延伸
- Resource Behavior 官方文档
- Import Blocks(1.7 配置式导入)
- generate-config-out 生成配置说明
- 下一课:Variables——类型、默认值、校验规则、
sensitive与多环境变量文件组织。