GitHub ActionsでTerraformのCI/CDパイプラインを構築する方法|plan自動実行からapply承認フローまで

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > GitHub ActionsでTerraformのCI/CDパイプラインを構築する方法|plan自動実行からapply承認フローまで
「Terraformのコードをレビューしているのに、実際のplan結果を誰も確認できていない」「本番へのapplyを誰でも実行できる状態になっていて、誤操作でインフラが壊れないか不安だ」

こういった課題は、Terraformを個人で使い始めた段階から、チームに広げようとした瞬間に一気に顕在化します。コードレビューはできても、そのコードを適用したらインフラがどう変わるのかをレビュアーが把握できていない、というのは非常に多い現場の悩みです。

この記事では、GitHub Actionsを使ってTerraformのCI/CDパイプラインを構築する方法を解説します。PRトリガーによるterraform plan自動実行とPRへの結果コメント投稿、GitHub Environmentsを使った本番applyの承認フロー、AWSへのOIDC認証設定まで、チーム開発で実際に使えるワークフロー設計の全体像をカバーします。

この記事のポイント

・PR作成時にterraform planを自動実行してレビュアーが差分を確認できる
・GitHub Environmentsで本番applyに承認フローを挟み誤操作を防ぐ
・OIDCを使いアクセスキー不要でAWSへ安全に認証する方法
・initの失敗・plan差分なし・OIDC認証エラーの典型的なトラブル対処法


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

なぜTerraformにCI/CDが必要なのか

Terraformを使いはじめた段階では、「ローカルでplan→目視確認→apply」という運用で十分に機能します。しかし、チームで作業するようになると、このワークフローは急速に機能しなくなります。

1. ローカルplanの運用が抱える問題

ローカルでterraform planを実行しても、その結果はターミナルに表示されるだけです。コードレビューでPull Requestを見ているレビュアーは、コードの差分を見ているに過ぎず、「このコードを実際に適用するとインフラがどう変わるのか」を目にすることができません。

また、複数のエンジニアが同時にローカルからapplyを実行しようとすると、tfstateのロック競合が発生します。S3バックエンドとDynamoDBによるstateロックを設定していても、applyのタイミングをチームで調整する手間は残ります。

さらに、「誰がいつapplyしたか」の証跡をterraformコマンドのログとして残すのは困難です。GitHubのワークフロー経由でapplyを一元化すれば、変更の履歴とapplyのログが自然に残ります。

2. CI/CDが解決する3つの課題

GitHub ActionsによるCI/CDパイプラインは、以下の3点を解決します。

planの可視化:PR作成・更新時に自動でterraform planを実行し、結果をPRコメントとして投稿します。レビュアーはコードの差分とplan結果を同じ画面で確認できます。
applyの権限制御:applyはmainブランチへのマージ後、かつ承認者のレビューを通過した場合のみ実行されます。任意のタイミングで誰でもapplyできる状態を解消します。
証跡の記録:applyはGitHub Actionsのジョブとして実行されるため、実行者・タイムスタンプ・ログがGitHubのWorkflow履歴として残ります。

CI/CDの設計パターンを選ぶ

Terraform CI/CDの設計パターンは主に2つあります。チームの規模と環境の重要度によって選択します。

1. パターンA(シンプル型):マージ後に自動apply

PRを作成するとterraform planが実行され、mainブランチへのマージをトリガーとしてterraform applyが自動実行されます。承認フローはなく、PR承認が実質的なapplyのゲートになります。

・開発環境・ステージング環境に向いている
・セットアップが簡単で、CI/CDの導入初期に採用しやすい
・本番環境には向かない(マージ即applyのため誤操作リスクが残る)

2. パターンB(承認フロー型):GitHub Environmentsで承認後にapply

PR作成→planの確認→マージ、ここまでは同じです。その後、GitHub Environmentsの「Required reviewers」設定により、承認者がapplyを許可するまでワークフローが待機します。承認後にterraform applyが実行されます。

・本番環境向き
・承認者を特定のユーザーに限定できる
・承認待ち状態がGitHubのUIで可視化される

この記事ではパターンBを中心に解説します。本番運用に耐えられる設計を最初から採用する方が、後から追加するよりもコストが低いためです。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformのCI/CD設計からモジュール設計・環境分離まで、実務で使えるIaCスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

AWSへの認証設定(OIDCを使う)

GitHub ActionsからAWS APIを呼び出すにはIAM認証が必要です。かつてはアクセスキーをGitHub Secretsに登録する方法が主流でしたが、現在はOIDC(OpenID Connect)を使う方法が推奨されています。

1. OIDCを使う理由

アクセスキーをGitHub Secretsに登録する方法は、キーが長期間有効であることが問題です。Secretsの設定ミスや流出事故があると、長期間にわたってAWS操作権限が悪用されるリスクがあります。

OIDCでは、GitHub Actionsが実行されるたびに一時トークンをAWSから取得します。トークンの有効期間は通常1時間未満で、静的なアクセスキーを保持しません。長期的な認証情報の管理コストとリスクを根本から排除できます。

2. IAM IDプロバイダーの登録

まず、GitHubをAWSのIAM IDプロバイダーとして登録します。

# GitHubのOIDCプロバイダーURLとフィンガープリントを使って登録 aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ --client-id-list sts.amazonaws.com \ --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 # 作成されたARNを確認する aws iam list-open-id-connect-providers

3. IAMロールの作成(信頼ポリシーの設定)

次に、GitHub ActionsがAssumeRoleできるIAMロールを作成します。信頼ポリシーで、どのリポジトリのどのブランチからの実行を許可するかを厳密に指定します。

# trust-policy.json の内容 # your-org/your-repo の部分を実際のリポジトリに変更する { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*" } } } ] } # IAMロールを作成 aws iam create-role \ --role-name TerraformCIRole \ --assume-role-policy-document file://trust-policy.json # Terraformに必要な権限ポリシーをアタッチ(例: AdministratorAccess) # 実際の運用では最小権限のカスタムポリシーを作成することを推奨 aws iam attach-role-policy \ --role-name TerraformCIRole \ --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

信頼ポリシーの `sub` 条件に `repo:your-org/your-repo:*` のようにワイルドカードを使うと、リポジトリ内のすべてのブランチ・環境からAssumeRoleが可能になります。【注意】本番環境用のロールでは、`repo:your-org/your-repo:ref:refs/heads/main` のように特定のブランチに限定することを強く推奨します。ワイルドカードのまま運用すると、フォークリポジトリからの意図しないApplyリスクが残ります。

GitHub Actionsワークフローの実装

ワークフローは2つのファイルに分けます。plan用(PRトリガー)とapply用(mainマージ後トリガー)です。リポジトリの `.github/workflows/` ディレクトリに配置します。

1. planワークフロー(PR作成・更新時)

# .github/workflows/terraform-plan.yml name: Terraform Plan on: pull_request: branches: - main paths: - '**.tf' - '**.tfvars' permissions: contents: read id-token: write # OIDCトークン取得に必要 pull-requests: write # PRコメントの投稿に必要 jobs: plan: name: Terraform Plan runs-on: ubuntu-latest defaults: run: working-directory: ./terraform # tfファイルのあるディレクトリ steps: - name: Checkout uses: actions/checkout@v4 - name: Configure AWS Credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/TerraformCIRole aws-region: ap-northeast-1 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: 1.8.5 - name: Terraform Init id: init run: terraform init - name: Terraform Format Check id: fmt run: terraform fmt -check continue-on-error: true - name: Terraform Validate id: validate run: terraform validate - name: Terraform Plan id: plan run: terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt continue-on-error: true # planの結果をPRコメントに投稿する - name: Post Plan Result to PR uses: actions/github-script@v7 with: script: | const fs = require('fs'); const planOutput = fs.readFileSync('./terraform/plan_output.txt', 'utf8'); const truncated = planOutput.length > 60000 ? planOutput.substring(0, 60000) + '\n... (出力が長すぎるため省略)' : planOutput; const body = `## Terraform Plan 結果 #### Format: \`${{ steps.fmt.outcome }}\` #### Validate: \`${{ steps.validate.outcome }}\` #### Plan: \`${{ steps.plan.outcome }}\`

Plan の詳細を見る \`\`\` ${truncated} \`\`\`
`; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: body });

`paths` フィルターで `.tf` と `.tfvars` の変更時のみワークフローを起動するようにしています。READMEの更新やアプリケーションコードの変更でTerraform planが走るのを防ぐためです。

2. applyワークフロー(mainマージ後)

# .github/workflows/terraform-apply.yml name: Terraform Apply on: push: branches: - main paths: - '**.tf' - '**.tfvars' permissions: contents: read id-token: write jobs: apply: name: Terraform Apply runs-on: ubuntu-latest # GitHub Environmentsの「production」環境を指定 # Required reviewersを設定することで承認フローが有効になる environment: production defaults: run: working-directory: ./terraform steps: - name: Checkout uses: actions/checkout@v4 - name: Configure AWS Credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/TerraformCIRole aws-region: ap-northeast-1 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: 1.8.5 - name: Terraform Init run: terraform init - name: Terraform Apply run: terraform apply -auto-approve -input=false

`environment: production` の指定が承認フローのキーです。この1行が、次節で設定するGitHub Environmentsの承認ゲートと連動します。

GitHub Environmentsで承認フローを設定する

1. Environmentの作成

GitHubリポジトリの「Settings」→「Environments」→「New environment」から `production` という名前のEnvironmentを作成します。

作成後、「Required reviewers」に承認者を追加します。ここに登録されたユーザーまたはチームがapplyを承認するまで、ワークフローのapplyジョブは「Waiting」状態で停止します。

2. 承認フローの動作確認

mainブランチにマージが行われると、以下の流れで処理が進みます。

・mainへのpushをトリガーにterraform-apply.ymlのワークフローが起動する
・`environment: production` の設定により、ジョブの実行前にRequired reviewersへ承認依頼の通知が送られる
・承認者がGitHubのUIで「Approve and deploy」を選択するとジョブが再開し、terraform applyが実行される
・承認者が「Reject」するとジョブはキャンセルされ、applyは実行されない

承認依頼はメール通知またはGitHubのIn-app通知で届きます。承認の有効期限(Deployment protection rules内のTimer)も設定可能で、一定時間以内に承認がなければ自動でタイムアウトさせることができます。

CI/CDパイプラインで出がちなエラーと対処

1. terraform init が失敗する

よくある原因は、backendの設定に使っているS3バケットやDynamoDBテーブルへのアクセス権限不足です。IAMロールのポリシーに以下のアクションが含まれているか確認します。

# initに必要なS3・DynamoDB操作権限の確認 # S3バケット: GetObject, PutObject, DeleteObject, ListBucket # DynamoDB: GetItem, PutItem, DeleteItem(stateロック用) # IAMポリシーのシミュレーションで確認する aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:role/TerraformCIRole \ --action-names s3:GetObject s3:PutObject dynamodb:GetItem \ --resource-arns arn:aws:s3:::your-tfstate-bucket/*

2. planで「No changes」になり差分が出ない

CI環境とローカルで異なる変数ファイル(`.tfvars`)を参照していないか確認します。GitHub Actionsのワークフローでは `-var-file` オプションを明示して、どの変数ファイルを使うかをコードで管理することを推奨します。

# 変数ファイルを明示して指定する terraform plan -var-file="environments/prod.tfvars" -out=tfplan # GitHub Secretsに格納したセンシティブ変数を渡す場合 terraform plan \ -var="db_password=${{ secrets.DB_PASSWORD }}" \ -out=tfplan

3. OIDC認証エラー(Error assuming role)

`Error assuming role: AssumeRoleWithWebIdentity` が出た場合は、以下を順に確認します。

IDプロバイダーのARN:trust-policy.jsonの `Federated` フィールドに設定したARNが実際のIDプロバイダーのARNと一致しているか
subの条件:trust-policy.jsonの `sub` 条件にリポジトリ名やブランチ名の指定ミスがないか。`repo:` の後はGitHubのオーナー名/リポジトリ名の形式です。
AWSアカウントID:trust-policy.jsonやワークフロー内のARNに正しいAWSアカウントIDが入っているか

# 実際に受け取っているsub値を確認するには、ワークフローに以下を追加してログ確認する - name: Debug OIDC Token Sub run: | echo "ACTIONS_ID_TOKEN_REQUEST_URL: $ACTIONS_ID_TOKEN_REQUEST_URL" TOKEN=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) echo $TOKEN | cut -d '.' -f2 | base64 -d 2>/dev/null | jq .sub

sub値の出力例: `repo:your-org/your-repo:ref:refs/heads/main` のような形式になります。trust-policy.jsonのConditionと一致しているかをこの出力で確認します。

本記事のまとめ

GitHub ActionsでのTerraform CI/CD構築における設計ポイントをまとめます。

設計項目 推奨設定
planの実行タイミング PR作成・更新時(`pull_request`トリガー)
planの結果共有 actions/github-scriptでPRコメントに投稿
applyの実行タイミング mainへのpushトリガー+Environment承認後
本番承認フロー GitHub Environments + Required reviewers
AWS認証方式 OIDCによる一時トークン(アクセスキー不要)
IAMロールのsub条件 本番用ロールはmainブランチのみに限定
変数ファイル -var-fileで環境ごとのtfvarsを明示指定
CI/CDパイプラインを導入すると、Terraformの運用コストは一時的に上がりますが、チームが大きくなるほど事故防止と証跡管理の恩恵が大きくなります。特にOIDC認証と承認フローの設定は、一度整備してしまえばほとんどメンテナンスコストがかかりません。

Terraformのstateバックエンド(S3+DynamoDB)の設定がまだの場合は、CI/CD導入前に「Terraformのtfstate管理とS3バックエンド設定」を先に確認することをお勧めします。また、providerのバージョン管理とterraform.lock.hclの扱いについては「Terraformのprovider設定とバージョン管理」を参照してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformのCI/CDパイプライン設計・module再利用・tfstate運用まで、実務直結のハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。