スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Terraform の CI を AWS CodeBuild から GitHub Actions + tfaction に移行しました

こんにちは。 SRE の @suzuki-shunsuke です。 Terraform の CI を AWS CodeBuild (以下 CodeBuild) から GitHub Actions + tfaction に移行した話を紹介します。

これまでの Terraform Workflow (CodeBuild)

弊プロダクトの Terraform の CI に関しては過去の記事でも何度か紹介していますが、 元々 CodeBuild 上で CI を実行していました。

かつては CircleCI 上で実行していましたが、 CodeBuild に移行しました。

blog.studysapuri.jp

CodeBuild に移行した理由は大きく 2 つありました。

  • Security
    • 永続的な Access Key を発行することなく AWS のリソースを管理できる
    • GCP に関しても Workload Identity Federation によって Service Account Key なしで管理できる
  • 動的な workflow
    • Monorepo において、 Pull Request でコードが変更された working directory でだけ CI を実行したい
    • build 中に動的に buildspec を生成し、 AWS CLI で Batch Build を実行することで実現
    • 現在は CircleCI も動的な workflow をサポートしているが、当時はサポートしていなかった

特に 1 つ目の理由は CodeBuild の大きな強みでした。

AWS だけでなく、 GCP に関しても Workload Identity Federation によって Service Account の key なしでセキュアにアクセスできます。

blog.studysapuri.jp

また、 CodeBuild であれば VPC 内で実行できるというのも強みです。 弊プロダクトでは MongoDB Atlas も Terraform で管理しています。 Atlas の API key には IP 制限をかけることができ、 IP 制限をかけていないと key が流出した際のリスクが高いので、 特定の AWS VPC の NAT Gateway の Elastic IP Address からのみアクセスを許可するようにしています。

GitHub Actions の OIDC サポート

しかし、その後 GitHub Actions が OIDC によって AWSGCP に永続的な Access Key なしでアクセスできるようになり、状況が大きく変わりました。

docs.github.com

VPC 内での実行に関しても GitHub Actions の Self-hosted Runner を使えば実現できます。 弊プロダクトでは元々 Self-hosted Runner を運用しているため、比較的簡単に Terraform も Self-hosted Runner で実行できると考えました。

blog.studysapuri.jp

CodeBuild の強みであった部分が GitHub Actions でも実現できるようになり、 GitHub Actions に移行する機運が高まりました。

GitHub Actions に移行した理由

CodeBuild から GitHub Actions に以下のような理由で移行することにしました(他にも色々ありますが、割愛します)。

  • CI のログを見たり、リトライするために AWS にサインインする必要がなくなる
  • build matrix を使うことで動的な workflow をより自然に実現できる
  • Action というエコシステムを活用できる

CI のログを見たり、リトライするために AWS にサインインする必要がなくなる

AWS へのサインインは先日ブログでも紹介したとおり AWS SSO を導入したことで以前よりだいぶ楽になりました。

blog.studysapuri.jp

しかしそれでも GitHub Actions に比べると面倒です。

build matrix を使うことで動的な workflow をより自然に実現できる

CodeBuild では Terraform の CI/CD を CodeBuild に移行した話 - スタディサプリ Product Team Blog で紹介したとおり、 動的に buildspec を生成して S3 に upload し、 AWS CLIBatch Build を起動するという若干トリッキーなことをしています。 このため、 build が 2 段階で実行され、 CI に少々時間がかかります。 そもそも Batch Build 自体が起動と終了に時間がかかるというのもあります。

GitHub Actions では build matrix を使うことで動的な workflow をより自然に実現できます。 buildspec を動的に生成したり S3 に upload する必要はありません。 CI も高速になります。

Action というエコシステムを活用できる

GitHub Actions では Action というエコシステムを活用できます。 既存のシェルスクリプトを Action で置き換えることでメンテナンス対象を減らし、メンテナンス性を改善できます。

tfaction の採用

tfaction という Terraform のための Action を採用しました。

github.com

tfaction についてはこちらの記事も参照してください。

zenn.dev

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 を作成しつつコメントでガイドするという手法で解決しています。

image

image

スクリーンショットを見れば分かるかと思いますが、何をすればよいのか説明が書いてあるので、ユーザーは何をすればよいのか迷わずに済むかと思います。

Pull Request の自動 update

ある working directory に関する Pull Request がマージされたら、同じ working directory に対する Pull Request の CI の結果を更新する必要があります。 そうしないと CI の結果が古いものになってしまいますし、何より S3 に upload した Plan File が stale になって terraform apply が失敗するようになってしまいます。

これまでは CodeBuild の build を自動で restart することでこの問題を解決していました。

blog.studysapuri.jp

tfaction では build を API で実行する代わりに、 Pull Request の feature branch に base branch をマージして update することで、 コードの更新しつつ CI を再実行しています。 対象の Pull Request は Pull Request Label によって判別されます。

image

従来のやり方だと 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 するという手法が採られています。

image

workflow を実行すると自動で Pull Request が作成され、マージすると working directory を追加できます。

image

これによって環境差異による問題が起こらなくなりますし、問題が起こっても GitHub Actions の log を見れば良いので余計なコミュニケーションが不要でトラブルシューティングが楽になります。

さいごに

Terraform の CI を CodeBuild から GitHub Actions + tfaction に移行し、以下のようなことを実現することができました。

  • Least Privilege によるセキュリティの改善
  • tfaction によるメンテナンス性の改善・シェルスクリプトの撲滅
  • tfaction による User Experience の改善