CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
Resources — CodeRoadMap
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:
会看到 ~ 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 里建立绑定
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 最核心的资源操作心法。
延伸