こんにちは。 SRE の @suzuki-shunsuke です。 Terraform Monorepo の CI にtfmigrate を導入した話を紹介します。
なお、弊社の Terraform Monorepo に関しては過去の記事も参照してください。
- Terraform リポジトリをマージして CI/CD を改善した話 - スタディサプリ Product Team Blog
- Pull Request の terraform plan の実行結果を S3 に保存して安全に apply - スタディサプリ Product Team Blog
- Terraform の CI/CD を CodeBuild に移行した話 - スタディサプリ Product Team Blog
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 にコメントするようにしています。
成功
失敗
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 やスタディサプリの製品や技術にご興味がある方はぜひお気軽にご応募いただけると嬉しいです!