1. 概要
- GitHub ActionsでTerraformのCI/CDを構築する基本構成
- plan(PRレビュー用)とapply(mainマージ後)の2段階ワークフロー
- 認証(OIDC vs アクセスキー)の選択
- Stateバックエンド(S3+DynamoDB)の準備
GitHub ActionsはGitHubに統合されたCI/CDサービスです。Terraformと組み合わせることで、インフラをコードとしてレビュー・承認・適用するワークフローを構築できます。
2. 前提:S3バックエンドの準備
# backend_setup/main.tf(一度だけ手動applyする)
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
variable "environment" {
description = "環境名"
type = string
default = "dev"
}
resource "aws_s3_bucket" "tfstate" {
bucket = "my-company-tfstate-${var.environment}"
tags = {
Name = "my-company-tfstate-${var.environment}"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "tflock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "terraform-lock"
Environment = var.environment
ManagedBy = "terraform"
}
}
3. メインのTerraform設定
# main.tf
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-company-tfstate-dev"
key = "app/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
provider "aws" {
region = "ap-northeast-1"
}
variable "environment" {
description = "環境名"
type = string
default = "dev"
}
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
root_block_device {
volume_size = 20
volume_type = "gp3"
encrypted = true
}
tags = {
Name = "${var.environment}-web"
Environment = var.environment
ManagedBy = "terraform"
}
}
4. GitHub Actionsワークフロー
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
id-token: write # OIDC認証に必要
contents: read
pull-requests: write # PRコメントに必要
env:
TF_VERSION: "~> 1.9"
AWS_REGION: "ap-northeast-1"
jobs:
terraform:
name: Terraform Plan / Apply
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./environments/production
steps:
# 1. ソースコードをチェックアウト
- name: Checkout
uses: actions/checkout@v4
# 2. AWS認証(OIDC — アクセスキー不要)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: ${{ env.AWS_REGION }}
# 3. Terraform CLIをセットアップ
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
# 4. terraform init
- name: Terraform Init
run: terraform init
# 5. フォーマットチェック
- name: Terraform Format Check
run: terraform fmt -check -recursive
# 6. バリデーション
- name: Terraform Validate
run: terraform validate
# 7. plan(PRのみ)
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: |
terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt
echo "exitcode=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
continue-on-error: true
# 8. plan結果をPRコメントに投稿
- name: Comment Plan on PR
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('environments/production/plan_output.txt', 'utf8');
const output = plan.length > 65000 ? plan.substring(0, 65000) + '
...(truncated)' : plan;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan
\`\`\`
${output}
\`\`\``
});
# 9. apply(mainへのpushのみ)
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve
5. OIDC IAMロールの設定
# iam_oidc.tf(一度だけ適用)
data "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
}
resource "aws_iam_role" "github_actions" {
name = "GitHubActionsRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = data.aws_iam_openid_connect_provider.github.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:*"
}
}
}]
})
tags = {
Name = "GitHubActionsRole"
Environment = "dev"
ManagedBy = "terraform"
}
}
resource "aws_iam_role_policy_attachment" "github_actions_terraform" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
# 本番環境では最小権限ポリシーに絞ること
}
6. よくある問題と対処
| 問題 | 原因 | 対処 |
|---|---|---|
terraform initでバックエンドエラー | S3バケットが未作成 | backend_setupを先にapply |
| OIDC認証エラー | IAMロールのConditionが合わない | subのパターンを確認 |
fmt -checkが失敗 | フォーマット未適用 | ローカルでterraform fmt -recursive |
| planがPRコメントに表示されない | pull-requests: write権限がない | permissionsブロックを確認 |
7. 関連記事
- GitHub Actions: PR時にterraform planをコメントで返す — PRコメント詳細
- GitHub Actions: OIDC認証でAWSクレデンシャルを排除する — OIDC設定詳解
- GitHub Actions: 複数AWSアカウントへのデプロイ — マルチアカウント
- Terraform Cloud vs GitHub Actions — プラットフォーム比較
- backend — stateファイルの保存場所を設定 — S3バックエンド設定
- Terraform CI/CDのシークレット管理 — 認証情報の安全な扱い
8. まとめ
- GitHub ActionsでTerraformを動かす基本構成はplan(PR用)+ apply(main用)の2段階
- AWS認証はOIDCを使えばアクセスキーが不要でセキュア
- StateはS3+DynamoDB(ロック)をバックエンドとして使う
- plan結果はPRコメントに自動投稿することでチームレビューが可能になる
fmt -checkとvalidateをplanの前に入れることで品質を担保する
対象バージョン: Terraform >= 1.9 / GitHub Actions (2024) 公式ドキュメント: https://developer.hashicorp.com/terraform/tutorials/automation/github-actions