こういった課題は、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認証エラーの典型的なトラブル対処法
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ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を中心に解説します。本番運用に耐えられる設計を最初から採用する方が、後から追加するよりもコストが低いためです。
>> 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
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 }}\`
`; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: body });Plan の詳細を見る
\`\`\` ${truncated} \`\`\`
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
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
本記事のまとめ
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を明示指定 |
Terraformのstateバックエンド(S3+DynamoDB)の設定がまだの場合は、CI/CD導入前に「Terraformのtfstate管理とS3バックエンド設定」を先に確認することをお勧めします。また、providerのバージョン管理とterraform.lock.hclの扱いについては「Terraformのprovider設定とバージョン管理」を参照してください。
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:Terraformのprovider設定とバージョン管理|required_providersとterraform.lock.hclでチーム開発を安定させる方法
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます