TT Lab
开始
学习 学习路径 课程

Terraform/OpenTofu 基础

init 和锁文件守住的东西

在 TT Lab 中继续学习

一句话总结

.terraform.lock.hcl 是一份契约,写明了“允许了什么、选了什么,以及它是不是真的那个”,而 init 就是每次都要核对这份契约的命令。

为什么需要这个文件

provider 不在配置文件里。配置文件里只写了名称和版本约束,实物是由 init 下载的。所以拿到同一个仓库的两个人,如果在不同的时间运行 init,手里可能拿到不同的包。如果期间 provider 发布了新版本,一个人用旧版本、另一个人用新版本来生成计划。计划不同,评审就失去了意义。

有人会说,写上版本约束不就行了?但仅有约束是不够的。约束通常允许一个范围。在这个范围内具体选中了哪一个,约束中并没有写;选中的包是否与昨天下载的那个包字节完全相同,也无从得知。于是就有了这个把三件事分别记下来的文件。

provider "registry.opentofu.org/hashicorp/local" {
  version     = "2.9.0"
  constraints = "2.9.0"
  hashes = [
    "h1:rxomJjDwOo+YZ+WIPc25FqEgsz9orh/2MCyUcZmFjvw=",
  ]
}

version 是选中的版本,constraints 是允许的范围,hashes 是这个包的指纹。constraints 这一行只有在配置中写了版本约束时才会出现——没写约束的 provider,其块中根本没有这一行。

工作原理

init 依次这样运行:从配置中收集所需的 provider 和约束;如果锁文件中已经有选好的版本,就尝试原样使用;如果没有,或者与约束不符,就重新选择。在安装选中的包之前,先与锁文件中的哈希比对,不匹配就拒绝安装。

拒绝时显示的样子如下。

Error: Failed to install provider

Error while installing hashicorp/local 2.9.0: the current package for
registry.opentofu.org/hashicorp/local 2.9.0 doesn't match any of the
checksums previously recorded in the dependency lock file

出现这个错误时,即使加上 -upgrade 也解决不了。如果版本选择不变,就会再次被同一项检查拦住。如果原因是被改动过的哈希,老老实实的恢复办法,就是丢掉契约,重新创建。

在收紧版本约束这一侧,有一个人们不太了解的漏洞。锁文件中的 constraints 这一行,是在该条目最初创建时写入的。如果已经选中的版本仍然符合新的约束,init 就没有理由重新选择,所以根本不会写锁文件,因此后来才加上的约束不会出现在契约中。即使加上 -upgrade,只要选择不变,情况也一样。要把允许范围反映到契约中,就必须让该条目重新创建——删除锁文件再重新生成,才是老老实实的做法。

如果没有任何一个版本符合范围,情况就不同了。那时它会尝试重新选择,并说没有可选的。

Could not resolve provider hashicorp/local: no available releases match the
given constraints 2.5.0

约束有两种常见的写法。一种是固定精确的版本,另一种是只放开补丁版本的波浪箭头运算符。

version = "2.9.0"     # 정확히 이것만
version = "~> 2.9"    # 2.x 안에서 2.9 이상, 3.0 미만

init 创建的另一样东西是 .terraform/providers/ 目录。其中,实际的二进制文件放在按注册表地址、命名空间、名称、版本、平台逐级变深的路径下。它是因平台而异的可执行文件,所以不提交。因为 init 随时可以重新生成,丢失了也没有损失。相反,锁文件是必须由人来评审的变更,所以一定要提交——provider 版本升级,与代码变更一样是重要的事件。

还有不经过 init 就能创建或修改锁文件的命令。tofu providers lock 不安装包,只计算并写入校验和。如果环境只能访问公司内部镜像源,用 -fs-mirror 指向那个镜像源即可;如果团队在多个平台上使用同一个仓库,就多次传入 -platform,预先填好各平台的哈希。只在 Linux 上执行过 init 的锁文件,拿到 Mac 上使用时,会因“没有这个平台的哈希”而被拦住,这就是对它的预防措施。

在现场相遇的样子

最常见的事故,是把锁文件放进了 .gitignore 的仓库。每个人获取到不同的版本,某天 provider 改变了默认值,就只有一个人的 apply 会替换资源。第二种是在合并冲突时用手去修改锁文件。由人来编辑哈希行,十有八九会出偏差,从那以后,这个分支上就没有人能执行 init 了。冲突不要用手去解,而应先选定一方,再重新生成,这才是正道。

第三种是只有 CI 失败的情形。开发者的笔记本上有缓存,锁文件略有偏差也能蒙混过关,而每次都从空白工作区开始的 CI 会立刻被拦住。所以修改了锁文件的提交,合并之前一定要让 CI 跑一遍。

下一项实验要做什么

用 Pod 的离线镜像源走完八个步骤。打开第一次 init 生成的锁文件,读取版本和哈希的数量;把下载到的版本固定为约束,确认会出现 constraints 这一行;找到已安装的包的实际路径。接着在没有锁文件的情况下尝试生成计划;固定一个镜像源中没有的版本,体验被拒绝;不经过 init,只重新生成锁文件。最后两步,故意破坏哈希,确认安装被拦住并恢复,然后亲手编写一个检查脚本,找出没有约束的 provider。