2. Terraform核心机制回顾
在把 Terraform 从“能跑通”推进到“可复用、可治理的平台”之前,必须先把几个核心机制看清楚:state 记录了什么、provider 如何决定操作对象、resource 与 data source 如何分工、lifecycle 如何影响变更行为。模块化平台并不是把这些概念重新发明一遍,而是把它们组织成稳定的边界和契约。下面按“机制本身—在模块中的位置—实际影响”来展开。
state:模块化平台的唯一事实来源
Terraform 的 state 文件记录了“配置中的资源地址”与“真实基础设施对象”之间的映射,同时缓存了资源属性。没有 state,Terraform 无法判断一个资源是已存在需要更新,还是不存在需要创建。在模块化平台中,state 的划分方式直接决定了团队协作方式:通常按环境(dev/staging/prod)和按层(网络、数据、应用)拆分 state,而不是把所有资源塞进一个 state。
state 的地址由模块路径和资源名共同构成。例如根模块调用 module "network",模块内定义 resource "aws_vpc" "main",其完整地址是 module.network.aws_vpc.main。这个地址一旦变化,Terraform 会认为旧资源需要销毁、新资源需要创建。因此模块重构时,移动资源必须使用 moved 块或 terraform state mv,而不是直接改名字。
# 模块重构时保留资源身份
moved {
from = aws_vpc.main
to = aws_vpc.primary
}
# 跨 state 迁移资源
terraform state mv \
module.network.aws_subnet.private[0] \
module.network.aws_subnet.private_a
远程后端是平台化的前提。把 state 放在 S3、GCS、Azure Blob 或 Terraform Cloud 中,并开启锁机制,可以避免多人同时 apply 导致状态损坏。生产环境还应开启版本控制与加密,因为 state 中可能包含密码、连接串等敏感值。
provider:决定“在哪里、以什么身份”操作
provider 是 Terraform 与具体 API 之间的适配层。它负责认证、区域/端点选择、资源类型注册。模块化平台中,provider 通常由根模块统一配置,子模块只声明 required_providers 和 required_version,不写死认证信息。这样同一套模块可以在不同账号、不同区域复用。
provider 的传递有两种常见方式:显式传入 providers = { aws = aws.us_east_1 },或依赖隐式继承。跨区域、跨账号场景必须显式传递,否则子模块会使用默认 provider,导致资源建在错误位置。平台层还应固定 provider 版本范围,例如 version = "~> 5.0",避免某次 init 拉到不兼容的大版本。
resource 与 data source:声明“我要什么”与“我查什么”
resource 表达期望状态,Terraform 负责创建、更新、销毁;data source 只读查询已有对象,不参与生命周期管理。模块化平台中,二者的边界要清晰:模块内部创建的资源用 resource;对外部已有资源(如已有 VPC、已有镜像、已有 DNS 区域)用 data source 引用,而不是重新创建。
一个典型组合是:data source 查询最新 Amazon Linux 2 AMI,resource 用它创建实例。这样镜像更新时只需重新 apply,无需改硬编码 ID。但要注意 data source 在 plan 阶段就会读取,若查询结果变化,会触发依赖它的资源重建。
data "aws_ami" "al2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.al2.id
instance_type = var.instance_type
subnet_id = var.subnet_id
}
lifecycle:控制变更行为与保护边界
lifecycle 元参数决定资源在创建、更新、销毁时的特殊行为。模块化平台中最常用的是 create_before_destroy、prevent_destroy、ignore_changes 和 replace_triggered_by。它们不是语法糖,而是平台稳定性的护栏。
create_before_destroy = true:先建新再删旧,适合负载均衡器、安全组等不能中断的对象。prevent_destroy = true:防止误删数据库、state 存储桶等关键资源;但会阻止合法销毁,需配合流程审批。ignore_changes = [tags["LastModified"]]:忽略外部系统写入的字段,避免每次 plan 都出现漂移。replace_triggered_by:当依赖对象变化时强制替换,例如 AMI 更新后重建实例。
在模块中,lifecycle 应尽量由模块内部根据资源语义决定,而不是全部暴露给调用方。否则调用方可能误设 prevent_destroy 导致无法清理测试环境。平台层可以通过变量控制是否启用保护,例如 var.enable_deletion_protection。
核心机制在模块化平台中的协同关系
把上述机制放在一起看:provider 决定操作目标,resource 与 data source 描述期望与查询,state 记录映射与属性,lifecycle 约束变更行为。模块则是这些机制的封装单元——输入变量、输出值、provider 传递、state 边界共同构成模块契约。下图用一张关系图展示它们之间的依赖与数据流向,帮助在排查问题时快速定位是认证、状态、查询还是生命周期导致的差异。
把机制落到模块契约上
理解了上述关系后,模块设计就有了明确抓手。一个可复用的网络模块,通常接收 vpc_cidr、azs、tags 等变量,内部用 resource 创建 VPC、子网、路由表,用 data source 查询可用区,输出 vpc_id、subnet_ids。provider 由根模块传入,state 由根模块后端统一管理,lifecycle 在数据库等关键资源上启用保护。这样调用方只需关心输入输出,不必了解内部实现。
实际排障时,可以按以下顺序判断:terraform plan 报认证或区域错误,先查 provider 配置与传递;计划中出现大量非预期替换,查 state 地址是否变化、data source 查询结果是否漂移;销毁阶段被阻止,查 lifecycle 的 prevent_destroy;跨环境行为不一致,查模块变量默认值与 provider 版本约束。把这些机制当成平台的“骨架”,模块化才不会退化成简单的文件拆分。