こんにちは、SREの侘美です。
今回はGitHub Actions上でTerraformを実行する環境を構築したので紹介させていただきます。 もともと利用していた Terraform Cloud から使い勝手や機能を悪化させることなく移行を目指し、様々な工夫を行ったのでそれらも合わせて紹介いたします。

移行の理由
弊社では2020年頃からTerraformの統一的な実行環境としてTerraform Cloudを採用していました。 Terraform CloudはTerraformのplan/applyの実行、チームとアクセス権の管理、モジュールレジストリ機能などTerraformに必要な様々な機能を備えているTerraform開発元であるHashiCorp製のSaaSです。
特に機能面で大きな不満なく利用できていたのですが、2023年5月にTerraform Cloudの料金体系の変更が発表されました。
端的に言うとユーザー数での課金から管理するリソース数での課金に変わるという内容でした。 この発表を受け直近の管理リソース数からコストを試算したところ、新料金プランでは従来の約12倍のコストとなることが判明しました。
ここまでの値上がり幅となってしまったのは、少人数のSREで効率化しながら多数のAWSアカウントを管理できるよう努力してきた成果の表れとも言えます。
弊社では契約タイミングの都合で新料金体系に切り替わるのは2025年10月と比較的猶予がある状況でした。そこで、これを機に別の実行環境への移行を検討し2025年9月に晴れてGitHub Actionsへの移行を完了させました。
前提
先に以下の説明に関連する範囲で弊社のTerraformの運用フローやリポジトリ構成を説明しておきます。
- リポジトリはサービス単位(約50リポジトリ)
- リポジトリ内に複数のTerraform Workspace(stg, prd, datadog, etc...)
- PR上で
terraform planを実行 - デフォルトブランチにマージ後、
terraform applyを実行
構成
まずは移行前のTerraform Cloudを採用した構成と、移行後である現在のGitHub Actionsを採用した構成をご紹介します。
Before

特に特殊な使い方をしているわけではなく、一般的なTerraform Cloudの利用方法をしていました。
リポジトリ内で envs/stg envs/prd のようにディレクトリを分けて、それぞれのディレクトリをTerraform Cloudのworkspaceに対応させる形で利用していました。
After

移行後もTerraform Cloudのworkspaceがそのまま同リポジトリ内のActionsのジョブに置き換わった形になります。 なお、Terraform Cloudはモジュールのレジストリ機能のみ今後も継続して利用するため残しています。
工夫点
ここからは移行に際して、使い勝手を落とさないため/既存の課題を解決するために行った工夫点をいくつか紹介させていただきます。
PR上にplan結果のコメント
plan結果をPull Requestのレビュー時に確認するのは必須となっています。
Before

Terraform Cloudの場合、外部のCIなのでPR上のChecksに結果へのリンクが掲載される仕組みでした。 リンクを開いて(未ログインならTerraform Cloudへログインして)plan結果を閲覧するという流れです。
After

Actionsで実行したplan結果は、何も追加の実装を行わなければActionsのログで閲覧するしかありません。 そこで、 suzuki-shunsuke/tfcmt を使いActionsで実行したplan結果をPR上にコメントとして記載するようにしました。
「リンクを開かずともサッとplan差分を見られるようになった」「Terraform Cloudへのログインが不要になった」と開発者からの評判は上々です。
※ PRのコメントなので極端に長いplan差分は一部省略されてしまいますが、レアケースですしActionsのログを確認すれば閲覧可能なのでOKとしました。
applyのレビュー待ちと通知
Terraform Cloudにはapply前に最終plan結果を確認し、そのplan結果でapplyを行ってよいかの確認をはさむConfirmという機能があります。

PR上のplanが実行された後、別PRまたは手動でAWS上でリソースが変更される可能性はゼロではありません。こういった意図しない変更が入った状態でPRマージ後にapplyを行うと、PR上で確認したplan結果とは異なる差分が発生する可能性があります。
本番環境など、万が一リソースを誤って変更・削除してしまうと一発で障害につながるような環境では、apply直前のplan差分のチェックが必須です。
Actions移行後もこのConfirm機能相当のフローを継続することにしました。 GitHubのEnvironments機能を利用して同等の仕組みを実現可能できそうな目処がたったので、これを採用しました。
- リポジトリ内のTerraform Workspaceに対応するEnvironmentsを作成する
- 例: stg, prd, datadog
- apply前のplan結果確認が必要なEnvironmentsに対してレビュアーを指定する
- 例: stg, datadog → レビュアーなし / prd → 開発チーム + SREチームをレビュアーに指定
- マージ後に実行されるplan → applyを行うActionsのジョブにおいて、applyジョブに
environmentを指定する
jobs: plan: # 省略 apply: needs: plan environment: name: prd # 省略
上記の設定を行うことで、レビュアーが指定されたEnvironmentのみマージ後のplanジョブ完了後に「DeployのApprove待ち」状態となります。


参考:
- https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments
- https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/review-deployments
一方でTerraform Cloudの時代から、「開発者が忘れてapply前の確認待ち状態で止まっている」というケースが発生する問題がありました。 Terraform CloudからのSlack通知はあるのですが、メンションがないためその他の通知と共に埋もれてしまう点を改善することにしました。

そこでマージ後にapplyのApproveが必要なジョブに関しては、planジョブの最後でSlackにメンション付きで通知を送るようにActionsを実装しました。 通知対象はマージしたユーザーとなるように設定しています。

Actionの共通化
弊社ではもともとメタデータ構文機能を使い、Actionsの実装の共通部分を別リポジトリ( actions リポジトリ )に切り出しています。
このリポジトリに新たに terraform-apply terraform-plan terraform-notification などを追加し、各サービスのTerraformリポジトリ上のActionsではこのリポジトリをチェックアウトして uses で指定して利用する形としました。
https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax
さらにactionsリポジトリ側でバージョン番号をtagで管理することで、利用側でclone時にバージョン指定して利用できるようにもしています。 このバージョン固定の仕組みを導入することで、破壊的変更を伴うアップデートもより安全に行うことができるようになりました。(常に最新のコードを参照する仕組みだと、 actionsリポジトリ側で破壊的変更を加えた際に、即座に全利用リポジトリ側を修正する必要がありました。)
- name: Checkout Actions uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: 'org_name/actions' ssh-key: ${{ secrets.ACTIONS_DEPLOY_KEY }} path: 'mpg-actions' ref: v2.0.0
秘匿情報の管理
秘匿情報の管理に関しても、Terraform Cloud時代にあった課題を解決することができました。
Before
Terraform Cloud上のterraformで扱う秘匿情報のうち、複数のworkspaceに同じ秘匿情報を設定したいケースが存在していました。 主にAWSや各種SaaSの認証情報など、TerraformのProviderを実行するのに必要な権限がここに該当します。
これらは事前に手動でTerraform Cloud上のOrganizationのVariable Setsという形で登録し、利用するWorkspaceから参照可能に紐づけるという形で管理していました。この管理方法を採用することで、Workspaceが増えるたびに手動で設定しなければならない事態は回避できていました。

一方で、この構成では手動で秘匿情報を設定する以上「誰がいつなんの値を登録したのか後から確認が困難」「再登録などのために別途秘匿情報を管理する必要がある」といった課題が残っていました。
After
弊社では、GitHub上の一部のリソース(ユーザー、チーム、Branch Protection、etc...)をTerraformで管理しています。今回の移行時にはTerraform関連のリポジトリの設定もいくつかTerraformで管理するようにしました。
GitHubのOrganizationまたはRepositoryの秘匿情報をAPIで扱うために、暗号化の仕組みが提供されています。 Organization/Repositoryから暗号化のための公開鍵を取得し、手元で秘匿情報を暗号化し、その暗号化された値をAPIで送ることでGitHub内部で復号されつつ秘匿情報が登録される仕組みです。GitHubのTerraform Providerからもこの仕組みは利用可能です。 暗号化用のスクリプトを用意し、これを利用してリポジトリに登録するための暗号化をスムーズに行えるように構築しました。
全体の流れは下記の図のようになります。

この仕組みを構築したことで秘匿情報を暗号化してコード管理できるようになり、設定経緯・管理・属人化といった手動設定にまつわるもろもろの課題を解決できました!
参考:
- https://docs.github.com/en/rest/actions/secrets#create-or-update-a-repository-secret
- https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret
workspace一覧
Terraform Cloudで地味に重宝していたのがWorkspace一覧画面でのapplyエラーの確認です。

(移行済みなので若干古いUIの時の画像しか用意できませんでした、ご了承ください。)
Confirmが必要なprdなどはきちんとapplyが正常に終了するまでチェックされることが多いですが、Confirm不要でマージしたらplan→applyと自動で流れるworkspaceに関しては、たまにエラーが放置されているケースがあります。
もちろんapply状況のSlack通知を行っているためそちらで気づいて対応するケースが大半ですが、それでも開発者によって、または通知先チャンネルによっては気づかずにエラーが放置されてしまうケースは発生してしまいます。
そこで全workspaceにアクセス権のあるSREメンバーがこのworkspace一覧を見ることで、エラーになったままのworkspaceに気づく、という使い方ができ重宝していました。
さて、Actionsへ移行した場合このworkspace一覧に相当する画面を用意することは困難です。 各apply Actionはそれぞれのリポジトリ内で動作します。GitHub自体にOrganization内のActionsを横断的に表示する機能があれば良かったのですが、現時点ではそのような機能は存在しません。
そこでStatus BadgeをWikiに掲載する方法を採用しました。 幸い、弊社ではOrganization配下のTerraform系のリポジトリのコードをスキャンし、自前で構築した静的解析を行い結果をリポジトリのWikiに掲載するシステムがすでに稼働していました。そこで、この静的解析の機能を拡張し、各Terraform系リポジトリのworkflowファイルを取得→workflowファイル名からStatus BadgeのURLを組み立て→それをWikiに反映という形で実装しました。 この静的解析は定期的に実行されるので、リポジトリやworkspaceが増減しても自動でStatus Badge一覧が更新されます。 Status Badgeはその仕組み上、閲覧するタイミングで最新のジョブの終了状態が反映されるので、これでTerraform Cloud時代のworkspace一覧に相当する一覧ページを再現することができました。

※ Status Badgeのリンク先は各リポジトリのActionsページにしてあるので、エラーの確認がスムーズなのも地味に便利ポイントです
移行手順
移行は概ね下記の手順で行いました。 手順をドキュメントにまとめてSREメンバーで手分けして全リポジトリをActionsに移行しました。
- plan差分が無い状態にする
terraform state pullでStateをローカルに取得- Terraform CloudからGitHub Actionsへの実装に変更
- バックエンドをTerraform CloudからS3へ変更
- plan/apply等のActionsを追加
terraform state pushで新しいバックエンドのS3にStateをpush- PRを出す(Terraform CloudとActions両方でplanが実行される)
- マージ後、Terraform Cloudを管理しているリポジトリで移行完了したリポジトリのworkspaceを削除するPRを出す
- SlackのGitHub Appで通知を設定
- 必要に応じてRemote Stateで参照している側の実装を修正
特にミスが発生することもなくスムーズに全リポジトリで移行できました。
課題
移行に際して既存の課題はあらかた解決できたのですが、一部で新しい課題も出てきたので紹介させていただきます。
ローカルからのplan
Terraform Cloudを実行環境に利用している場合、PRやデフォルトブランチにコードをpushした際のplan/applyの流れは下記の図のようになります。

一方で、ローカルで terraform plan を実行すると、ローカルのコードをTerraform Cloudに転送してplanを実行し、その結果を表示することができます。

これはTerraform CloudがTerraform開発元であるHashiCorp社製のサービスであるから実現できる仕組みとなっています。terraformコマンド自体に、バックエンドがTerraform Cloudだった場合にコードを転送してplanを実行するような仕組みが組み込まれているためです。
この仕組みにより、「コードをpushすることなく迅速に」「開発者間で統一された実行環境で」 terraform plan の実行が実現されています。
Actions移行後はさすがにこれと同様の仕組みを提供することはできませんでした。
AWSを管理するworkspaceの場合、ローカルで terraform plan の実行に必要な権限をもったIAM Roleを利用可能な開発者はローカルから実行することは可能です。
しかし、この権限をもったエンジニアは一部ですし、弊社の権限設計的にもあまり多くの開発者にこの権限を付与するのは望ましくありません。
また、AWSのように個々に権限を管理する仕組みが整備されている場合は実現の目処はありますが、GitHubやSendGridやDataDogなどの各種SaaSで同様に個々の開発者がローカルから terraform plan を実行するためのAPIキーなどを発行するのはリスクが伴います。
現状は高速にplan結果を確認したいケースはそこまで頻繁に発生しないことから、いったんはplanはコードをpushして必ずActionsで実行するような運用としています。
deployのapprove時にplanを確認する手間
「applyのレビュー待ちと通知」の項にて、メンション付きでマージしたユーザーに通知を送る仕組みを紹介しました。 通知自体は問題ないのですが、このDeployのApprove画面からplan結果を確認するためにActionsのログを開くのに数回のページ遷移とスクロールを行わなければならず、若干の手間となっています。





PR上にコメントでplan結果を表示したように、Actionsのworkflow画面にplan結果を表示することができれば手間をもっと減らせそうですが、現状はそういった機能は提供されていません。
今後の取り組み: RenovateによるAuto Merge
今まではDependabotを利用していましたが、Renovateを導入して以下を実現したいと検証中です。
- 同一リポジトリ内のworkspace間でのPRの統一
- plan差分の無いPRの自動マージ
上記を実現することで、さらなるリポジトリ管理の工数削減を目指します。
まとめ
弊社ではTerraform Cloudの新料金体系への移行タイミングを契機に、より安価でカスタマイズ性の高いGitHub ActionsへTerraformの実行環境を移しました。 Terraform Cloud相当の使い勝手を維持しつつ、既存の課題をいくつか解決するなどしてかなり使い勝手の良い実行環境を整備することができました。 今後Terraformを自動実行する環境を構築する方の参考になれば幸いです。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら medpeer.co.jp
■エンジニア紹介ページはこちら engineer.medpeer.co.jp
■メドピア公式YouTube www.youtube.com
■メドピア公式note
style.medpeer.co.jp