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

Terraform 实战

没注意选中的是 prod,就执行了 destroy

在 TT Lab 中继续学习

目标

打开看看用 workspace 划分环境时,状态实际生成在哪里;亲手制造选错 workspace 的事故,再用守卫拦住它;然后用目录划分做出同样的东西,用数字比较两种方式。

为什么重要

人们会把 workspace 作为划分环境的第一个工具,原因很明确——代码只有一份,provider 缓存也只有一份,一条命令就能切换。但这些优点恰恰就是风险。后端只有一个,意味着使用 dev 的人和使用 prod 的人用同一套凭据打开同一个仓库;而“现在处于哪个环境”并不显示在屏幕上,只写在工作目录内的一个文件里。官方文档写明不要把 workspace 用于系统分解或凭据各不相同的部署,原因就在于此。所以实际工作中的选择不是“哪个才对”,而是“用什么来划分什么”——同一个团队使用的临时副本用 workspace,权限分开的边界则用目录。

步骤

  1. 在 /root/tfa-ws/main.tf 中放入 local provider 和 local_file.env。文件名为 out/<workspace 이름>.conf,内容的第一行为 workspace=<workspace 이름>,第二行为 replicas=<local.replicas>(前两处的占位符均为 workspace 名称)。在 /root/tfa-ws/sizes.tf 中,只用 locals 放入 replicas = 1。执行 init 和 apply。
  2. 创建 dev 和 prod workspace,并分别在其中 apply。完成后,/root/tfa-ws/out/ 中必须有三个文件,/root/tfa-ws/terraform.tfstate.d/ 之下必须有两个状态文件。
  3. 在 /root/tfa-ws/where.tsv 中,按名称顺序(default、dev、prod)每行写一个 workspace。用制表符分隔的三列依次是 <workspace 이름>(占位符为 workspace 名称)、/root/tfa-ws 기준 상태 파일 상대경로(韩文,意为“相对于 /root/tfa-ws 的状态文件路径”)和 그 상태의 lineage(韩文,意为“该状态的 lineage”)。
  4. 修改 /root/tfa-ws/sizes.tf,从 local.sizes 映射(default = 1、dev = 2、prod = 6)中用 lookup 取出当前 workspace 的值,作为 local.replicas 使用。在三个 workspace 中都重新 apply 后,out/ 中的三个文件会有互不相同的 replicas 值。
  5. 在选中 prod 的状态下运行 tofu destroy -auto-approve,亲手制造事故。输出保存到 /root/tfa-ws/incident/prod-destroy.log,当时选中的 workspace 名称保存到 /root/tfa-ws/incident/selected.txt(原样复制 .terraform/environment)。然后在 /root/tfa-ws/incident.md 中写入 selected:、command:、lost:、recovery: 四行,并重新对 prod 执行 apply 进行恢复。
  6. 创建 /root/tfa-ws/guard.sh。当作为参数传入的名称与当前选中的 workspace 相同时,输出确认消息并以 0 退出;不同时,显示当前选中的名称并以 1 退出;没有参数时以 2 退出。不要修改 workspace。
  7. 在 /root/tfa-ws/modules/app/main.tf 中放置一个接收 var.env 和 var.replicas、并写入 /root/tfa-ws/out-dir/<env>.conf 的模块(占位符为环境名称),然后在 /root/tfa-ws/envs/dev 和 /root/tfa-ws/envs/prod 两个目录中,分别以 env="dev" replicas=2 和 env="prod" replicas=6 调用这个模块。两个目录各自独立地执行 init 和 apply。不使用 workspace。
  8. 在 /root/tfa-ws/compare.tsv 中,用两列(以制表符分隔)写入下面五行。不要编造数值,要现在从磁盘上统计后再写。 workspace_states = workspace 方式拥有的状态文件数,directory_states = envs/ 之下的状态文件数,workspace_plugin_dirs = workspace 方式的 .terraform 目录数,directory_plugin_dirs = envs/ 之下的 .terraform 目录数,selected_marker = 记录当前选中 workspace 的文件的相对路径。

参考

默认 workspace 的状态在哪里

在 /root/tfa-ws/main.tf 中放入 local provider 和 local_file.env。文件名为 out/<workspace 이름>.conf,内容的第一行为 workspace=<workspace 이름>,第二行为 replicas=<local.replicas>(前两处的占位符均为 workspace 名称)。在 /root/tfa-ws/sizes.tf 中,只用 locals 放入 replicas = 1。执行 init 和 apply。

在配置中,用 terraform.workspace 读取当前选中的 workspace 名称(在 OpenTofu 中,这个名称仍然叫 terraform)。默认 workspace 的状态还不会放进特殊目录——apply 之后,请直接对工作目录执行 ls 看看。

再创建两个 workspace,状态会放到哪里

创建 dev 和 prod workspace,并分别在其中 apply。完成后,/root/tfa-ws/out/ 中必须有三个文件,/root/tfa-ws/terraform.tfstate.d/ 之下必须有两个状态文件。

tofu workspace new 在创建的同时也会选中它。如果名称已存在会报错,请改用 select 切换。用 find 确认非默认 workspace 的状态生成在哪个路径,并查看三个状态的 lineage 是否互不相同。

记录哪个 workspace 的状态是哪个文件

在 /root/tfa-ws/where.tsv 中,按名称顺序(default、dev、prod)每行写一个 workspace。用制表符分隔的三列依次是 <workspace 이름>(占位符为 workspace 名称)、/root/tfa-ws 기준 상태 파일 상대경로(韩文,意为“相对于 /root/tfa-ws 的状态文件路径”)和 그 상태의 lineage(韩文,意为“该状态的 lineage”)。

状态文件是 JSON,所以 lineage 用 jq 读取。这一步的关键是:默认 workspace 的路径与另外两个的路径不同。不要在相对路径前加 ./。

让同一份代码在每个环境取不同的值

修改 /root/tfa-ws/sizes.tf,从 local.sizes 映射(default = 1、dev = 2、prod = 6)中用 lookup 取出当前 workspace 的值,作为 local.replicas 使用。在三个 workspace 中都重新 apply 后,out/ 中的三个文件会有互不相同的 replicas 值。

lookup 的第三个参数,决定了当选中的名称不在映射中时应得到什么。在 locals 内部引用其他 local 也可以。修改值之后,必须在每个 workspace 中各运行一次 apply,文件才会更新。

选错了 workspace 就下达命令

在选中 prod 的状态下运行 tofu destroy -auto-approve,亲手制造事故。输出保存到 /root/tfa-ws/incident/prod-destroy.log,当时选中的 workspace 名称保存到 /root/tfa-ws/incident/selected.txt(原样复制 .terraform/environment)。然后在 /root/tfa-ws/incident.md 中写入 selected:、command:、lost:、recovery: 四行,并重新对 prod 执行 apply 进行恢复。

workspace 把当前的选择记在工作目录内的一个文件里——请打开 .terraform/environment 看看。事故的原因不是“命令敲错了”,而是“在哪里执行命令,屏幕上任何地方都看不到”。lost 行写消失的文件路径,recovery 行写用来恢复的命令。

在写入命令前放一个守卫

创建 /root/tfa-ws/guard.sh。当作为参数传入的名称与当前选中的 workspace 相同时,输出确认消息并以 0 退出;不同时,显示当前选中的名称并以 1 退出;没有参数时以 2 退出。不要修改 workspace。

当前选中的名称用 tofu workspace show 一行就能得到。把退出码分成三个,是因为在 CI 中需要区分“不对”和“调用有误”。评分器会用三种方式直接调用这个脚本。

用目录划分做出同样的东西

在 /root/tfa-ws/modules/app/main.tf 中放置一个接收 var.env 和 var.replicas、并写入 /root/tfa-ws/out-dir/<env>.conf 的模块(占位符为环境名称),然后在 /root/tfa-ws/envs/dev 和 /root/tfa-ws/envs/prod 两个目录中,分别以 env="dev" replicas=2 和 env="prod" replicas=6 调用这个模块。两个目录各自独立地执行 init 和 apply。不使用 workspace。

模块文件的内容格式与第 1 步相同,也是两行(workspace= 和 replicas=)。目的是在不使用 workspace 的情况下做出相同的结果。apply 之后,请确认两个目录各自拥有自己的状态和自己的 provider 缓存。

用数字比较两种方式

在 /root/tfa-ws/compare.tsv 中,用两列(以制表符分隔)写入下面五行。不要编造数值,要现在从磁盘上统计后再写。 workspace_states = workspace 方式拥有的状态文件数,directory_states = envs/ 之下的状态文件数,workspace_plugin_dirs = workspace 方式的 .terraform 目录数,directory_plugin_dirs = envs/ 之下的 .terraform 目录数,selected_marker = 记录当前选中 workspace 的文件的相对路径。

四个数字用 find 统计(envs/ 之下的不要计入 workspace 一侧)。最后一行就是第 5 步中复制过的那个文件。这五行就是“为什么 workspace 无法构成凭据边界”的答案——因为后端和缓存都只有一份。