Terraform の CI に tfmigrate を導入した話

こんにちは。 SRE の @suzuki-shunsuke です。 Terraform Monorepo の CI にtfmigrate を導入した話を紹介します。

なお、弊社の Terraform Monorepo に関しては過去の記事も参照してください。

tfmigrate とは

tfmigrate とはなにかについては、 tfmigrate の作者様がこちらの記事で詳しく解説されているので、 そちらをご参照ください。 詳しい仕様などについては README に書かれているのでそちらを読むと良いでしょう。

tfmigrate をなぜ導入したのか

以下のようなモチベーションがありました。

  • SRE に依頼しなくても誰でも State 操作をできるようにする
    • コマンドを実行する代わりにコードを書くようにすることで、敷居が下がる
  • ある Pull Request (以下PR) のために State を操作することで、同じ State に関する他の PR にも影響が出ることを防ぐ
  • State 操作をコードとして管理できるようになる
    • Review ができる
    • 既存のコードを参考にマイグレーションを実行できる(完全に理解できてなくてもコピペでできたりもする)
  • 安全に State 操作ができる
    • tfmigrate は No change じゃないと失敗するので安全
    • migration がうまくいってから Remote State を更新するので、 State が壊れるようなこともない

導入方法

CI の中でどのように tfmigrate を使っているか説明します。

.tfmigrate.hcl

tfmigrate を使う場合は、対象の working directory に .tfmigrate.hcl を置くことにしています。 設定は基本的に次のような感じで、 key 以外は共通で、 key は working directory ごとに異なる感じになっています。

tfmigrate {
  migration_dir = "./tfmigrate"
  history {
    storage "s3" {
      bucket = "hello-world"
      key    = "tfmigrate/hello/production/history.json"
    }
  }
}

Migration files

Migration files は working directory の tfmigrate 配下に置き、ファイル名はタイムスタンプで始めるようにしています。 tfmigrate/20210712100000_mv_s3_bucket_hello.hcl みたいな感じです。

PR label

PR に migrate:$TARGET みたいな label をつけると、対象の working directory に対し、通常の terraform plan などの代わりに tfmigrate plan が実行されます。 $TARGET は working directory を識別する文字列で hello/production みたいな文字列になります。 ちなみに、なぜ label の prefix が tfmigrate ではなく migrate なのかというと、 label の長さには上限があるので、ちょっと短くするためです。 通常であれば plan file を S3 に upload したり plan file を Conftest で validation したりなどの処理がありますが、 tfmigrate を使う場合、これらの処理は行われません。 そもそも plan file 作られないですしね。 tfmigrate plan の結果は成功・失敗限らず PR にコメントするようにしています。

成功

image

失敗

image

PR をマージすると default branch で CI が走り、 migrate:$TARGET label がついていると terraform apply の代わりに tfmigrate apply が実行されます。 tfmigrate apply の結果は成功・失敗限らず PR にコメントするようにしています。

2 つの State をまたいだ Migration

tfmigrate は 2 つの State をまたいだ Migration をサポートしています。

https://github.com/minamijoyo/tfmigrate#migration-block-multi_state

しかし、弊社の CI では PR で変更されたファイルから plan, apply を実行する working directory を絞り込んでいるため、 tfmigrate を実行していない working directory でも CI で tfmigrate の結果が反映されない形で terraform plan, apply が実行されてしまうという問題がありました。 State A から State B に resource を Migration し、 Migration file を B の working directory に置くとしましょう。 すると A, B の両方で CI が実行されます。 B では tfmigrate が実行され、期待通りの結果になる一方、 A では tfmigrate による state 更新が反映されない状態で terraform plan や apply が実行されてしまいます。 この問題を解決するため、 ignore:$TARGET label をつけると $TARGET に対する CI を実行しないようにしました。 つまり上記の例だと ignore:A という label をつければ A の CI は skip されるので問題は解決します。 A の CI を skip しても tfmigrate によって内部的に A に対しても terraform plan が実行され、 No change であることが保証されるため、大きな問題は起こりにくいと思います。 ignore:A という label をつける際は、 A の working directory に関しては tfmigrate によるマイグレーション以外の変更は加えないようにします。

また、弊社では working directory ごとに tfenv を使って Terraform のバージョン管理をしているのですが、 上記の例で A, B の Terraform のバージョンが違うと tfmigrate を実行時に A のバージョンの Terraform がインストールされていないのでマイグレーションに失敗するという問題も発生しました。バージョンが違ってもインストールしてあれば問題ありませんでした(State の互換性がないとだめだとは思いますが)。 この問題も頑張れば解決できなくはないとは思いますが、今の所マイグレーションしたかったらバージョンを揃えようということにしています。

さいごに

以上、 Terraform Monorepo の CI に tfmigrate を導入した話を紹介しました。 tfmigrate は以前より個人的に導入したいと思っておりましたが、実際に導入してみると期待以上に体験がよく、非常にリファクタリングが捗っています。 これまでリファクタリングをしたいと思ったら、実行結果に気を配りながらローカルで terraform コマンドを実行し、 PR の description に何をやったか書いたりするのがちょっとした手間で、 それ故に手つかずになってた部分もあったのですが、 tfmigrate を導入したことで一気に進めることができています。

皆さんも是非 tfmigrate を CI に導入して快適な Terraform ライフを送りましょう。

イベントのお知らせ

Quipper ではスタディサプリの開発に関わる SRE を積極的に募集しています。

また、2021-08-25 に SRE を対象として スタディサプリ/Quipper オンラインミートアップ #3 を開催します。 Quipper やスタディサプリの製品や技術にご興味がある方はぜひお気軽にご応募いただけると嬉しいです!

20210825110543