【実務・中級編】GitHub Actionsによる「Infrastructure as Code (IaC)」の構成管理:Terraform/OpenTofuの計画・適用フロー – バージョン管理・CI/CD活用バイブル

【GitHub Actions × Terraform/OpenTofu】破壊を防ぎ、デプロイ速度を極限まで高めるIaCパイプラインの設計思想

こんにちは。テックリードの私だ。

日々のインフラ運用で、こんな悪夢を見たことはないだろうか。

  • 「誰かがローカルから `terraform apply` を実行し、リモートステートがロックされて次のデプロイが進まない」
  • 「プルリクエストの数千行に及ぶ `terraform plan` の差分を誰もちゃんと読まずにマージし、本番のデータベースが消えかけた」
  • 「手動承認のフローが雑で、意図しないタイミングでステージング環境が吹き飛んだ」

Infrastructure as Code(IaC)において、GitHub Actionsはその強力なエンジンとなり得るが、素朴な設定のまま放置すれば、ただの「インフラ破壊加速装置」に成り下がる。

今回は、チームの開発生産性を落とさず、かつ「絶対に事故らない」ための、GitHub ActionsとTerraform(またはOpenTofu)による堅牢なCI/CDパイプラインの構築手法を、実戦投入可能なコードとともにすべて解説する。

—

1. チーム開発を破綻させないための設計方針

安全かつ高速なIaCパイプラインを実現するため、以下の3原則を敷く。

1. Planの自動化とPRへの集約: すべての変更はPR経由で行い、`plan` の結果を人間が視認しやすい形でフィードバックする。
2. ステート競合の完全排除: 同時実行制御(Concurrency)を使い、同一環境へのデプロイメントを直列化する。
3. 明示的な承認ゲート: 本番環境(Production)への `apply` は、コードのマージ後、または特定の承認者(Reviewers)のチェックを経た手動トリガーを必須とする。

—

2. 現場で即効性を発揮するワークフロー実装

以下に、Staging/Production環境を分離し、PRへのplan結果自動投稿、ステートロック回避、承認フローをすべて網羅したワークフローの完全版を示す。

`.github/workflows/iac-pipeline.yml` として配置してほしい。

name: “IaC Pipeline (Terraform/OpenTofu)”

on:
pull_request:
branches:

  • main

paths:

  • ‘terraform/’

push:
branches:

  • main

paths:

  • ‘terraform/’

【極めて重要】同一環境・同一ブランチでの同時実行(ステート競合・レースコンディション)を完全に防ぐ
concurrency:
group: terraform-${{ github.head_ref || github.ref }}
cancel-in-concurrency: false # 実行中のplan/applyを勝手に殺さず、キューイングさせる

env:
TF_VERSION: “1.6.6” # または OpenTofu のバージョン
WORKING_DIR: “terraform”
# AWSを使う場合の例(OIDC認証を強く推奨)
AWS_REGION: “ap-northeast-1”

jobs:
terraform-plan:
name: “Terraform Plan”
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC認証用
contents: read # リポジトリ読み取り用
pull-requests: write # PRへのコメント投稿用

steps:

  • name: Checkout Repository

uses: actions/checkout@v4

# セキュアなクラウド認証(AWSの例:長期クレデンシャルは絶対に環境変数に持たせない)

  • name: Configure AWS Credentials via OIDC

uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsTerraformExecutionRole
aws-region: ${{ env.AWS_REGION }}

  • name: Setup OpenTofu/Terraform

uses: opentofu/setup-opentofu@v1 # Terraformの場合は hashicorp/setup-terraform@v3
with:
tofu_version: ${{ env.TF_VERSION }}

  • name: Terraform Fmt

id: fmt
run: tofu fmt -check
working-directory: ${{ env.WORKING_DIR }}
continue-on-error: true

  • name: Terraform Init

id: init
run: tofu init
working-directory: ${{ env.WORKING_DIR }}

  • name: Terraform Validate

id: validate
run: tofu validate
working-directory: ${{ env.WORKING_DIR }}

  • name: Terraform Plan

id: plan
run: tofu plan -no-color -out=tfplan
working-directory: ${{ env.WORKING_DIR }}
continue-on-error: false

# PRの場合のみ、plan結果をスマートにコメントとして投稿する

  • name: Update Pull Request with Plan

uses: actions/github-script@v7
if: github.event_name == ‘pull_request’
with:
script: |
const fs = require(‘fs’);
const path = require(‘${{ env.WORKING_DIR }}/tfplan’); // 実際にはplanのテキスト出力をパースするか、echoする

// 可読性を高めるために出力整形を行う処理(長すぎる場合は折りたたむなど)
const output = `

Terraform Plan Results 🤖

  • Fmt: \`${{ steps.fmt.outcome }}\`
  • Init: \`${{ steps.init.outcome }}\`
  • Validate: \`${{ steps.validate.outcome }}\`
  • Plan: \`${{ steps.plan.outcome }}\`
Show Plan Details

\`\`\`hcl
Changes to Outputs:
(Plan output successfully generated)
\`\`\`

`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})

terraform-apply:
name: “Terraform Apply”
needs: terraform-plan
if: github.event_name == ‘push’ && github.ref == ‘refs/heads/main’
runs-on: ubuntu-latest
environment: production # GitHub Environmentsの保護ルール(承認者設定)をフックする
permissions:
id-token: write
contents: read

steps:

  • name: Checkout Repository

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/GitHubActionsTerraformExecutionRole
aws-region: ${{ env.AWS_REGION }}

  • name: Setup OpenTofu/Terraform

uses: opentofu/setup-opentofu@v1
with:
tofu_version: ${{ env.TF_VERSION }}

  • name: Terraform Init

run: tofu init
working-directory: ${{ env.WORKING_DIR }}

  • name: Terraform Apply

run: tofu apply -auto-approve
working-directory: ${{ env.WORKING_DIR }}

—

3. 現場で差がつくプロのハックとベストプラクティス

上記のコードをベースに、さらにチームの運用の質を一段引き上げるための「実践知」を共有しよう。

① `concurrency` によるステートロックの競合回避

TerraformやOpenTofuはS3やDynamoDB等でステートロックを張る機能を持っているが、GitHub Actions側で同時実行制御をしないと、CI上でジョブが同時に走り、競合エラーや予期せぬステート破損の原因になる。
上記のワークフローにある `concurrency` ブロックは必須だ。`cancel-in-concurrency: false` とすることで、後から走ったジョブが前のジョブを強制終了するのを防ぎ、安全に順番待ち(キューイング)させることができる。

② GitHub Environments による厳格な承認フロー

`terraform-apply` ジョブにある `environment: production` の指定に注目してほしい。
GitHubのRepository Settingsから Environments 機能を開き、`production` 環境に対して以下を設定する。

  • Required reviewers(承認者の必須化): シニアエンジニアやインフラ責任者を指定。
  • Deployment branches: `main` ブランチからのみデプロイを許可。

これにより、コードが `main` にマージされた後、人間の手による「Review & Approve」のボタンが押されるまで本番適用が物理的にブロックされる。チャットツール(Slack等)への通知連携と組み合わせれば、監査証跡としても完璧なフローが完成する。

③ クラウド認証には「OIDC(OpenID Connect)」を絶対に使え

AWS等のアクセスキー(`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`)をGitHubのSecretsに保存していなイカ? それはセキュリティ上の爆弾を抱えているようなものだ。キーのローテーション漏れや、悪意あるPRからのシークレット流出のリスクがある。
GitHub Actionsが発行する一時的なJWTトークンを信頼させ、クラウド側のIAMロールを直接引き受ける OIDC認証 を今すぐ導入しよう。長期的クレデンシャルをリポジトリから完全に排除できる。

—

4. エディタ・拡張機能の選択とローカル開発の高速化

CI/CDを回す前に、開発者のローカル体験(DX)を最適化することも忘れてはならない。

  • VS Code 拡張機能:
  • HashiCorp Terraform / OpenTofu: 構文補完、ホバー時のドキュメント表示、リファクタリング支援に必須。
  • Error Lens: エラーや警告をコード行のインラインに即座に表示させ、タイポをゼロにする。
  • キーボードショートカットの極意:
  • 保存時(`Cmd + S` / `Ctrl + S`)に自動で `terraform fmt` が走るように `settings.json` を設定せよ。CIでのフォーマットエラー(`tofu fmt -check` 落ち)で無駄なコミットを量産する時間を完全にゼロにできる。

“[terraform]”: {
“editor.formatOnSave”: true,
“editor.defaultFormatter”: “hashicorp.terraform”
},
“[terraform-vars]”: {
“editor.formatOnSave”: true,
“editor.defaultFormatter”: “hashicorp.terraform”
}

—

5. おわりに

IaCの自動化において、最もコストが高いのは「障害発生時のリカバリ」と「ステートの修復」だ。
今回紹介した構成は、一見すると少しボイラープレートが多く見えるかもしれないが、「事故の起きようがない構造」をパイプライン側に強制させるための投資である。

規律正しいパイプラインは、開発者の認知負荷を劇的に下げ、ビジネスの価値を創出するスピードを加速させる。あなたのチームのインフラ管理も、今すぐこのモダンな設計にアップデートしてほしい。

タイトルとURLをコピーしました