「资源重构与迁移」

讲解 Terraform 资源重构与迁移:moved block 地址迁移、state mv 手工搬移、import 采纳存量资源、循环引用消除、大变更拆分演练,以及迁移的回滚与安全步骤。

1. 重构心智模型

一句话总结: Terraform 重构的核心是「让代码意图与 state 地址对齐」,任何改动分三步——改代码、对 state、验证 plan,且每一步都不应产生意外删除。

Terraform 把「代码里的资源地址」与「state 里的记录」一一对应。重构的本质是:代码变了,但云端资源没变,所以要同步改地址映射,否则 plan 会把资源当成「删除再重建」。

代码地址                     state 记录                    云端资源
aws_instance.web[0]  ─────── 同名记录  ────────────────  i-0abc123
(改名后)                   (地址不匹配)
aws_instance.web_server     无记录 → 计划创建           (仍是旧实例)
                            aws_instance.web 记录      → 计划删除!

1.1 重构三原则

原则含义
先对状态,后看计划改地址后先 plan,确认无「删除」类意外
一次只动一处地址迁移与属性变更分开提交
以 plan 为镜子任何重构都以「plan 无意外删除/重建」为通过标准

2. moved block 地址迁移

一句话总结: moved block 声明「资源从旧地址搬到新地址」,Terraform 据此把 state 记录迁移过去,是代码级重构的官方首选。

moved block 写在配置里,声明地址迁移关系。apply 时 Terraform 自动把旧地址的 state 记录搬去新地址,随后计划收敛。

# 原来叫 aws_instance.web
resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

# 声明迁移:从旧地址到新地址
moved {
  from = aws_instance.web
  to   = aws_instance.web_server
}

2.1 批量迁移与模块场景

重构进模块、重命名 module 实例、拆分资源时,moved 同样适用,且可多个 block 同时声明。

# 资源被收进模块
moved {
  from = aws_instance.web
  to   = module.webserver.aws_instance.web
}

# 模块实例改名
moved {
  from = module.legacy
  to   = module.webserver
}

2.2 moved 与 count/for_each 迁移

for_each 键名变化或 count 变 for_each 时,用 moved 逐条声明键映射。

resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob", "carol"])
  name     = each.value
}

moved {
  from = aws_iam_user.alice
  to   = aws_iam_user.users["alice"]
}

一句话:moved block 是「可提交进仓库的迁移声明」,评审友好、可保留历史,适合随代码一起演进;完成后再择机删除。

3. state mv 手工搬移

一句话总结: terraform state mv 在命令行直接搬移 state 记录,适合临时性、脚本化的地址调整;它不是代码的一部分,适合一次性或批量操作。

当地址变化是一次性操作(或者不方便在代码里加 moved block)时,用 state mv 直接搬。

# 单地址搬移
terraform state mv aws_instance.web aws_instance.web_server

# 批量前缀搬移(模块化时常用)
terraform state mv 'aws_instance.web' 'module.webserver.aws_instance.web'

# 加 dry-run 确认
terraform state mv -dry-run aws_instance.web aws_instance.web_server

3.1 state mv 的语义细节

  • mv 只改 state 地址,不触发云上任何变更。
  • 目标地址若已存在记录会报错,除非用 -replace(谨慎)。
  • 搬移后立即 terraform plan,确认与代码意图一致。

3.2 moved 与 state mv 的选择

方式是否入库适用场景
moved block是,随代码提交长期重构、模块化演进
terraform state mv否,命令操作一次性调整、批量迁移

两者可以混用:大规模调整先用 state mv 脚本批量处理,后续演进用 moved block 固化在代码里。

4. import 采纳存量资源

一句话总结: terraform import 把「云上已存在、Terraform 未管理」的资源纳入 state,是「从零到 IaC」和「接纳历史资产」的标准入口。

历史遗留的手工资源,或从其他工具接管过来的资源,先用 import 让 Terraform 认识它,再逐步收敛配置。

# 语法:terraform import <地址> <云资源ID>
terraform import aws_instance.web_server i-0abc123

4.1 import 后必须对齐代码

import 只是「登记」:state 里有了记录,但代码里若没有对应 resource 块,下次 plan 仍会计划删除。正确流程是三步走。

# 1. 先写好 resource 块(属性先留占位)
resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

# 2. import 登记
# terraform import aws_instance.web_server i-0abc123

# 3. plan 收敛:补齐必填属性,消除 diff

4.2 import 常见场景

场景示例
接管手工资源手工建的 S3 桶、安全组
从 CloudFormation 迁移已存在 EC2/RDS 转 Terraform
从其他 IaC 迁移Pulumi/Ansible 管理的资源
子资源导入单独纳管某个对象

4.3 import 块(Terraform 1.5+)

Terraform 1.5 起支持声明式 import block,把导入写成代码的一部分。

import {
  to = aws_instance.web_server
  id = "i-0abc123"
}

terraform plan 时自动执行导入并校验差异,比命令行 import 更可评审、可回放。

5. 循环引用消除

一句话总结: 循环引用是重构时最常见的编译期障碍,解法是先打破依赖环,把「互相引用」改成「数据源读取」或「先算后传」。

5.1 循环引用的成因

两个资源互相引用对方属性就会成环。例如安全组互相放行彼此,或 EC2 与弹性 IP 双向引用。

# ❌ 成环:A 引用 B,B 又引用 A
resource "aws_instance" "a" {
  vpc_security_group_ids = [aws_security_group.b.id]
}
resource "aws_security_group" "b" {
  ingress {
    security_groups = [aws_instance.a.security_groups]
  }
}

5.2 打破循环的三种方式

方式做法
拆环把环上的一个引用改成已知值(CIDR、变量)
数据源用 data 读取对方已存在的 ID,解除引用关系
分步一个资源先建,另一个后引用其输出
# ✅ 用 data 读取既有安全组,打破循环
data "aws_security_group" "existing" {
  name = "shared-sg"
}

resource "aws_instance" "a" {
  vpc_security_group_ids = [data.aws_security_group.existing.id]
}

一句话:循环引用是「依赖图打结」,数据源与变量是剪线的剪刀,先剪断环再谈其他重构。

6. 大变更拆分

一句话总结: 大规模迁移要拆成「可独立验证的小步」,每步一次 apply、一次 plan 审计,先迁后删,避免一把梭触发大面积删除。

6.1 安全拆分的节奏

第 1 步:加新代码,用 import/moved 让 state 认识
第 2 步:plan 确认只有新增、没有删除
第 3 步:apply,验证新资源
第 4 步:移除旧代码,plan 确认只删除旧资源
第 5 步:apply,清理旧资源与旧 state

每步之间都要 terraform plan 审计「变更集合是否可控」。

6.2 批量迁移脚本示例

#!/bin/bash
# 批量 import 一批 EC2
for id in i-0abc1 i-0abc2 i-0abc3; do
  terraform import "aws_instance.web[\"$id\"]" "$id"
done

# 逐条确认迁移结果
terraform state list | grep aws_instance
terraform plan -detailed-exitcode

6.3 拆分的判断信号

信号处理
plan 显示超过 N 个删除立即停止,拆小步
迁移与属性变更混在一起属性变更单独提交
涉及不可重建资源(数据库)单独演练,先备份

7. 迁移演练与回滚

一句话总结: 迁移前先做演练(dry-run、临时分支),回滚靠「备份 state + 代码回退」,生产迁移选择低峰窗口并保留快照。

7.1 演练

# 状态备份
terraform state pull > backup.$(date +%s).tfstate

# dry-run 迁移
terraform state mv -dry-run aws_instance.web aws_instance.web_server

# 计划审计
terraform plan -detailed-exitcode

7.2 回滚路径

阶段回滚手段
代码未 apply直接回退 PR
state 已 mv 未 applystate mv 反方向搬回
已 apply代码回退 + 按需 terraform apply 恢复
云资源已变依赖备份与快照恢复

迁移期间全程禁用 terraform apply -auto-approve,任何自动审批都会让回滚窗口消失。

8. 总结

资源重构与迁移,是把「老代码」平滑带到「新结构」而不触发云上破坏的系统工程:

环节要点
心智代码地址与 state 地址对齐,plan 为准
moved入库的地址迁移声明,评审友好
state mv命令级搬移,适合一次性批量
import接纳存量资源,先登记再收敛
循环引用用数据源/变量剪断依赖环
拆分先迁后删,每步 plan 审计
回滚备份 state + 代码回退 + 演练

一句话收尾:重构不是「改代码」,而是「改地址映射」,每一步都以 plan 无意外删除为绿灯。把 moved/import/拆分与回滚用熟,再大的架构演进都能小步安全落地。下一篇「漂移检测与收敛」将讲如何发现并修复「云端现状与代码不一致」的问题。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. 「漂移检测与收敛」
  2. 「数据源与远程数据读取」
  3. 「Kubernetes Provider 编排」