prod を選んだまま destroy を叩いた
目標
ワークスペースで環境を分けるとき、状態が実際にどこにできるかを開いて確認し、選択ミスの事故を自分で起こしたあと、ガードで防ぎ、同じものをディレクトリ分離で作って、2つの方式を数字で比べます。
なぜ重要なのか
環境を分ける最初のツールとして、ワークスペースを選ぶ理由は明確です。コードが1つで、プロバイダーのキャッシュも1つで、コマンド1行で乗り換えられます。ところが、その利点が、そのまま危険です。バックエンドが1つということは、devを使う人とprodを使う人が、同じ認証情報で同じストレージを開くという意味であり、「いまどの環境か」は、画面ではなく、作業ディレクトリ内のファイル1つにだけ書かれています。公式ドキュメントが、ワークスペースをシステムの分解や認証情報が分かれるデプロイに使うなと書いている理由が、これです。そのため、実務での選択は、「どちらが正しいか」ではなく、「何を何で分けるか」です。同じチームが使う一時的なコピーはワークスペースで、権限が分かれる境界はディレクトリで分けます。
ステップ
/root/tfa-ws/main.tfにlocalプロバイダーとlocal_file.envを置いてください。ファイル名はout/<workspace 이름>.conf(プレースホルダーはワークスペース名です)、内容は1行目がworkspace=<workspace 이름>、2行目がreplicas=<local.replicas>です。/root/tfa-ws/sizes.tfには、localsでreplicas = 1だけを置きます。initしてapplyしてください。devとprodのワークスペースを作成し、それぞれでapplyしてください。終わったら、/root/tfa-ws/out/に3つのファイルが、/root/tfa-ws/terraform.tfstate.d/の下に2つの状態ファイルがある必要があります。/root/tfa-ws/where.tsvに、ワークスペースを3つ、名前順(default・dev・prod)に1行ずつ書いてください。タブで区切った3つの列は、<workspace 이름>、/root/tfa-ws 기준 상태 파일 상대경로、그 상태의 lineageです(プレースホルダーは順に、ワークスペース名、/root/tfa-wsを基準とする状態ファイルの相対パス、その状態のlineageです)。/root/tfa-ws/sizes.tfを修正して、local.sizesマップ(default = 1、dev = 2、prod = 6)から、いまのワークスペースの値をlookupで取り出し、local.replicasとして使ってください。3つのワークスペースすべてで再度applyすると、out/の3つのファイルが、互いに異なるreplicasの値を持ちます。prodを選択した状態でtofu destroy -auto-approveを回して、事故を自分で起こしてください。出力は/root/tfa-ws/incident/prod-destroy.logに、そのとき選択されていたワークスペース名は/root/tfa-ws/incident/selected.txtに(.terraform/environmentをそのままコピー)残します。そのあと、/root/tfa-ws/incident.mdにselected:、command:、lost:、recovery:の4行を書き、prodを再びapplyして復旧してください。/root/tfa-ws/guard.shを作成してください。引数として受け取った名前と、いま選択されているワークスペースが同じなら、確認メッセージを出して0で、異なるなら、いま選択されている名前を表示して1で、引数がなければ2で終了する必要があります。ワークスペースは変更しません。/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の2つのディレクトリから、そのモジュールを、それぞれenv="dev" replicas=2、env="prod" replicas=6で呼び出してください。2つのディレクトリを、別々にinit・applyします。ワークスペースは使いません。/root/tfa-ws/compare.tsvに、以下の5行を、タブ区切りの2列で書いてください。値は作り上げず、いまディスク上で数えて書きます。workspace_states= ワークスペース方式が持つ状態ファイルの数、directory_states= envs/の下の状態ファイルの数、workspace_plugin_dirs= ワークスペース方式の.terraformディレクトリの数、directory_plugin_dirs= envs/の下の.terraformディレクトリの数、selected_marker= いま選択されているワークスペースを記憶しているファイルの相対パス。
参考
- Podには、OpenTofu 1.9.0とlocalプロバイダーのミラーがあり、インターネットなしで動きます。このラボのバックエンドは、すべてlocalです。
- localバックエンドでは、デフォルトのワークスペースの状態は、作業ディレクトリのterraform.tfstateで、それ以外のワークスペースは、terraform.tfstate.dの下に行きます。バックエンドごとに、パスのルールが違います。
- よくある間違い: ステップ2で、すでにある名前に対してworkspace newをもう一度呼ぶことです。selectで乗り換える必要があります。
- よくある間違い: ステップ4で、sizes.tfだけを直して、1つのワークスペースでだけapplyすることです。状態がワークスペースごとに別なので、それぞれ適用する必要があります。
- Workspaces・Managing Workspaces (CLI)・Command: workspace・Backend: local
デフォルトのワークスペースの状態はどこにあるか
/root/tfa-ws/main.tfにlocalプロバイダーとlocal_file.envを置いてください。ファイル名はout/<workspace 이름>.conf(プレースホルダーはワークスペース名です)、内容は1行目がworkspace=<workspace 이름>、2行目がreplicas=<local.replicas>です。/root/tfa-ws/sizes.tfには、localsでreplicas = 1だけを置きます。initしてapplyしてください。
設定の中で、いま選択されているワークスペース名は、terraform.workspaceで読み取ります(OpenTofuでも名前はterraformのままです)。デフォルトのワークスペースの状態は、まだ特別なディレクトリには行きません。apply後に、作業ディレクトリをそのままlsしてみてください。
ワークスペースをさらに2つ作ると、状態はどこに行くか
devとprodのワークスペースを作成し、それぞれでapplyしてください。終わったら、/root/tfa-ws/out/に3つのファイルが、/root/tfa-ws/terraform.tfstate.d/の下に2つの状態ファイルがある必要があります。
tofu workspace newは、作成と同時に選択までします。すでにある名前ならエラーになるので、selectで乗り換えてください。デフォルトではないワークスペースの状態がどのパスにできるかをfindで確認し、3つの状態のlineageが互いに異なるかも見てください。
どのワークスペースの状態がどのファイルかを書く
/root/tfa-ws/where.tsvに、ワークスペースを3つ、名前順(default・dev・prod)に1行ずつ書いてください。タブで区切った3つの列は、<workspace 이름>、/root/tfa-ws 기준 상태 파일 상대경로、그 상태의 lineageです(プレースホルダーは順に、ワークスペース名、/root/tfa-wsを基準とする状態ファイルの相対パス、その状態のlineageです)。
状態ファイルはJSONなので、lineageはjqで読みます。デフォルトのワークスペースのパスと、残りの2つのパスが違うことが、このステップの核心です。相対パスの前に./を付けないでください。
同じコードが、環境ごとに違う値を持つようにする
/root/tfa-ws/sizes.tfを修正して、local.sizesマップ(default = 1、dev = 2、prod = 6)から、いまのワークスペースの値をlookupで取り出し、local.replicasとして使ってください。3つのワークスペースすべてで再度applyすると、out/の3つのファイルが、互いに異なるreplicasの値を持ちます。
マップにない名前が選択されたとき、何になるべきかを決めるのが、lookupの3つ目の引数です。locals内で、別のlocalを参照しても構いません。値を変更したあと、ワークスペースごとにapplyを1回ずつ回さないと、ファイルが更新されません。
選択を間違えたままコマンドを出す
prodを選択した状態でtofu destroy -auto-approveを回して、事故を自分で起こしてください。出力は/root/tfa-ws/incident/prod-destroy.logに、そのとき選択されていたワークスペース名は/root/tfa-ws/incident/selected.txtに(.terraform/environmentをそのままコピー)残します。そのあと、/root/tfa-ws/incident.mdにselected:、command:、lost:、recovery:の4行を書き、prodを再びapplyして復旧してください。
ワークスペースは、現在の選択を、作業ディレクトリ内のファイル1つで記憶しています。.terraform/environmentを開いてみてください。事故は「コマンドを間違って打ったこと」ではなく、「どこに出しているのか、画面のどこにも見えないこと」です。lost行には、消えたファイルのパスを、recovery行には、復活させたコマンドを書きます。
書き込みコマンドの前にガードを立てる
/root/tfa-ws/guard.shを作成してください。引数として受け取った名前と、いま選択されているワークスペースが同じなら、確認メッセージを出して0で、異なるなら、いま選択されている名前を表示して1で、引数がなければ2で終了する必要があります。ワークスペースは変更しません。
いま選択されている名前は、tofu workspace showの1行で出ます。終了コードを3つに分ける理由は、CIで「間違っている」と「呼び出し方が間違っている」を区別する必要があるためです。採点ツールが、このスクリプトを、直接3通りの方法で呼び出してみます。
同じものをディレクトリ分離で作る
/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の2つのディレクトリから、そのモジュールを、それぞれenv="dev" replicas=2、env="prod" replicas=6で呼び出してください。2つのディレクトリを、別々にinit・applyします。ワークスペースは使いません。
モジュールのファイルの内容の形式は、ステップ1とまったく同じ2行です(workspace=とreplicas=)。同じ結果を、ワークスペースなしで作ることが目的です。2つのディレクトリが、それぞれ自分の状態と自分のプロバイダーのキャッシュを持つことを、apply後に確認してください。
2つの方式を数字で比べる
/root/tfa-ws/compare.tsvに、以下の5行を、タブ区切りの2列で書いてください。値は作り上げず、いまディスク上で数えて書きます。
workspace_states = ワークスペース方式が持つ状態ファイルの数、directory_states = envs/の下の状態ファイルの数、workspace_plugin_dirs = ワークスペース方式の.terraformディレクトリの数、directory_plugin_dirs = envs/の下の.terraformディレクトリの数、selected_marker = いま選択されているワークスペースを記憶しているファイルの相対パス。
4つの数字は、findで数えます(envs/の下のものは、ワークスペース側の数に入れません)。最後の行は、ステップ5でコピーしたファイルです。この5行が、「なぜワークスペースは認証情報の境界を作れないのか」の答えです。バックエンドもキャッシュも、1つだからです。