导入与漂移
本章目标:把已经在云上存在、但没被 Terraform 管理的资源「收编」进 state(import);并处理「有人手动改了云上资源」造成的漂移(drift)。这两件事是真实运维里绕不开的。
完成后你应能安全地把存量资源纳入管理,并决定「改回去」还是「接受现状」。
1. 为什么需要 import
现实里大量资源是「先有云、后补 IaC」的:
- 早期手动建的资源,现在想用 Terraform 统一管理。
- 别的团队/工具建的,你想接管。
- 灾难恢复后,云上资源还在,但 state 丢了,需要重新建立映射。
import 的本质:告诉 Terraform「这个资源地址,对应云上那个真实对象」。传统 terraform import 会立即把映射写进 state;声明式 import 块则在 apply 时写入。两种方式都不会自动产出完整的生产配置,配置仍要你审阅并维护。
2. 方式一:terraform import(传统)
先写好资源配置,再执行 import 把云上对象绑定到该地址:
# 先写配置
resource "aws_instance" "web" {
# 必填字段仍需满足 provider schema;值应尽量与云上当前配置一致
ami = "ami-0abcd1234"
instance_type = "t3.small"
}
terraform import aws_instance.web i-0abc123def456
# 成功后再 terraform plan,会提示配置与真实状态的差异,逐步补齐
注意点:
- import 只写 state,不写代码。import 后必须跑
plan,根据差异把配置补全到和真实一致,否则下次apply会去「修正」它。 - 需要目标资源的 ID(不同资源 ID 形式不同:
i-xxx、ARN、名称等),从云控制台或 CLI 查。 - 一批资源要逐个 import,繁琐。
3. 方式二:import 块(Terraform 1.5+,推荐)
声明式、可进版本控制、支持 for_each 批量:
import {
to = aws_instance.web
id = "i-0abc123def456"
}
resource "aws_instance" "web" {
# 正常写完整配置
ami = "ami-0abcd1234"
instance_type = "t3.small"
tags = {
Name = "web-01"
}
}
流程:
terraform plan # 检测到 import 块,读取云上对象并预览导入与配置差异;不写 state
# 根据 plan 差异补全 resource 配置
terraform apply # 写入 state,完成导入
若还没有资源配置,可先让 Terraform 生成初稿,再人工收敛:
# Terraform 1.5+:基于 import 块生成资源配置初稿
terraform plan -generate-config-out=generated.tf
# 逐项审阅 generated.tf,整理到模块文件后再提交和 apply
生成的配置只用于加速盘点,不能跳过 plan 评审直接进入生产。
导入后删掉 import 块即可(它只在导入时起作用,留着无害但冗余)。
把 import 块和 resource 放一起提交,相当于「声明我要接管这个已有资源」,可读、可评审、可重复,比一堆 terraform import 命令强得多。
4. 漂移(drift)是什么
漂移 = 云上真实状态 与 state 记录不一致。常见原因:
- 有人直接在控制台手改了配置。
- 别的脚本/工具越权修改。
- 云厂商侧自动行为(如某些托管服务的后台调整)。
Terraform 默认在 plan 时先 refresh,所以漂移会在 plan 里暴露出来——但那是「准备改之前才发现」,偏被动。
5. 主动检测漂移:-refresh-only
terraform plan -refresh-only
它只刷新 state、不改云,输出「真实 vs state 的差异」:
# aws_security_group.web has been changed
~ resource "aws_security_group" "web" {
~ ingress = [ # 有人手动加了一条规则
+ { ... } # 控制台手加的
]
}
适合定时任务(每日)跑,发现漂移就告警/派单。
terraform refresh 已不推荐旧版 terraform refresh 直接改 state,容易误覆盖。1.5+ 统一用 plan -refresh-only,更安全、可审阅。
6. 漂移怎么处理:改回去 or 接受
| 选择 | 做法 | 适用 |
|---|---|---|
| 改回代码口径 | 在 plan 里 apply,Terraform 把云上拉回代码定义 | 手动改动是「误改/越权」,应以代码为准 |
| 接受现状 | 把真实值同步进配置,使其与代码一致 | 手动改是「合理临时调整」,应固化进 IaC |
6.1 用 lifecycle 接受某些字段的偏离
有些字段天然会被外部改动(如 ASG 当前实例数、某些托管服务的后台标签),不想每次 plan 都报噪,可显式忽略:
resource "aws_security_group" "web" {
# ...
lifecycle {
ignore_changes = [
ingress, # 接受入站规则被外部调整
tags["LastModified"],
]
}
}
它能降噪,但也会掩盖真实漂移。只忽略「明确由外部系统负责」的字段,别用它掩盖自己代码没覆盖的变更。
6.2 停止管理但保留资源:terraform state rm
terraform state rm aws_instance.legacy
把资源从 state 移除——云上资源不会被删除,只是 Terraform 不再管它。用于「这个资源交给别的团队/工具了」的场景。之后再导入或手工处置都可。
7. 与手工变更共存的整体策略
- 新资源一律走 Terraform,杜绝「手动建完再 import」成为常态。
- 关键资源加
prevent_destroy(见第 9 章),防止apply误删。 - 定时
plan -refresh-only做漂移巡检,把「代码 vs 真实」的偏差变成可观察、可评审的事件。 - import 块纳管存量,别用一堆临时命令。
- 漂移处置要留痕:无论改回还是接受,都在 PR/工单里说明,保持代码与真实的因果可追溯。
8. 练习与验收
练习 A
- 手动在控制台建一个安全组(不入 Terraform)
- 写
import块把它导入,跑plan观察差异,apply完成接管 - 导入成功后删掉
import块,再plan应无变更
练习 B
- 导入后,去控制台手动加一条入站规则
- 跑
terraform plan -refresh-only,确认 drift 被检出 - 用
lifecycle { ignore_changes = [ingress] }接受该字段,再plan应不再报
验收清单
- 能用
terraform import与import块两种方式纳管存量资源 - 理解 import 只写 state、不写代码,需补配置
- 会用
plan -refresh-only主动检测漂移 - 会用
ignore_changes与state rm处理「接受/退出管理」 - 建立了「新资源走 IaC、漂移可巡检」的整体意识
下一章:生产协作 —— 收口权限、密钥、破坏性变更的护栏,把 Terraform 真正放进团队协作的生产环境。