没注意选中的是 prod,就执行了 destroy
目标
打开看看用 workspace 划分环境时,状态实际生成在哪里;亲手制造选错 workspace 的事故,再用守卫拦住它;然后用目录划分做出同样的东西,用数字比较两种方式。
为什么重要
人们会把 workspace 作为划分环境的第一个工具,原因很明确——代码只有一份,provider 缓存也只有一份,一条命令就能切换。但这些优点恰恰就是风险。后端只有一个,意味着使用 dev 的人和使用 prod 的人用同一套凭据打开同一个仓库;而“现在处于哪个环境”并不显示在屏幕上,只写在工作目录内的一个文件里。官方文档写明不要把 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。 - 创建
dev和prodworkspace,并分别在其中 apply。完成后,/root/tfa-ws/out/中必须有三个文件,/root/tfa-ws/terraform.tfstate.d/之下必须有两个状态文件。 - 在
/root/tfa-ws/where.tsv中,按名称顺序(default、dev、prod)每行写一个 workspace。用制表符分隔的三列依次是<workspace 이름>(占位符为 workspace 名称)、/root/tfa-ws 기준 상태 파일 상대경로(韩文,意为“相对于 /root/tfa-ws 的状态文件路径”)和그 상태의 lineage(韩文,意为“该状态的 lineage”)。 - 修改
/root/tfa-ws/sizes.tf,从local.sizes映射(default = 1、dev = 2、prod = 6)中用lookup取出当前 workspace 的值,作为local.replicas使用。在三个 workspace 中都重新 apply 后,out/ 中的三个文件会有互不相同的 replicas 值。 - 在选中
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 进行恢复。 - 创建
/root/tfa-ws/guard.sh。当作为参数传入的名称与当前选中的 workspace 相同时,输出确认消息并以 0 退出;不同时,显示当前选中的名称并以 1 退出;没有参数时以 2 退出。不要修改 workspace。 - 在
/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。 - 在
/root/tfa-ws/compare.tsv中,用两列(以制表符分隔)写入下面五行。不要编造数值,要现在从磁盘上统计后再写。workspace_states= workspace 方式拥有的状态文件数,directory_states= envs/ 之下的状态文件数,workspace_plugin_dirs= workspace 方式的.terraform目录数,directory_plugin_dirs= envs/ 之下的.terraform目录数,selected_marker= 记录当前选中 workspace 的文件的相对路径。
参考
- Pod 中有 OpenTofu 1.9.0 和 local provider 的 mirror,无需联网即可运行。本实验的后端全部是 local。
- 在 local 后端中,默认 workspace 的状态是工作目录中的 terraform.tfstate,其他 workspace 则放在 terraform.tfstate.d 之下。各个后端的路径规则不同。
- 常见错误:在第 2 步对已经存在的名称再次调用 workspace new。应该用 select 切换。
- 常见错误:在第 4 步只修改 sizes.tf,却只在一个 workspace 中运行 apply。状态在每个 workspace 中各自独立,所以必须分别应用。
- Workspaces · Managing Workspaces (CLI) · Command: workspace · Backend: local
默认 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 无法构成凭据边界”的答案——因为后端和缓存都只有一份。