countからfor_eachへ、破壊せずに移す
目標
countとfor_eachの違いを状態アドレスで確認し、すでに適用されたリソースを、破棄なしでfor_eachに移行させる手順を身につけます。
なぜ重要なのか
countとfor_eachは、文法の選択のように見えますが、実際には、各インスタンスの識別子を何にするかを決める判断です。countは順序を、for_eachは名前を、識別子として使います。リストの真ん中の項目を1つ削除してみると、違いが現れます。countは、後ろが1つずつ繰り上がって、残っているべきリソースまで再作成され、for_eachは、該当のキーだけがなくなります。実際のクラウドだったなら、この違いは、「設定を1行消しただけなのに、サーバー2台が再作成された」として現れます。すでに適用されたリソースのアドレスを変える必要があるときは、movedブロックを使ってください。movedは、状態だけを移して実体には触れず、コードにリファクタリングの履歴が残るため、ほかの人がplanを回すだけでも自動的に追従します。移行が正しくできたかを判定する基準は、1つです。プランに破棄が1件もないことです。
ステップ
/root/tf/foreach/main.tfにlocal_fileリソースnodeをcount = 3で宣言し、tofu initのあとに適用してください。そのあと、アドレスを移す前に、tofu state listの結果を/root/tf/foreach/out/count-state.txtに保存してください。このファイルには、local_file.node[0]、local_file.node[1]、local_file.node[2]の3行が入っている必要があり(文字列キーの形が入っていてはいけません)、ステップ7の移行前のスナップショットの役割を果たします。nodeのファイル名を/root/tf/foreach/out/node-${count.index}.txt、内容をnode-${count.index}にしてください。名前を3回手で書かず、必ず繰り返しインデックスで作る必要があります。outの下のnode-*.txtは、ちょうど3つで、各ファイルの最初の行は、node-0、node-1、node-2である必要があります。local_fileリソースzoneをfor_each = toset(["kr", "us", "eu"])で作成し、ファイルは/root/tf/foreach/out/zone-<키>.txtが3つできるようにしてください(プレースホルダーはキーです)。状態のキーは、eu、kr、usである必要があります。local_fileリソースserviceを、マップで回してください。キーはapi、web、workerで、各値にはポートが入ります(webのポートは必ず8080)。/root/tf/foreach/out/service-web.txtの中には、name=webとport=8080が、それぞれ1行で入っている必要があります。for式で、サービス名をキー、ポートを値とする出力service_portsを定義し、tofu output -jsonの結果を/root/tf/foreach/out/outputs.jsonに保存してください。項目は3つで、webは8080である必要があります。variable "enable_debug"(bool、デフォルト値はfalse)を作成し、local_fileリソースdebugにcount = var.enable_debug ? 1 : 0を使ってください。ファイルのパスは/root/tf/foreach/out/debug.txtで、内容は自由です(スイッチはインスタンスが最大1つなのでインデックスがずれることがない、という点が、countを使い続けてよい理由です)。スイッチをオンにしたプラン(tofu plan -var enable_debug=true)の出力を/root/tf/foreach/out/toggle.txtに保存してください(1 to addが表示されている必要があります)。ただし、適用はしないでください。最終状態には、debugインスタンスが0個で、/root/tf/foreach/out/debug.txtもない必要があります。nodeをfor_each = { n0 = 0, n1 = 1, n2 = 2 }に変更してください。マップのキーは新しいアドレス用で、値は古いインデックスなので、ファイル名と内容は、each.keyではなく、each.valueで作って、node-0.txt・node-1.txt・node-2.txtとその内容が、ステップ1–2と完全に同じになるよう維持する必要があります(名前を変えると、移行ではなく再作成になり、プランに破棄が表示されます)。そして、movedブロック3つで、local_file.node[0]をlocal_file.node["n0"]に移す形で、古いアドレスと新しいアドレスをつなげてください。適用したあと、tofu planの出力を/root/tf/foreach/out/after-plan.txtに保存すると、No changesで、破棄が1件もない必要があります。/root/tf/foreach/out/addresses.jsonを作成してください。addressesキーに、tofu state listのアドレスを配列として入れ(node 3 + zone 3 + service 3 = 9個以上)、数値のインデックスが付いたアドレスは1つもあってはいけません。numeric_index_countキーには、数値インデックスのアドレスの個数を書き、移行が終わっていれば0です。
参考
- このラボの状態ファイルは
/root/tf/foreach/terraform.tfstateで、countで作成した成果物は、/root/tf/foreach/out/node-0.txt、/root/tf/foreach/out/node-1.txt、/root/tf/foreach/out/node-2.txtです。移行のあとも、このファイル名はそのままである必要があります。 tofu state listは、アドレスを1行に1つずつ出力します。jq -R -sで丸ごと読み込んで配列に畳めば、ステップ8が楽です。- ステップ1の
count-state.txtと、ステップ8のaddresses.jsonは、同じコマンドの移行前・移行後のスナップショットです。実際の移行作業でも、移す前のアドレスの一覧と状態のコピーを先に残すのが、最初のステップです。記録がなければ、「漏れたリソースはない」ことを証明する方法がありません。 movedブロックは、一度適用したあとも、コードに残しておくのが慣例です。ほかの人の状態でも、同じ移行が起きる必要があるためです。- ステップ6の
toggle.txtは、ほかの変更がすべて適用されたあとで出力しないと、プランにスイッチ1つだけが表示されません。 - よくある間違い1: ステップ7で、ファイル名を
node-${each.key}.txtに変えてしまうことです。名前が変わると、移行ではなく再作成になり、プランに破棄が表示されます。 - よくある間違い2: リストをそのまま
for_eachに渡すことです。toset()でセットに変換する必要があります。 - よくある間違い3: 移行を終えたあとで、
count-state.txtを出力することです。そのファイルは、移す前の記録であってこそ意味があります。
countで3つ作り、移す前のアドレスを残す
/root/tf/foreach/main.tfにlocal_fileリソースnodeをcount = 3で宣言し、tofu initのあとに適用してください。そのあと、アドレスを移す前に、tofu state listの結果を/root/tf/foreach/out/count-state.txtに保存してください。このファイルには、local_file.node[0]、local_file.node[1]、local_file.node[2]の3行が入っている必要があり(文字列キーの形が入っていてはいけません)、ステップ7の移行前のスナップショットの役割を果たします。
リソースブロックにcount = 3を入れると、インスタンスが3つできます。あとでアドレスを移すので、いまのアドレスの一覧をファイルに残しておいてください。移す前の記録がないと、何がどのように移ったかを証明できません。
count.indexでインスタンスを区別する
nodeのファイル名を/root/tf/foreach/out/node-${count.index}.txt、内容をnode-${count.index}にしてください。名前を3回手で書かず、必ず繰り返しインデックスで作る必要があります。outの下のnode-*.txtは、ちょうど3つで、各ファイルの最初の行は、node-0、node-1、node-2である必要があります。
インスタンスごとにファイル名が違う必要があるため、インデックスを名前に埋め込みます。内容も、同じ方法で区別してください。
セットを回すfor_eachを使う
local_fileリソースzoneをfor_each = toset(["kr", "us", "eu"])で作成し、ファイルは/root/tf/foreach/out/zone-<키>.txtが3つできるようにしてください(プレースホルダーはキーです)。状態のキーは、eu、kr、usである必要があります。
リストはfor_eachに直接渡せません。セットに変換する必要があり、そのとき、キーは要素の値そのものになります。
マップを回すfor_eachとeach.value
local_fileリソースserviceを、マップで回してください。キーはapi、web、workerで、各値にはポートが入ります(webのポートは必ず8080)。/root/tf/foreach/out/service-web.txtの中には、name=webとport=8080が、それぞれ1行で入っている必要があります。
マップを渡すと、each.keyはマップのキー、each.valueはその値です。値がオブジェクトなら、フィールドに掘り下げられます。
for式で出力マップを作る
for式で、サービス名をキー、ポートを値とする出力service_portsを定義し、tofu output -jsonの結果を/root/tf/foreach/out/outputs.jsonに保存してください。項目は3つで、webは8080である必要があります。
for_eachはリソースを増やすメタ引数で、forは値を別の形に変換する式です。{ for k, v in ... : k => ... }の形を使ってください。
条件付きcountで機能スイッチを作る
variable "enable_debug"(bool、デフォルト値はfalse)を作成し、local_fileリソースdebugにcount = var.enable_debug ? 1 : 0を使ってください。ファイルのパスは/root/tf/foreach/out/debug.txtで、内容は自由です(スイッチはインスタンスが最大1つなのでインデックスがずれることがない、という点が、countを使い続けてよい理由です)。スイッチをオンにしたプラン(tofu plan -var enable_debug=true)の出力を/root/tf/foreach/out/toggle.txtに保存してください(1 to addが表示されている必要があります)。ただし、適用はしないでください。最終状態には、debugインスタンスが0個で、/root/tf/foreach/out/debug.txtもない必要があります。
있으면 1, 없으면 0(韓国語の文は「あれば1、なければ0」という意味です)は、オン・オフの慣用句です。オンの状態は、プランでだけ確認し、実際には適用しないでください。
movedブロックでcountをfor_eachに移行する
nodeをfor_each = { n0 = 0, n1 = 1, n2 = 2 }に変更してください。マップのキーは新しいアドレス用で、値は古いインデックスなので、ファイル名と内容は、each.keyではなく、each.valueで作って、node-0.txt・node-1.txt・node-2.txtとその内容が、ステップ1–2と完全に同じになるよう維持する必要があります(名前を変えると、移行ではなく再作成になり、プランに破棄が表示されます)。そして、movedブロック3つで、local_file.node[0]をlocal_file.node["n0"]に移す形で、古いアドレスと新しいアドレスをつなげてください。適用したあと、tofu planの出力を/root/tf/foreach/out/after-plan.txtに保存すると、No changesで、破棄が1件もない必要があります。
アドレスが変わると、ツールは破棄と作成として解釈します。movedで古いアドレスと新しいアドレスをつなぎ、ファイル名・内容はそのまま維持すると、再作成が起きません。
全アドレスの一覧と移行完了レポートを作る
/root/tf/foreach/out/addresses.jsonを作成してください。addressesキーに、tofu state listのアドレスを配列として入れ(node 3 + zone 3 + service 3 = 9個以上)、数値のインデックスが付いたアドレスは1つもあってはいけません。numeric_index_countキーには、数値インデックスのアドレスの個数を書き、移行が終わっていれば0です。
状態の一覧を、JSON配列に変換して入れます。数値インデックスのアドレスが1つも残っていないかを、自分で数えてみてください。