Terraform 依赖管理实用指南
Source: Dev.to
TL;DR
- 将 Terraform 依赖管理视为两套不同的系统:提供者通过
.terraform.lock.hcl进行选择和固定(默认即可复现),而模块没有锁文件,除非你固定到精确版本或 git 引用,否则会随时间漂移。 - 对 Terraform CLI 使用有界范围(
required_version),对根模块中的提供者使用悲观约束(~>)。 - 在可复用的子模块中,倾向于使用宽松的最低版本(仅在必要时添加可选的上限),让根模块完成最终解析。
- 对于模块,明确在精确固定(最大可复现性)和
~>范围(更易升级)之间做选择,并配合严格的terraform init -upgrade工作流。
指定版本约束,运行 terraform init,完成——但 提供者和模块遵循不同的解析和持久化规则。提供者被锁定;模块没有锁定。正是这种不对称导致团队在“没有任何更改”的配置在不同机器或 CI 运行中产生不同结果时感到惊讶。
本文中,根模块 指的是你运行的顶层 Terraform 配置(即 init/plan/apply 的目录)。可复用模块 指的是被其他配置消费的库式模块。我们将从机制出发,构建可实践、可测试的策略。
真正的问题:“约束”并不等同于“固定”
版本约束是对可接受版本的过滤器(例如 >= 5.0)。相同的运算符在 Terraform 是否记录所选结果时,会产生截然不同的稳定性。
持久化差异:
| 构件 | 是否记录在锁文件中? | 行为 |
|---|---|---|
| 提供者 | 是(.terraform.lock.hcl) | 选定的版本默认被锁定并复用 |
| 模块 | 否 | 随着新版本发布,范围会漂移 |
关键洞察: 相同的运算符在 Terraform 是否记录所选结果时,会产生截然不同的稳定性。
一个可以推理的思维模型
graph TD
A[User defines constraints] --> B[Terraform resolves versions]
B --> C{Provider?}
C -->|Yes| D[Write to .terraform.lock.hcl]
C -->|No| E[No lock file entry]
D --> F[Future runs reuse locked version]
E --> G[Future runs may pick newer module version]
此行为已有文档说明:锁文件覆盖 提供者,而不覆盖 模块。
运算符:它们真正为你买到的东西
Terraform 支持标准比较运算符以及悲观约束 ~>(“仅允许对最右侧指定的组件进行更改”,即一种方便的有界范围)。
如何理解每个运算符
| 运算符 | 含义(操作层面) | 主要风险 |
|---|---|---|
= | 硬性固定 | 除非手动更改,否则会阻止 bug‑fix/安全更新 |
>=(单独使用) | “更新的版本都可以接受” | 未来可能出现破坏性变化 + 漂移;取决于锁定行为 |
~> | 方便的有界范围(例如 ~> 5.0 表示 >= 5.0.0, < 6.0.0) | 如果选错精度,容易出现约束过宽或过窄 |
示例解释(Terraform 语义)
提供者版本约束
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
~> 5.0转换为>= 5.0.0, < 6.0.0。- 锁文件记录了实际选中的提供者版本,使后续运行可复现,除非执行
terraform init -upgrade。
模块版本约束
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}
- 同样的
~> 5.0范围适用,但 不会为该模块创建锁文件条目。 - 除非使用
-upgrade明确升级或添加更严格的约束,否则未来运行可能会选取更新的5.x发行版。