こんにちは。 SRE の @suzuki-shunsuke です。 Terraform の CI を AWS CodeBuild (以下 CodeBuild) から GitHub Actions + tfaction に移行した話を紹介します。
これまでの Terraform Workflow (CodeBuild)
弊プロダクトの Terraform の CI に関しては過去の記事でも何度か紹介していますが、 元々 CodeBuild 上で CI を実行していました。
かつては CircleCI 上で実行していましたが、 CodeBuild に移行しました。
CodeBuild に移行した理由は大きく 2 つありました。
- Security
- 動的な workflow
特に 1 つ目の理由は CodeBuild の大きな強みでした。
AWS だけでなく、 GCP に関しても Workload Identity Federation によって Service Account の key なしでセキュアにアクセスできます。
また、 CodeBuild であれば VPC 内で実行できるというのも強みです。 弊プロダクトでは MongoDB Atlas も Terraform で管理しています。 Atlas の API key には IP 制限をかけることができ、 IP 制限をかけていないと key が流出した際のリスクが高いので、 特定の AWS VPC の NAT Gateway の Elastic IP Address からのみアクセスを許可するようにしています。
GitHub Actions の OIDC サポート
しかし、その後 GitHub Actions が OIDC によって AWS や GCP に永続的な Access Key なしでアクセスできるようになり、状況が大きく変わりました。
VPC 内での実行に関しても GitHub Actions の Self-hosted Runner を使えば実現できます。 弊プロダクトでは元々 Self-hosted Runner を運用しているため、比較的簡単に Terraform も Self-hosted Runner で実行できると考えました。
CodeBuild の強みであった部分が GitHub Actions でも実現できるようになり、 GitHub Actions に移行する機運が高まりました。
GitHub Actions に移行した理由
CodeBuild から GitHub Actions に以下のような理由で移行することにしました(他にも色々ありますが、割愛します)。
- CI のログを見たり、リトライするために AWS にサインインする必要がなくなる
- build matrix を使うことで動的な workflow をより自然に実現できる
- Action というエコシステムを活用できる
CI のログを見たり、リトライするために AWS にサインインする必要がなくなる
AWS へのサインインは先日ブログでも紹介したとおり AWS SSO を導入したことで以前よりだいぶ楽になりました。
しかしそれでも GitHub Actions に比べると面倒です。
build matrix を使うことで動的な workflow をより自然に実現できる
CodeBuild では Terraform の CI/CD を CodeBuild に移行した話 - スタディサプリ Product Team Blog で紹介したとおり、 動的に buildspec を生成して S3 に upload し、 AWS CLI で Batch Build を起動するという若干トリッキーなことをしています。 このため、 build が 2 段階で実行され、 CI に少々時間がかかります。 そもそも Batch Build 自体が起動と終了に時間がかかるというのもあります。
GitHub Actions では build matrix を使うことで動的な workflow をより自然に実現できます。 buildspec を動的に生成したり S3 に upload する必要はありません。 CI も高速になります。
Action というエコシステムを活用できる
GitHub Actions では Action というエコシステムを活用できます。 既存のシェルスクリプトを Action で置き換えることでメンテナンス対象を減らし、メンテナンス性を改善できます。
tfaction の採用
tfaction という Terraform のための Action を採用しました。
tfaction についてはこちらの記事も参照してください。
tfaction は GitHub Actions で良い感じの Terraform Workflow を構築するための Action です。 tfaction では Workflow のサンプルもあり、それを参考にして Workflow を実装することが出来ます。
https://github.com/suzuki-shunsuke/tfaction-example
tfmigrate を使った workflow *1 や Terraform の Plan File を S3 に upload して安全に apply する仕組み *2 など、 元々シェルスクリプトで実装してたことのほぼすべてが tfaction で実現できることから、シェルスクリプトをごっそり消すことができると考えられました。 元々シェルスクリプトで表現していた部分が tfaction では YAML の設定ファイルで管理することが出来、メンテナンスもしやすいと考えました。
移行の流れ
弊プロダクトでは AWS, GCP 以外にも Datadog, Pingdom, MongoDB Atlas, Dead Man's Snitch など様々なサービスを管理しており、 400 近い working directory が存在します。 一度に全部移行するのは難しいので、特定の Provider から段階的に移行していきました。 全部 1 つ 1 つ Pull Request を作成して移行するとかなり大変ですしミスもしやすいので、 Provider ごとに幾つか working directory を移行してうまく動くことを確認したら CI を一度動かないようにして、残り全部を一気に移行してから再び CI を動くようにするという手順で進めました。 CodeBuild と GitHub Actions の並行運用が長引くと辛いですし、何より早く快適になりたかったので一週間程度で集中して終わらせました。 面倒なトラブルを避けるため、移行中は Renovate を無効化しました。
tfaction の設定に関しては tfaction のドキュメントを読んでください。
GitHub Actions + tfaction に移行してよかった点
tfaction を使って GitHub Actions に移行して、以下のような点で改善しました。
- Least Privilege の実現
- CI のログを見たり、リトライするために AWS にサインインする必要がなくなって楽
- CI の高速化
- シェルスクリプトの撲滅
- Follow up Pull Request の自動生成、 Pull Request の自動 update, GitHub Actions による scaffold など tfaction 固有の便利機能
Least Privilege の実現
元々の大きな問題として、 full 権限に近い非常に強い権限を持った IAM Role が Terraform の全 build で使われていました。 GitHub Actions の OIDC サポートでは、 branch によって Assume できる IAM Role を制限できるため、 terraform apply を実行する default branch でのみ強い権限を持った IAM Role を使い、 Pull Request ではほぼ ReadOnly に近い権限に制限した IAM Role を使うようにしました。 また、 tfmigrate を実行する workflow や、 AWS 以外の provider では Terraform の S3 Backend などの非常に限られたリソースに対してのみ権限を付与するようにしました。
tfaction では最小権限を持った IAM Role を作成するための Terraform Module がありますし、 設定ファイルによって working directory と GitHub Actions の job (terraform plan, terraform apply, tfmigrate plan, tfmigrate apply) ごとに IAM Role を設定でき、 比較的簡単に Least Privilege を実現できます。
https://github.com/suzuki-shunsuke/terraform-aws-tfaction
CI の高速化
すでに説明したとおり、複数の working directory を変更した場合の CI がかなり高速化されました。
Follow up Pull Request の自動生成, Pull Request の自動 update, GitHub Actions による scaffold など tfaction 固有の便利機能
tfaction には元々の CodeBuild による CI では実装されていなかった以下のような便利機能がありました(他にも色々ありますが、割愛)。
- Follow up Pull Request の自動生成
- Pull Request の自動 update
- GitHub Actions による scaffold
Follow up Pull Request の自動生成
terraform plan が pass したのに terraform apply に失敗するというのはよくあることだと思います。 弊プロダクトでは SRE チームが Terraform の CI の仕組みを開発者に提供するという構図になっていますが、 ユーザーである開発者が Terraform 及び CI の仕組みに詳しいわけではありません。 CI の仕組みをよく知らないと、 terraform apply が失敗したときにどうすればいいのか、 CI をリトライしていいのか迷ってしまうこともありました。
また、複数の working directory に対して terraform apply が実行され特定の working directory の apply だけが失敗した場合、 失敗した working directory の terraform apply だけリトライしたくても、 CodeBuild の Batch Build や GitHub Actions の build matrix では特定の build だけリトライすることが出来ないという制約があります。
この問題を、 tfaction では自動で Follow up Pull Request を作成しつつコメントでガイドするという手法で解決しています。
スクリーンショットを見れば分かるかと思いますが、何をすればよいのか説明が書いてあるので、ユーザーは何をすればよいのか迷わずに済むかと思います。
Pull Request の自動 update
ある working directory に関する Pull Request がマージされたら、同じ working directory に対する Pull Request の CI の結果を更新する必要があります。 そうしないと CI の結果が古いものになってしまいますし、何より S3 に upload した Plan File が stale になって terraform apply が失敗するようになってしまいます。
これまでは CodeBuild の build を自動で restart することでこの問題を解決していました。
tfaction では build を API で実行する代わりに、 Pull Request の feature branch に base branch をマージして update することで、 コードの更新しつつ CI を再実行しています。 対象の Pull Request は Pull Request Label によって判別されます。
従来のやり方だと feature branch に default branch の更新が反映されていないのでたまに予期せぬ問題が起こることがありましたが、 tfaction の方式だとそういったことは起こりません。
GitHub Actions による scaffold
弊プロダクトでは 1 つの Monorepo に 400 近い working directory が存在し、日常的に working directory が追加されています。 従来は簡単なシェルスクリプトをローカルで実行してもらって scaffold するようにしていましたが、 ローカルでのスクリプト実行では環境差異によるトラブルが起こることがありますし、ユーザから適切にヒアリングしてトラブルシューティングする必要もあったりして中々大変です。 これは Terraform に限った話ではなく、ローカルでスクリプトを実行してもらう場合全般に言えることです。
tfaction では GitHub Actions の workflow_dispatch event によって workflow を実行して scaffold するという手法が採られています。
workflow を実行すると自動で Pull Request が作成され、マージすると working directory を追加できます。
これによって環境差異による問題が起こらなくなりますし、問題が起こっても GitHub Actions の log を見れば良いので余計なコミュニケーションが不要でトラブルシューティングが楽になります。
さいごに
Terraform の CI を CodeBuild から GitHub Actions + tfaction に移行し、以下のようなことを実現することができました。
- Least Privilege によるセキュリティの改善
- tfaction によるメンテナンス性の改善・シェルスクリプトの撲滅
- tfaction による User Experience の改善