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

Terraform/OpenTofu 基础

改了锁文件一行,init 就停了

在 TT Lab 中继续学习

目标

亲手创建依赖锁文件,加上约束来收窄,删除后重新生成,破坏哈希再恢复。亲手确认 init 每次都核对什么,以及为什么只提交这个文件而不提交安装目录。

为什么重要

锁文件不是用来阅读的文件,而是团队的契约。选中了哪个版本(version)、允许了什么(constraints)、这个包与当时的那个是否相同(hashes),这三件事都写在这里,init 每次都会核对这三项。没有契约,任何命令都无法生成计划;校验和一旦不符,安装就会当场被拦住。不提交这个文件的团队,每个人会获取到不同的版本,于是“在我的笔记本上是好的”就会以 provider 为单位出现。相反,安装目录不提交——它是因平台而异的二进制文件,会使仓库膨胀,而且 init 随时可以重新生成。

步骤

  1. 在 /root/tfb-lock/app/main.tf 中不带版本约束地声明 local·random 两个 provider,并放置 random_pet.tag(length 2)和把它的 id 写入 note.txt 的 local_file.note。执行 init 后,阅读生成的 .terraform.lock.hcl,在 /root/tfb-lock/lock-read.txt 中写入 local_version=<값>、random_version=<값>、local_hashes=<local 블록의 h1 해시 줄 수> 三行(占位符依次为 local 的版本、random 的版本、local 块中 h1 哈希的行数)。
  2. 重新创建 /root/tfb-lock/pinned/main.tf,这次从一开始就在 local 中写上 version = "2.9.0",random 不带约束。用 local_file.fixed 声明一个 fixed.txt,然后执行 init。接着从锁文件中读取各 provider 的 constraints 值,在 /root/tfb-lock/constraints.txt 中写入 local=<값>(占位符为该值)和 random=none 两行。
  3. 在 /root/tfb-lock/app/.terraform/providers/ 之下找出实际安装的 provider 包的位置,在 /root/tfb-lock/installed.txt 中写入 local=<버전>/<플랫폼>(占位符依次为版本、平台)和 random=<버전>/<플랫폼>(占位符依次为版本、平台)两行。
  4. 创建 /root/tfb-lock/fresh/,只复制 /root/tfb-lock/app/main.tf(不要带来锁文件和 .terraform/)。不执行 init,直接运行 tofu plan,把错误输出保存到 /root/tfb-lock/no-init.txt。在 fresh 中不要执行 init。
  5. 在 /root/tfb-lock/badpin/main.tf 中只使用 local,用 version = "2.5.0" 固定版本,并放置一个 local_file.pinned。运行 init,把失败输出保存到 /root/tfb-lock/pin-fail.txt,然后把约束改为 2.9.0,使 init 成功。
  6. 到这时才给 /root/tfb-lock/app/main.tf 的 local 加上 version = "2.9.0",再次执行 init。确认锁文件的 local 块中是否生成了 constraints 这一行,并在 /root/tfb-lock/stale-lock.txt 的第一行写入 after_init=<그 값, 없으면 none>(占位符为该值,没有则为 none)。然后删除 .terraform.lock.hcl,不用 init,而是用 tofu providers lock -fs-mirror=/opt/tofu-mirror -platform=linux_amd64 重新生成,把它的输出保存到 /root/tfb-lock/relock.txt,并在 stale-lock.txt 的第二行写入 after_relock=<지금 값>(占位符为当前的值)。
  7. 把 /root/tfb-lock/app/.terraform.lock.hcl 中 local 的一行哈希改成任意值,删除 .terraform/ 之后执行 init。把校验和错误的输出保存到 /root/tfb-lock/tamper.txt。然后删除锁文件,重新执行 init,恢复为正确的哈希。
  8. 创建 /root/tfb-lock/audit.sh。读取作为第一个参数传入的目录中的 .terraform.lock.hcl,每行输出一个没有 constraints 行的 provider 块的名称,只要有一个就以退出码 1 结束,没有则以 0 结束。把对 /root/tfb-lock/app 运行的结果保存到 /root/tfb-lock/audit-app.txt,把对 /root/tfb-lock/badpin 运行的结果保存到 /root/tfb-lock/audit-pin.txt,并在每个文件的最后一行追加 exit=<종료 코드>(占位符为退出码)。

参考

阅读第一次 init 生成的契约

在 /root/tfb-lock/app/main.tf 中不带版本约束地声明 local·random 两个 provider,并放置 random_pet.tag(length 2)和把它的 id 写入 note.txt 的 local_file.note。执行 init 后,阅读生成的 .terraform.lock.hcl,在 /root/tfb-lock/lock-read.txt 中写入 local_version=<값>、random_version=<값>、local_hashes=<local 블록의 h1 해시 줄 수> 三行(占位符依次为 local 的版本、random 的版本、local 块中 h1 哈希的行数)。

锁文件是 HCL,每个 provider 会生成一个 provider 块。块中包含 version,必要时还有 constraints,以及 hashes 列表。现在还不需要执行 apply。

一开始就写上约束,契约中就会同时留下允许范围

重新创建 /root/tfb-lock/pinned/main.tf,这次从一开始就在 local 中写上 version = "2.9.0",random 不带约束。用 local_file.fixed 声明一个 fixed.txt,然后执行 init。接着从锁文件中读取各 provider 的 constraints 值,在 /root/tfb-lock/constraints.txt 中写入 local=<값>(占位符为该值)和 random=none 两行。

只有在配置中写了版本约束时,constraints 这一行才会出现在锁文件中。没写约束的 provider,其块中根本没有这一行。选中了什么(version)和允许了什么(constraints),是不同的信息。

找到锁文件所指向的实物在哪里

在 /root/tfb-lock/app/.terraform/providers/ 之下找出实际安装的 provider 包的位置,在 /root/tfb-lock/installed.txt 中写入 local=<버전>/<플랫폼>(占位符依次为版本、平台)和 random=<버전>/<플랫폼>(占位符依次为版本、平台)两行。

安装路径按注册表地址、命名空间、名称、版本、平台的顺序逐级变深。这个目录由 init 重新生成,所以不提交;而锁文件必须由人评审,所以要提交。

没有契约,连计划都生成不了

创建 /root/tfb-lock/fresh/,只复制 /root/tfb-lock/app/main.tf(不要带来锁文件和 .terraform/)。不执行 init,直接运行 tofu plan,把错误输出保存到 /root/tfb-lock/no-init.txt。在 fresh 中不要执行 init。

这是人们在刚拿到的仓库中最先遇到的错误。想一想消息在要求做什么,以及为什么在计划阶段就把它拦住。没有 provider,就无法生成计划。

固定一个镜像源中没有的版本,init 会拒绝

在 /root/tfb-lock/badpin/main.tf 中只使用 local,用 version = "2.5.0" 固定版本,并放置一个 local_file.pinned。运行 init,把失败输出保存到 /root/tfb-lock/pin-fail.txt,然后把约束改为 2.9.0,使 init 成功。

这个 Pod 的 provider 只来自离线镜像源。如果要求镜像源中没有的版本,工具会说没有可选的。在实际工作中,只使用公司内部镜像源的组织也会遇到同样的消息。

后来才加上的约束,不会自动反映到契约中

到这时才给 /root/tfb-lock/app/main.tf 的 local 加上 version = "2.9.0",再次执行 init。确认锁文件的 local 块中是否生成了 constraints 这一行,并在 /root/tfb-lock/stale-lock.txt 的第一行写入 after_init=<그 값, 없으면 none>(占位符为该值,没有则为 none)。然后删除 .terraform.lock.hcl,不用 init,而是用 tofu providers lock -fs-mirror=/opt/tofu-mirror -platform=linux_amd64 重新生成,把它的输出保存到 /root/tfb-lock/relock.txt,并在 stale-lock.txt 的第二行写入 after_relock=<지금 값>(占位符为当前的值)。

如果已经选中的版本仍然符合约束,init 就不会重新选择,因此也不会重新写锁文件。要把允许范围反映到契约中,就必须让该条目重新创建。providers lock 不安装包,只计算并写入校验和。

改动哈希,安装就会被拦住

把 /root/tfb-lock/app/.terraform.lock.hcl 中 local 的一行哈希改成任意值,删除 .terraform/ 之后执行 init。把校验和错误的输出保存到 /root/tfb-lock/tamper.txt。然后删除锁文件,重新执行 init,恢复为正确的哈希。

锁文件中的哈希,是用来确认包与当时的那个是否相同的机制。被改动过的哈希与任何包都不匹配,所以安装会停止。保留错误的哈希,只加上 -upgrade,是解决不了的。

编写找出没有约束的 provider 的检查

创建 /root/tfb-lock/audit.sh。读取作为第一个参数传入的目录中的 .terraform.lock.hcl,每行输出一个没有 constraints 行的 provider 块的名称,只要有一个就以退出码 1 结束,没有则以 0 结束。把对 /root/tfb-lock/app 运行的结果保存到 /root/tfb-lock/audit-app.txt,把对 /root/tfb-lock/badpin 运行的结果保存到 /root/tfb-lock/audit-pin.txt,并在每个文件的最后一行追加 exit=<종료 코드>(占位符为退出码)。

锁文件逐行读取即可。以 provider 开头的行是新块的开始,只需要记住其后是否出现 constraints 这一行。必须在 awk 的 END 块中也判定最后一个块,才不会漏掉一个。