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 过户,平滑承接。
相关推荐
阿里云云原生7 小时前
HiRoute 智能路由实战:GPT-6 Astra × Qwen-3.8 Flash,同等质量下成本降低 90%
云原生
阿里云云原生7 小时前
用自然语言驾驭日志:阿里云 SLS Agent Skills 实践指南
云原生
衡石科技9 小时前
衡石 BI PaaS:面向 Agent 的分析底座
云原生·云计算·paas
阿里云云原生10 小时前
阿里云 STAROps 全新升级,让 AI 持续推进问题闭环解决
云原生
分布式存储与RustFS10 小时前
指标、日志、链路进同一个桶:GreptimeDB 存算分离解读与 RustFS 接入配置
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
阿里云云原生11 小时前
AI 进入生产后,研发组织怎么变
云原生
陈聪.11 小时前
01-项目一_企业级高可用Web集群实施手册
运维·云原生
分布式存储与RustFS12 小时前
RustFS 上生产第一步:从单机到 4 节点多盘集群(MNMD)部署实操
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
旋生万物13 小时前
螺旋数不是玄学:Python 实现广义复数与等角螺线
开发语言·人工智能·python·云原生·aigc·软件工程·后端开发