别名与版本约束:用哪份配置来创建
一句话总结
alias 是给同一个 provider 的第二套配置贴上名称标签,而版本约束则是规定明天早上 CI 最多可以接受到哪一个新版本。两者都是把“选择”留在代码中的机制。
为什么需要它
provider 配置包含凭据、端点、区域这类连接信息。资源会从中选取一个来创建。只有一套配置时没有可选的,所以这个选择并不显眼。一旦出现第二套,每个资源都必须回答:你是从哪一边创建的?
如果不回答这个问题,工具就会使用默认配置。而且是悄悄地使用。所以“本想在 DR 区域创建,却又在主区域创建了一份”这样的事故,会不报任何错误地过去。更糟的还在后面。状态文件会记录该资源是用哪套配置创建的,以后删除时也会用同一套配置去删除。错误的配对就这样固定下来。
版本约束是同一个问题的另一个方向。如果不写约束,今天一切正常,明天 CI 上却出现从未见过的错误,因为发布了新版本。反过来,如果钉死在某一个版本,即使发布了安全修复,也得手动升级。约束就是写明你要站在这两者之间的哪个位置。
工作原理
需要区分三件事。
provider "random" {} # 기본 설정 — 이름표 없음
provider "random" {
alias = "seeded" # 두 번째 설정 — 이름표 seeded
}
resource "random_pet" "a" {
provider = random.seeded # 리소스는 provider (단수)
}
module "site" {
providers = { # 모듈은 providers (복수, 맵)
random.east = random
random.west = random.seeded
}
}
- 资源用
provider,模块用providers。两者名称相似,经常写错,但含义完全不同:一个表示“我使用这套配置”,另一个表示“模块内部的这个名称,对应外部的这套配置”。 - 模块不会自己创建配置。如果在模块内部声明
provider块,这个模块就会自己决定凭据,导致无法被使用两次。取而代之的是,用configuration_aliases只声明“我要接收这样名称的配置”这个位置。如果调用方没有填上这个位置,init会明确指出哪个名称没有接收到并予以阻止。 - 状态会记住。状态文件中每个资源都有
provider字符串,只有用别名创建的资源结尾不同。无论怎么修改代码,已经创建的资源的配对仍然以状态中所写的为准。
版本约束的位数决定了含义。波浪号约束只允许最右边的一位升高。写两位时,会接受中间那位升高;写三位时,只接受最末一位。在实验中,你会在同一个 mirror 上亲眼看到这两者给出不同的结果。
核心版本约束(required_version)则是另一个层次。它在下载插件之前就进行检查,所以即使完全没有 required_providers 也会被阻止。被阻止时,工具会直接告诉你当前使用的版本。
在现场相遇的样子
最常见的事故是:调用模块两次时复制了 provider 映射,却漏改了一行。语法正确,init 也能通过。第二次调用与第一次使用了同一套配置,本应分在两个区域的东西全都挤到了一边。要在评审中发现,只能让每次模块调用都把整个映射看一遍。
第二种是不写约束地使用,某一天只有 CI 坏了。人工操作的工作目录里已经有锁文件,所以一直使用旧版本,只有干净的 CI 工作空间才会拉取新版本。这是“在我的电脑上是好的”的典型例子。提交锁文件的原因就在这里。
第三种是把约束收得太窄然后忘了。钉死在某一版本的配置散落在几十个仓库中,发布安全修复时,光是找出需要升级的地方就成了第一件要做的事。在实际工作中,会采用只放宽 dev 侧仓库的约束、让它先升级试一试的方式来安排顺序。
第四种是用环境名称作别名。如果写成 provider "aws" { alias = "prod" },读起来很顺,但这套配置对应哪个区域、哪个账号,仍然看不出来。以后迁移账号,名称与实际就会错位,而错位的名称没有人会去改。别名最好用指向连接对象的名称来起,这样更持久。
总结起来,本模块所处理的两件事,都是把“选择”留在代码中。为每个资源写明用哪套配置来创建,状态就会记住这个选择;用约束写明最多可以接受到哪个新版本,锁文件就会留下这个决定的依据。两者都不写,工具就会默默地代替你决定,而这个决定只有在事故发生之后才会看到。
下一项实验要做什么
在 /root/tfa-alias 中配置两套 random provider,为每个资源选择其一,并把这些配置传给模块。然后从映射中去掉一行,让 init 用错误告诉你它在说什么;要求 mirror 中没有的版本并被拒绝;确认两种波浪号约束允许的内容互不相同;最后从锁文件和状态中读取值,整理成表格。