Terraform 避坑:模块下线时,如何不破坏已有的 Instance Template?

在 GCP 的 Terraform(以下简称 TF)运维中,我们经常会遇到资源生命周期不一致的问题。

最近在做资源清理和重构时,碰到了一个非常经典的 TF 状态机对齐"大坑":如何优雅地下线一个托管实例组(MIG),但保留它所依赖的实例模板(Instance Template)?

1. 场景与痛点

项目里引用了一个封装好的 MIG 模块,它的内部结构很简单:一个 google_compute_instance_template(实例模板),后面紧跟着一个 google_compute_instance_group_manager(MIG 组管理器)引用这个模板。

现在因为架构调整,这个 MIG(托管实例组)不需要了,我们要把它下线。但是,这个实例模板(Instance Template)线上还有其他独立的 VM 在使用,或者我们需要留作备份,绝对不能删。

按常规思路,我们在主配置里直接把模块调用处的 count 设为 0

hcl 复制代码
module "my_new_mig" {
  count  = 0
  source = "github.com/nvd11/terraform-mig?ref=1.0.6"
  ...
}

然而,跑 terraform plan 的时候,终端却给出了毁灭性的提示:

text 复制代码
# module.mig.module.my_new_mig.google_compute_instance_template.mig_template will be destroyed
- resource "google_compute_instance_template" "mig_template" {
  ...
}

MIG 确实是不建了,但 TF 居然连带着要把线上好端端的实例模板也一块儿给 Destroy 掉!这显然无法接受。


2. 为什么 TF 会"连带击杀"?

要解决这个问题,首先得明白 TF 的逻辑。

这个 mig_template 是在 module 的"肚子里"声明的。当你把整个模块的 count 设为 0(或者直接在代码中删掉这个 module 块)时,在 TF 看来:这个模块对应的所有代码配置都不复存在了。

但因为这个实例模板之前已经被成功创建过,它的元数据和状态依然保存在当前的 .tfstate(状态文件)里。

TF 在 Plan 时的比对逻辑是:

  1. 检测到状态文件(State)里有 module.mig.module.my_new_mig.google_compute_instance_template.mig_template 的记录。
  2. 比对当前的配置(Configuration),发现代码里该模块 count = 0
  3. 为了让线上真实世界和你的代码配置达成一致,TF 只能执行:"把线上这个处于该 Module 命名空间下的实例模板强制销毁"。

3. 解决方案

针对这种"只要保留线上模板,不要 MIG"的诉求,通常有两种最实用的解法。

方案一:彻底解耦,将模板移出 TF 管理(最推荐、最干净)

如果你以后不需要再用这套 TF 代码去管理、更新这个实例模板,只是想让它在线上安全地躺着,供其他地方手动引用。

直接修改 .tfstate 状态机,让 TF 单向失忆

第一步:代码层面停用模块

在你的主配置文件(比如 envoy-proxy.tf)中,保持 count = 0(或者直接删掉整个 module "my_new_mig" 块)。

第二步:从状态机中手动移除该资源

在终端执行以下命令,把实例模板从 TF 的"记忆"里强行抹去:

bash 复制代码
terraform state rm module.mig.module.my_new_mig.google_compute_instance_template.mig_template

(如果旧的 MIG 在 State 里也有残留,可以顺手用 terraform state rm module.mig.module.my_new_mig.google_compute_instance_group_manager.mig 一并清理掉)

原理解析:

terraform state rm 只是修改本地或远端的 State 状态文件,绝对不会 向 GCP 发送任何真实的 Delete 请求。

执行完后,TF 认为这个模板根本不归它管。此时你再跑 terraform plan

  • 代码里:count = 0,不创建;
  • 状态里:没有这个模板的任何记忆,无需销毁。
    Plan 结果直接变为完美的 0 to add, 0 to change, 0 to destroy。线上资源毫发无损。

方案二:状态转移,把模板收归根级资源继续管理

如果你未来还需要在这套 TF 代码里,继续对这个实例模板进行升级、修改,只是单纯地不想要 MIG 这个集群。

思路:把实例模板从模块(Module)的命名空间里"过户"到根级资源下。

第一步:在根级代码中独立声明该实例模板

我们不再通过 Module 管理了,直接写在主配置(如 envoy-proxy.tf)的根级下:

hcl 复制代码
resource "google_compute_instance_template" "my_envoy_proxy_template" {
  name_prefix  = "my-envoy-proxy-"
  machine_type = "e2-medium"
  # ... 抄写之前模块里对应的 disk、network_interface、service_account 等配置 ...
}

同时,保持原有模块 count = 0

第二步:通过 state mv 完成过户

我们需要把 state 里那个挂在 module 名字下的模板实体,重命名(过户)为你刚刚在根级声明的资源:

bash 复制代码
terraform state mv \
  module.mig.module.my_new_mig.google_compute_instance_template.mig_template \
  google_compute_instance_template.my_envoy_proxy_template

原理解析:

通过 state mv,线上同一个实例模板的控制权,从"即将被销毁的 Module 命名空间"顺利转移到了"你刚刚新建 of 根级配置"。

这样在 terraform plan 时,TF 发现代码里有一个根级模板,State 里也刚好对得上(只是改了名字,但指向同一个线上实体),所以它不仅不会去删除它,以后你还能像以前一样继续修改和升级它,同时原模块里的 MIG 也会被安全地清理掉。


4. 总结

Terraform 是声明式的。当我们要下线某个组合模块(Module)时,如果想保留其中的个别"子资源",切忌直接一刀切 count = 0

  • 想放手 :用 state rm 解耦,让 TF 遗忘,线上物理保留。
  • 想续管 :在根级写好配置,用 state mv 过户,平滑承接。
相关推荐
雨辰AI5 小时前
信创数仓分层建模|国产数据库 ODS/DWD/DWS 分层落地规范(金仓 / 达梦 / 高斯适配)
数据库·云原生·政务
阿里云云原生6 小时前
实验:回测、离线实验平台与题目级 Rubric丨AgentLoop 数据飞轮实践(四)
云原生·agent
Ningcode_cloud10 小时前
什么是CICD? GitLab + Jenkins 持续集成实战部署手册
运维·ci/cd·云原生·容器·gitlab·jenkins
瞬间&永恒~11 小时前
【Kubernetes】(十五)维护与升级、ETCD 备份和恢复
linux·运维·docker·云原生·kubernetes
小小程序员.¥12 小时前
docker基础
云原生·eureka
harmony&1 天前
Docker 容器技术从入门到实战:容器、网络、存储与监控全解析
docker·云原生
分布式存储与RustFS1 天前
RustFS 生命周期与分层:让冷数据自动搬家、热数据自己回家
云原生·开源·对象存储·分布式存储·s3·rustfs·性能基准
阿里云云原生1 天前
从 Inform 到 Operate:构建可持续运行的云成本巡检机制与 Mission 调度
云原生
再卷还是菜1 天前
Cephfs总结及kubernetes+Cephfs的整合项目
云原生·容器·kubernetes
大大大大晴天️2 天前
从 HDFS 到对象存储:计算存储分离如何重塑云原生大数据底座
大数据·云原生