2. Terraform核心机制回顾

在进入平台化交付之前,有必要把 Terraform 的几条核心机制重新梳理一遍。平台化并不是另起一套体系,而是在这些原生机制之上叠加抽象、约束与自动化。理解它们各自的职责边界,才能判断哪些能力可以封装、哪些必须保留给使用者。

Provider:与外部 API 的适配层

Provider 是 Terraform 与真实基础设施之间的翻译器。它把 HCL 中声明的资源类型映射为对云厂商、Kubernetes、DNS、监控系统等 API 的调用。每个 Provider 需要声明来源地址与版本约束,例如:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

provider "aws" {
  region = "cn-north-1"
}

平台化场景下,Provider 的版本与认证方式是最需要收敛的部分。版本约束若由各业务团队自由填写,同一平台内会出现多个不兼容的 Provider 版本,导致状态迁移困难。认证信息则不应出现在业务代码里,而应通过环境变量、OIDC 角色或平台注入的凭据链提供。Provider 的 alias 机制可以支持多区域、多账号,但 alias 命名规范应由平台统一约定,否则后续策略校验无法稳定识别目标账号。

Resource 与 Data Source:声明与查询的两类对象

Resource 描述"我要创建或管理什么",Data Source 描述"我要读取什么已存在的信息"。二者在语法上相似,但语义完全不同:Resource 参与状态写入与生命周期管理,Data Source 只在 plan 阶段读取,不写入 state 中的资源实例。

data "aws_vpc" "shared" {
  tags = { Name = "platform-shared-vpc" }
}

resource "aws_subnet" "app" {
  vpc_id            = data.aws_vpc.shared.id
  cidr_block        = "10.20.1.0/24"
  availability_zone = "cn-north-1a"
}

平台化时,Data Source 常被用来引用平台预置的网络、密钥、镜像等共享资产。这里有一个容易忽略的边界:Data Source 的读取结果会进入 plan 的 diff 计算,如果它依赖的远端对象在 apply 前发生变化,plan 结果可能失效。因此平台应尽量把共享资产的关键属性固化为变量或远程 state 输出,而不是让业务模块在每次 plan 时动态查询。

State:唯一的事实来源

State 记录 Terraform 认为已存在的资源及其属性映射。它既是 plan 的输入,也是 apply 的写入目标。平台化最核心的治理动作之一,就是 State 的隔离与访问控制。常见做法是按环境、账号、业务域划分独立的 state 文件,并使用远程后端(如 S3 + DynamoDB 锁、Terraform Cloud、GitLab 后端)保证并发安全。

需要特别注意的是,state 中包含敏感属性(如数据库密码、密钥材料),因此后端必须启用加密与访问审计。平台不应允许业务团队直接下载或手工编辑 state,而应通过受控的 CI 流水线执行 plan/apply。当出现资源漂移时,优先使用 terraform import 或 terraform state mv/rm 修正,而不是手工改 JSON。

Plan/Apply 工作流:从差异到收敛

标准工作流是 init → validate → plan → apply。plan 阶段会刷新 state、读取 Data Source、计算目标配置与当前状态的差异,输出将要执行的动作(create、update、replace、destroy)。apply 则按依赖图顺序执行这些动作并更新 state。

平台化后,这条链路通常被包装为流水线:合并请求触发 plan,人工审批后触发 apply。关键边界在于:plan 与 apply 之间必须使用同一份 plan 文件(terraform plan -out=tfplan 后 terraform apply tfplan),否则中间的状态变化会导致实际执行与审批内容不一致。此外,plan 输出需要被结构化解析,才能实现"禁止 destroy 生产数据库""限制高危资源类型"等策略。

Terraform 核心机制与平台化边界 配置层 (HCL) Resource 声明 Data Source 查询 变量 / 模块输入 平台边界:模块接口标准化 Provider 适配层 版本约束 / 来源地址 认证链 / alias 多账号 API 调用翻译 平台边界:版本与凭据收敛 State 事实来源 资源属性映射 远程后端 + 锁 敏感属性加密 平台边界:隔离与访问控制 Plan / Apply 工作流 init / validate plan -out 审批 / 策略 apply tfplan state 更新 平台化需特别关注的边界 版本收敛 Provider / 模块统一 State 隔离 环境 / 账号 / 业务域 凭据托管 OIDC / 环境变量注入 策略校验 plan 结构化解析

平台化场景下的关键边界

把上述机制放进平台语境,会浮现出几条必须提前划定的边界。

  • 版本边界:Provider 与模块版本由平台锁定,业务侧只能选择平台发布的版本组合,避免"同一平台、多套依赖"的碎片化。
  • 状态边界:state 按环境与账号隔离,跨 state 的引用通过远程输出或 Data Source 完成,禁止直接读取他人 state 文件。
  • 凭据边界:认证信息不进入代码仓库,由 CI 运行时通过 OIDC 或密钥管理服务注入,Provider 配置只声明角色与区域。
  • 执行边界:plan 与 apply 必须使用同一份 plan 文件,且 apply 只能由受控流水线触发,人工不得在本地对生产 state 执行 apply。
  • 策略边界:plan 输出需被结构化解析,对 destroy、替换、高危资源类型设置拦截规则,把"能不能做"的判断前移到审批之前。
判断一个平台化设计是否合理,可以问三个问题:业务团队是否还需要自己写 Provider 认证?是否还需要自己管理 state 后端?是否还能绕过流水线直接 apply?如果答案都是"不需要/不能",说明边界已经收敛到位。

回顾这些机制的目的不是复述语法,而是为后续的平台抽象找到锚点。Provider 决定能力边界,Resource/Data Source 决定表达方式,State 决定事实归属,Plan/Apply 决定变更路径。平台化的每一步设计,本质上都是在这四者之上做约束与封装。