前回はS3とDynamoDBを使ったTerraformのリモートステート管理を紹介しました。ローカルでterraform applyするのも悪くはないんですが、「チーム開発だと誰がいつ実行したかわからない問題」がどうしても出てくる。今回はそれを解消するべく、GitHub Actionsと組み合わせてCI/CDパイプラインに組み込んでみます。
正直、自分もつい最近まで「インフラはローカルから手動で流せばいいでしょ」派だったんですが、一度パイプラインに乗せてしまうと戻れなくなりました。実行ログが全部GitHubに残るのが地味に便利すぎる。
この記事でわかること
- GitHub ActionsでTerraformのplan・applyを自動化する流れ
- OIDCを使ったキーレス認証でAWSに接続する方法
- PRでplanを自動実行し、結果をコメント投稿する実装
- mainマージ時の自動applyと承認ゲートの設定
- 実運用で気をつけるべきポイント
今回やること
大きな流れはこうです。
- PRを出したら
terraform planが自動で走り、結果をPRコメントに投稿する - mainブランチにマージしたら
terraform applyが自動で実行される - AWSの認証はOIDC(キーレス認証)で行う
AWSのアクセスキーをGitHub Secretsに置く方法も一般的ですが、最近はOIDCを使うケースが増えてきているみたいです。キーの漏洩リスクがないのと、ローテーション管理が不要なのが大きい。
前提
- 前回記事のリモートステート(S3 + DynamoDB)が設定済みであること
- GitHubリポジトリにTerraformのコードがある
- AWS CLIがローカルで使える状態(IAMロールの作成だけ先に手動でやります)
Step 1: AWS側でOIDC認証の設定をする
GitHub ActionsがAWSを操作するには、IAMロールとOIDCプロバイダーの設定が必要です。ここだけは最初に一回手動でやる必要があります(これ自体をTerraformで管理するのが本当はカッコいいんですが、今回はひとまずマネコンで)。
OIDCプロバイダーの登録
AWSマネコン → IAM → IDプロバイダー → 「プロバイダーを追加」から以下を設定します。
- プロバイダーのタイプ: OpenID Connect
- プロバイダーのURL:
https://token.actions.githubusercontent.com - 対象者(Audience):
sts.amazonaws.com
IAMロールの作成
GitHub Actionsが引き受けるIAMロールを作ります。信頼ポリシーは以下のようにします(YOUR_GITHUB_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": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_ORG/YOUR_REPO:*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
]
}
subの末尾のワイルドカード(*)は、条件を広めに許可する書き方として使われることが多いです。本番環境だけはより厳密に絞るほうが安全です。ここはプロジェクトの規模感で判断してください。
アタッチするIAMポリシーは、管理するAWSリソースに応じて必要なものを最小権限で付けます。とりあえず試すだけならAdministratorAccessをつけてしまいがちですが、本番ではちゃんと絞った方がいいです(自分への戒めも込めて)。
作成したロールのARNはメモしておいてください。あとで使います。
Step 2: GitHub Secretsの設定
OIDCを使う場合、GitHubに置く情報は最小限で済みます。リポジトリの Settings → Secrets and variables → Actions から以下を登録します。
AWS_ROLE_ARN: 先ほど作成したIAMロールのARNAWS_REGION:ap-northeast-1(東京リージョンなど)
アクセスキーとシークレットキーは不要です。ここが従来の方法との大きな違い。
Step 3: GitHub Actionsのワークフローを書く
いよいよ本題。.github/workflows/terraform.ymlを作ります。
今回は「PRでplan、mainマージでapply」という構成にします。典型的なフローは「PR作成 → terraform plan → PRにプランを投稿 → レビュー&承認 → mainにマージ → terraform apply」です。
name: Terraform CI/CD
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
pull-requests: write # PRにコメントするために必要
id-token: write # OIDCトークンの発行に必要
jobs:
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Terraform Init
run: terraform init -no-color
- name: Terraform Fmt Check
run: terraform fmt -check -no-color
- name: Terraform Validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
continue-on-error: true # planが失敗してもコメントは投稿したい
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
const output = `#### Terraform Plan 📋
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
Plan result: ${{ steps.plan.outcome }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
- name: Check Plan Result
if: steps.plan.outcome == 'failure'
run: exit 1
terraform-apply:
name: Terraform Apply
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Terraform Init
run: terraform init -no-color
- name: Terraform Apply
run: terraform apply -no-color -auto-approve
少し長いですが、構造自体はシンプルです。pull_requestイベントのときはPlanジョブだけ、push(=mainへのマージ)のときはApplyジョブだけが動くようにしています。
よくあるハマりポイント
id-token: writeのpermissionsを忘れるとOIDCが動きません。自分は最初これを忘れて「なんで認証できないんだ」と30分溶かしました。ワークフローの冒頭に書くpermissionsブロックは必須です。
あとterraform fmt -checkをCI上で走らせておくと、フォーマットが揃っていないコードをマージ前に弾けて地味に便利です。ローカルでterraform fmtをかけ忘れたままPushするとCIが落ちるので、嫌でも癖がつきます。
Step 4: Environmentsで本番適用前に承認ゲートを設ける(オプション)
上記のワークフローはmainマージと同時に自動でApplyが走ります。開発環境なら問題ないですが、本番相当のインフラを触う場合は「誰かが承認してからApplyする」仕組みがほしくなります。
GitHub ActionsのEnvironmentsを使うと、dev→staging→productionのような昇格ワークフローが作れます。developmentは保護ルールなしで自動デプロイ、stagingはオプションの待機タイマーを設定、productionは必須レビュアーを指定するといった使い分けが可能です。
設定はシンプルで、リポジトリの Settings → Environments から環境を作成し、Required reviewersに承認者を設定するだけです。ワークフロー側はenvironment: productionを追加します。
terraform-apply:
name: Terraform Apply
runs-on: ubuntu-latest
environment: production # これを追加するだけ
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# ... 以下は同じ
この設定により、GitHub の Settings → Environments → production で Environment Protection Rules を構成でき、terraform applyの実行前に手動承認を要求できます。承認が来るまでジョブはその場で待機します。
余談ですが、承認待ちのジョブをSlack通知と連携させると「誰かApplyしてー」ってなれるのでより便利です。そこまでやり始めると沼なんですが。
動かしてみた感想と注意点
実際に動かしてみると、PRのコメント欄にterraform planの差分がバーンと出てきて、「ああ、これがインフラの変更レビューか」ってなります。コードのdiffとplanの差分を並べてレビューできるのは確かに良い体験。
ただし注意点がいくつかあって、PRのplanはマージ時点で古くなっている可能性があるというのは覚えておいてください。複数人で開発していてほぼ同時にPRをマージすると、先にマージされた側の変更がplanに含まれていない状態でApplyが走ります。PRで保存したplanはマージ後に古くなっている場合があるため、Applyジョブでは新たにplanを実行してからapplyするのがより安全とされています。
もう一点、terraform apply -auto-approveは一回実行したら止まりません。Applyが走り始めてから「あ、これまずい変更だった」と気づいても手遅れになる場合があります。本番環境こそEnvironmentsの承認ゲートは入れておくことをおすすめします。
※この記事にはプロモーションが含まれます
ちなみに、お名前.com レンタルサーバー(WordPressに特化した高速レンタルサーバー。月額990円〜、独自ドメイン実質0円)も気になっています。お名前.com レンタルサーバー![]()
まとめ
ここまでの3回で、Terraformの基本構文 → リモートステート → CI/CDパイプラインと積み上げてきました。今回設定したOIDCによるキーレス認証 + PRでのplan自動表示 + mainマージでのapplyという構成は、個人開発でも小規模チームでも使えるシンプルな出発点として機能します。GitHub ActionsのワークフローはYAMLなので一度書いてしまえば、あとはコード化されたパイプラインとして資産になるのも大きなメリット。インフラ構築をここまで見える化できると、運用がぐんと楽になります。
📚 シリーズ「Terraform × AWS インフラ自動化入門」(第3回 / 全5回)
← 前回の記事: 前回の記事はこちら
→ 次回の記事: 【第4回】Terraform × AWS インフラ自動化入門 — 環境分離(dev/stg/prod)とワークスペース・変数管理の実践パターン

