Terraformのcreate/update/destroy適用順序を理解して複雑なIaCに備える
はじめに
Terraform で IaC を進めると、複数のリソースが互いに依存する状況が生まれる。また運用期間が長くなるにつれ、リソースの create けでなく複数リソースの update や destroy も必要なケースが出てくる。
ここでありがちなのが「あるリソース A を削除する前にリソース B の内容を更新したい」といったケースだ。B が持っている配列に A の ID 等が含まれていて、先に配列から ID を消さないと destroy 時に API がエラーを返すというのがよくあるパターン。
これを解決するには apply の適用順序を理解しコントロールする必要があるが、あまり直感的でなく Web 上の情報も乏しいように感じたのでここにまとめる。
基本のおさらい
まずはシンプルな構成例でリソース間依存と適用順序の関係を整理する。
create や destroy の適用順序
これは依存関係から順序が決まるのでとても分かりやすい。以下の登場人物(resource)を例にする。
会社の中のチームを表す `team` resource
社員とその情報(社員番号、所属先チーム等)を表す `team_member` resource
リソースの定義は以下。
resource team "dev" { team_name = "dev" } # devチームに所属 resource team_membership "john_doe" { employee_id = 1234 team_id = team.dev.id }
依存関係を表すグラフと適用順序を図示すると以下のようになる。
適用のルールは極めてシンプルで、
resource の create は依存グラフのエッジから順に適用される
destroy は create の逆順
となる。
depends_on は基本的に書かなくていい
上記のサンプルコードのようにリソース内で他のリソースを参照している場合 Terraform は暗黙的に依存を認識し `depends_on` を書いているのと同じ作用が働く。そのため基本的に resource 定義で `depends_on` 明示的に書かないといけないケースは少ない。
Most resource dependencies are handled automatically. Terraform analyses any expressions within a resource block to find references to other objects, and treats those references as implicit ordering requirements when creating, updating, or destroying resources.
Resource Behavior - Configuration Language | Terraform | HashiCorp Developer
たとえば destroy の適用順序でエラーが起きた場合、もし闇雲に `depends_on` を書いてしまっている箇所があれば一度依存グラフを図に起こして精査すれば分かりやすいはず。
多くはないはずだが以下のような例だと `depends_on` が効果的になる。
前述のチームと所属メンバーを表す resource 群に、チームメンバーの稼働表 `work_schedule` があるとする
`work_schedule` にはメンバーの社員番号が含まれる
API の仕様上、稼働表に存在するメンバーをチームからは外せない (= team_membership を destroy できない)
resource work_schedule "schedule" { team_id = team.team_foo.id # team_membership との参照関係は無い employee_members = [1234, 12345, ...] }
もしチームが解散となった場合この仕様を満たすには `work_schedule` -> `team_membership` -> `team` の順で destroy しなければいけないが、 work_schedule と team_membership の間には暗黙的な依存関係がない。なので depends_on で以下のように依存グラフを組み替えて destroy の順序を変える必要がある。
destroy と create・update の順序を制御するには create_before_destroy
ここまでは resource 間依存と apply 順の関係性を整理するために create・destroy どちらかだけが走る例を挙げた。では冒頭に書いたような update や destroy が両方走る apply ではどう考えればいいか。
チーム・所属メンバー・勤怠表 3 つのリソースを例に、チームメンバーが 1 人抜ける場合を考えると次の順序で apply する必要がある。
`work_schedule` から抜けるメンバーの参照を消して update する
該当メンバーの `team_membership` を destroy する
しかし Terraform では仕様上 destroy -> create -> update の順番で apply が適用されるという挙動になっている (参考: HashiCorp Discuss)。 `depends_on` はあくまで依存関係の制御、つまり create・destroy それぞれの順序制御とも言えるので、update と destroy 間の順序には関係ない。このような時には `create_before_destroy` lifecycle の出番になる。
Currently, the only user-controllable method for changing the ordering of create and destroy operations is with the create_before_destroy resource lifecycle attribute.
https://github.com/hashicorp/terraform/blob/main/docs/destroying.md#create-before-destroy
依存グラフと `create_before_destroy` 無し・有りでの apply 順序は以下になる。名前からは create の為のものに思えるが update も同様で、destroy より前に適用させたい resource に lifecycle を指定すればいい。
作成済 resource の lifecycle 変更は state refresh が必要
やや余談だが、lifecycle に指定した値は resource を最初に作成した時だけ state へ書き込まれる。つまり作成済みの resource で適用順序起因の問題を見つけて create_before_destroy を追記し apply しても効果がない。
lifecycle も含めた既存 resource の変更を反映したい場合は `terraform apply -refresh-only` を実行する。このオプションを指定すると apply 時の差分がなくとも state を最新の状態にさせられるので、適用順序に起因してエラーが起きる修正を入れる前にこれを実行しておくといい。
まとめ
管理するリソースが増えた際に重要となる依存関係や適用順序のルールをこの記事で整理した。
この適用順序のルールについては一部 HashiCorp のドキュメントサイトに載ってないものがあったので、参考にした情報源も最後に貼っておく。 下記 1 つ目の GitHub repo 上の doc がまさにそれで、create_before_destroy についても詳しく書かれている。