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 过户,平滑承接。
相关推荐
梦想的颜色2 小时前
Docker 知识全貌:一份体系化的知识结构报告
docker·云原生·容器·eureka
zhangfeng11332 小时前
国家超算中心K8s 容器服务,新版容器和老版本的一些坑
云原生·容器·kubernetes
开发者联盟league15 小时前
使用k8s安装Sonarqube
云原生·容器·kubernetes
小义_18 小时前
【Ansible】(三)基础配置与连接设置
云原生·ansible
运维老郭1 天前
Kubernetes 二进制部署完全指南:从零搭建生产级HA集群
运维·云原生·kubernetes
宇明一不急1 天前
k8s headless svc
云原生·容器·kubernetes
容器魔方1 天前
Karmada v1.18 版本发布!新增混合云溢出式调度能力
人工智能·云原生·容器·华为云·云计算
tianyuanwo1 天前
容器全生命周期管理实战:从查看到调优的深度总结
云原生·容器管理
无聊的老谢1 天前
Spring Cloud Alibaba 应用的容器化部署与 K8s 编排
云原生·容器·kubernetes