あるところに、ひとつの Web サービスの本番環境が 20 個以上の Terraform state で管理されているリポジトリがありました。
おもに AWS 上で動いている、大変年代物かつ巨大なモノリスのサービスです。最初は何もコード管理されていない状態から徐々に Terraform を導入したのですが、その際に「一度に全部管理はできないから、徐々に Terraform 化していこう」という方針で管理を始めたところ…
- 新機能を追加するのでそのリソースを別 state で管理
- 改修があるのでそれらのリソースを別 state で管理
を繰り返すうちに、中に数個程度しかリソースがない state がどんどん増殖し、いつの間にやら20個以上になってしまったという状態です。ちなみにリソース定義は合計で 500 程度ありました。
さすがにここまで分割されてしまうと相互参照はできないし、VPC / Subnet / Security Group などの共通で使っているリソースはいちいち data resource として定義するのも面倒だし、何度も同じことを書かないといけないし、と利点がなさ過ぎたため、統合を考えました。
tfstate-merge
最近 tfstate-lookup というコマンド/ライブラリを書いて、JSON 形式の tfstate (version 4) にはちょっと仲良くなっていたので、こんな感じで結合したらいけるんじゃないかなと雑に書いてみたのが tfstate-merge コマンドです。
これは 2つの state ファイルを引数に取り、結合するコマンドです。Ruby 製(コアモジュール縛り)なので、手元に ruby があれば実行できると思います。
$ tfstate-merge aaa.tfstate bbb.tfstate
とすると、bbb.tfstate の中の resource をすべて aaa.tfstate にマージした物を標準出力に出力します。
何も考えずに結合してしまうと、同一の名前で実体は別のリソースがあった場合に壊してしまうため、簡単なチェック機能を持っています。
- 通常の resource (state では mode=managed) については、同じ名前があったらエラー
- data resource (mode=data) については、同じ名前の場合は attributes が同一でなければエラー
- terraform version が揃っていなければエラー (念のため)
- state version 4 (terraform v0.12) のみ対応
エラーがなければ、最後に aaa.tfstate の serial 属性を ++ して出力します。
tfstate-merge による統合作業
実際にどのような作業を行うかの例を説明します。
tfstate-merge は remote state には対応していないため、一旦結合したい state file をローカルに持ってきた上で作業します。
作業は結合先の .tf が存在している場所で行います。
$ tfstate-merge -i.bak terraform.tfstate xxx.tfstate resource data.aws_vpc.main is duplicated
2つの tfstate を結合します。オプションで -i.bak
とすると、作業前の terraform.tfstate を terraform.tfstate.bak として保存し、結合後の内容を terraform.tfstate に上書きします。
data resource が重複している場合は警告が標準エラー出力に出力されるので、その後の調整の参考にしてください。運が悪いことに managed resource name が重複している場合は、一度 terraform state mv
でリソース名を変更してから再挑戦するのがよいでしょう。
xxx.tfstate で管理している .tf をコピーしてきて、その中の terraform, provider の定義、重複している data resource を削除します。
そして terraform plan
を行うと、No changes. Infrastructure is up-to-date.
になるはずです。(ならなければ頑張って調整しましょう…)
最後に、追加した .tf をリポジトリにコミットし、tfstate を remote へ書き戻したら作業完了です。
まとめ
- 複数の Terraform state file を結合する tfstate-merge を書きました
- 過度に分割しすぎてしまった state をまとめたい場合にご利用ください