1. 为什么存量资源需要纳管
一句话总结: 真实世界极少是绿地项目,绝大多数团队面对的是「几百个手工点出来的资源」,纳管它们是把 IaC 从演示推进到生产的第一道门槛。
绿地项目里 terraform apply 从零创建资源,一切干净。但大多数云账号是「棕地」(brownfield):控制台点出来的 EC2、脚本建的 S3、别人用 CLI 开的 RDS。这些资源没有状态,直接写配置再 apply 的后果是「计划创建同名资源」——轻则重名冲突,重则误删重建。
纳管的目标是让 Terraform 相信「这些资源一直是我管的」:
# 纳管前后的对比
terraform plan # 未纳管:计划创建 12 个资源(危险)
# ...import 之后...
terraform plan # 已纳管:No changes(理想状态)
| 阶段 | 目标 | 风险 |
|---|---|---|
| 识别 | 摸清账号里有哪些资源 | 遗漏导致后续冲突 |
| 导入 | 把资源写进 state | 配置与真实属性不一致 |
| 对齐 | plan 收敛到 No changes | 差异被忽略留下隐患 |
| 固化 | 纳入模块与流水线 | 权限过大误删 |
一句话:纳管的核心不是「import 命令」,而是「让 plan 输出 No changes」——只要还有差异,就说明你对资源的描述还是错的。
2. import 块与 CLI import
一句话总结: CLI 的
terraform import只写状态不写配置,import 块(1.5+)则把导入声明写进代码、可评审、可批量执行,是当代推荐做法。
CLI 导入是「一次性动作」,配置要自己补:
# 传统方式:先写一个空壳资源块,再 import
terraform import aws_s3_bucket.data my-existing-bucket
# 导入后状态有了,但配置是空的,plan 会显示一堆差异
terraform plan
import 块把「导入」变成声明式代码,可以进 PR、可以被 review、可以一次导入一批:
# imports.tf
import {
to = aws_s3_bucket.data
id = "my-existing-bucket"
}
import {
to = aws_instance.web
id = "i-0abc123def4567890"
}
# 执行导入(会提示确认)
terraform plan
terraform apply
2.1 两种方式的取舍
| 维度 | CLI import | import 块 |
|---|---|---|
| 可评审 | 否,本地动作 | 是,进版本控制 |
| 批量 | 脚本循环 | 一个文件写多条 |
| 幂等 | 重复执行报错 | 已导入则跳过 |
| 配置生成 | 需手工补 | 可配合 generate-config-out |
| 适用 | 临时救火 | 正式纳管流程 |
# import 块导入后即可删除,避免重复导入报错
# 保留在代码里也是幂等的,Terraform 会识别已导入
一句话:新项目一律用 import 块——它把「我导入了什么」这件事留在了 git 历史里,而不是某个人的终端里。
3. 状态对齐与 refresh
一句话总结: 导入只是把资源 ID 塞进 state,真正的难点是让配置与真实属性完全一致,
plan的差异就是待办清单。
导入后第一件事是跑 plan,逐条看差异。差异通常来自三类:属性没写、属性写错、属性是 Provider 默认值但被显式覆盖。
terraform plan -out=import.tfplan
# 只看会修改/销毁的动作
terraform show -json import.tfplan | \
jq '[.resource_changes[]
| select(.change.actions != ["no-op"])
| {addr: .address, actions: .change.actions}]'
terraform refresh(0.15 后并入 plan)用真实 API 数据刷新 state,能发现控制台里被手工改动的属性:
# 只刷新不计划变更(-refresh-only 是推荐写法)
terraform apply -refresh-only
# 或把刷新结果单独看
terraform plan -refresh-only -out=refresh.tfplan
terraform show refresh.tfplan
3.1 属性不可见的部分怎么处理
有些属性(如 aws_instance 的 user_data)在 API 里不回传明文,导入后 plan 可能永远显示差异。常见处理是用 lifecycle.ignore_changes 明确「这块我不打算管」。
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
lifecycle {
# 明确声明:存量实例的 user_data 由外部维护,Terraform 不追踪
ignore_changes = [user_data, ami]
}
}
一句话:
ignore_changes不是逃避,而是对「这块由谁负责」的显式声明——比留下永远消不掉的 plan 差异要诚实。
4. 生成配置 -generate-config-out
一句话总结: 手写几百行配置去匹配存量资源极其痛苦,
-generate-config-out让 Terraform 根据 state 反向生成 HCL,是纳管效率的关键杠杆。
Terraform 1.5+ 支持在 import 块存在时,用 plan -generate-config-out 让 Provider 依据导入结果生成配置骨架。
# import.tf:只写 import 块,不写 resource 块
import {
to = aws_s3_bucket.data
id = "my-existing-bucket"
}
import {
to = aws_security_group.app
id = "sg-0abc123def456"
}
terraform plan -generate-config-out=generated.tf
# generated.tf 会包含从真实资源推导出的属性
生成结果形如:
resource "aws_s3_bucket" "data" {
bucket = "my-existing-bucket"
force_destroy = false
tags = {
Environment = "prod"
Team = "platform"
}
}
resource "aws_security_group" "app" {
name = "app-sg"
description = "application security group"
vpc_id = "vpc-0abc123"
}
4.1 生成后必须人工清理
生成的配置是「能跑」而不是「好读」:它会把所有真实属性写死,包括本该用变量、本该来自 data source 的值。必须人工重构。
# 生成版:硬编码 vpc_id
vpc_id = "vpc-0abc123"
# 重构版:改为引用
vpc_id = data.aws_vpc.main.id
一句话:生成配置是「脚手架」不是「成品」——它把从零手写的三小时压缩到三十分钟,剩下的重构仍需人工判断。
5. 渐进式接管与模块化
一句话总结: 一次性纳管整个账号风险极高,正确做法是按业务域分批、按依赖顺序推进,每批纳管后立刻让 plan 收敛并纳入流水线。
推荐的推进顺序:先纳管「无状态、易重建」的资源(安全组、IAM、S3),再纳管「有状态」的资源(数据库、存储卷),最后纳管「网络骨干」(VPC、子网、路由表)。
# 按批次推进,每批一个分支、一次 PR
# 批次 1:IAM 与安全组
# 批次 2:S3 与 SQS
# 批次 3:RDS(有状态,需快照兜底)
# 批次 4:VPC 网络(影响面最大)
5.1 用模块收敛重复结构
纳管过程中会发现大量结构相同的资源(每个环境一套子网),此时抽取模块,把「导入」与「抽象」同步完成。
module "app_subnets" {
source = "./modules/subnets"
for_each = var.environments
environment = each.key
vpc_id = data.aws_vpc.main.id
cidr_blocks = each.value.subnet_cidrs
}
import {
to = module.app_subnets["prod"].aws_subnet.private[0]
id = "subnet-0aaa111"
}
5.2 用 moved 块整理地址
纳管时常需要把资源从 aws_x.a 改到 module.m.aws_x.a,用 moved 块可以避免 destroy/create。
moved {
from = aws_subnet.private
to = module.app_subnets["prod"].aws_subnet.private[0]
}
一句话:渐进式接管的关键是「每批都可回退」——state 有备份、配置有分支、plan 已收敛,任何一批出问题都能停下而不影响其余。
6. 避免误删的护栏
一句话总结: 纳管最大的恐惧是「一次 apply 删掉生产数据库」,护栏由 prevent_destroy、删除保护、状态备份与权限收敛四层构成。
resource "aws_db_instance" "main" {
identifier = "prod-mysql"
engine = "mysql"
instance_class = "db.r6g.large"
lifecycle {
# 任何试图销毁该资源的计划都会直接报错
prevent_destroy = true
}
}
# 存储桶也建议加删除保护
resource "aws_s3_bucket" "data" {
bucket = "prod-data-lake"
lifecycle {
prevent_destroy = true
}
}
四层护栏:
| 护栏 | 实现 | 拦截什么 |
|---|---|---|
| 配置层 | prevent_destroy = true | 配置被误删导致的销毁 |
| 云平台层 | RDS deletion_protection、S3 MFA delete | 任何来源的删除 |
| 状态层 | state 版本化 + 定期备份 | 状态损坏后的恢复 |
| 权限层 | apply 角色不含 Delete* | 越权的销毁操作 |
# 纳管期间每次操作前先备份 state
terraform state pull > backup/state-$(date +%Y%m%d-%H%M%S).tfstate
# 查看当前状态里有哪些资源
terraform state list
一句话:护栏的成本是一次性配置,收益是「永远不会因为一个手滑而丢数据」——这笔买卖永远划算。
7. 大规模纳管的自动化
一句话总结: 当资源数以百计,手工写 import 块不现实,需要用云 API 枚举资源、脚本批量生成 import 块,再分批评审落地。
# 枚举账号内所有未纳管的 EC2 实例
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id:InstanceId,Name:Tags[?Key==`Name`]|[0].Value}' \
--output json > instances.json
# 与 state 对比,找出未纳管的
terraform state list | grep '^aws_instance' | sed 's/.*\.//' > managed.txt
jq -r '.[].Id' instances.json | sort > all.txt
comm -23 all.txt managed.txt > to-import.txt
# 批量生成 import 块
while read -r id; do
echo 'import {'
echo " to = aws_instance.legacy_${id//-/}"
echo " id = \"$id\""
echo '}'
done < to-import.txt > imports-generated.tf
7.1 分批与命名约定
自动生成的 to 地址需要符合团队的命名约定,建议统一用 legacy_ 前缀标记「待重构」的资源,后续用 moved 块重命名。
# 生成后人工审阅,按业务域拆成多个文件
# imports-prod-network.tf / imports-prod-app.tf
terraform plan -generate-config-out=generated-prod-app.tf
一句话:自动化的边界是「生成」——生成 import 块与配置骨架,但「这一批该不该纳管」的判断永远由人来做。
8. 总结
存量资源纳管是一条从「不可见」到「可收敛」的路径:
| 环节 | 手段 | 关键点 |
|---|---|---|
| 识别 | 云 API 枚举 + state 对比 | 找出未纳管清单 |
| 导入 | import 块 / CLI import | 优先 import 块,可评审 |
| 生成 | -generate-config-out | 脚手架,需人工重构 |
| 对齐 | plan 收敛到 No changes | 差异即待办清单 |
| 重构 | 模块化 + moved 块 | 抽象与改名同步完成 |
| 护栏 | prevent_destroy + 删除保护 | 四层防线避免误删 |
| 固化 | 纳入流水线 | 纳管完成才算真正接管 |
一句话收尾:纳管不是一次 import,而是一次「把基础设施的所有权从控制台转移到代码」的交接。当 plan 稳定输出 No changes、护栏就位、配置进入流水线,那些曾经只存在于某个人记忆里的资源,才真正成为团队可维护、可审计的资产。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。