1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自前ウェブサイトを構築してみた Part6 - GitHub Actions OIDC 認証 + VPC Endpoints 削減で $21.90/月 削減

1
Posted at

はじめに

Part5 まででコンテナアプリケーションの自動スケーリングまで実現しましたが、今回は GitHub Actions による CI/CD パイプラインVPC Endpoints の無効化によるコスト削減を実装します。

これまでの記事

対象読者

  • GitHub Actions で AWS へデプロイしたい方
  • OIDC 認証によるシークレットキー不要のデプロイに興味がある方
  • VPC Endpoints のコストを削減したい方
  • セキュリティとコストのトレードオフを理解したい方

概要

今回は大きく分けて 2 つの改善を実施しました。

1. GitHub Actions OIDC 認証によるセキュアな CI/CD

従来の課題:

  • AWS アクセスキーを GitHub Secrets に保存
  • シークレットキーの漏洩リスク
  • 定期的なローテーションが必要

OIDC 認証の利点:

  • シークレットキー不要(GitHub が発行する一時トークン使用)
  • 短期間トークン(デフォルト 1 時間で自動失効)
  • リポジトリ/ブランチ制限(特定のリポジトリのみ許可)
  • 最小権限の原則(必要な権限のみ付与)

2. VPC Endpoints 削減による $21.90/月 コスト削減

変更前:

  • Private Subnet + VPC Endpoints 構成
  • 月額 $21.90 の固定費

変更後:

  • Public Subnet + Public IP 構成
  • VPC Endpoints 不要

トレードオフ:

  • コスト削減: $21.90/月
  • セキュリティ: インターネット経由でのアクセス

開発環境

ローカル環境

項目 バージョン/内容
OS Windows 11
Terraform v1.6.0 以上
AWS CLI v2.13.0 以上

AWS 環境

項目 内容
リージョン ap-northeast-1(東京)
ECS クラスター app-cluster
GitHub リポジトリ RYA234/typescript-container

構成図

image.png

変更点

変更前(Part5):

Private Subnet (10.0.2.0/24)
  ↓
VPC Endpoints (ECR API, ECR DKR, S3, CloudWatch Logs)
  ↓
AWS Services

コスト: $21.90/月

変更後(Part6):

Public Subnet (10.0.4.0/24, 10.0.5.0/24)
  ↓
Public IP
  ↓
Internet Gateway
  ↓
AWS Services

コスト: $0/月

今回実現したこと

1. GitHub Actions OIDC 認証の実装

OIDC Provider の作成

# GitHub Actions OIDC プロバイダー
# GitHub が発行する OIDC トークンを AWS が検証するためのプロバイダー
resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  # GitHub Actions の OIDC エンドポイント
  client_id_list = [
    "sts.amazonaws.com"
  ]

  # GitHub の証明書フィンガープリント(公式値)
  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
    "1c58a3a8518e8759bf075b76b750d4f2df264fcd"
  ]

  tags = {
    Name = "github-actions-oidc-provider"
  }
}

ポイント:

  • url: GitHub Actions の OIDC エンドポイント
  • client_id_list: sts.amazonaws.com 固定
  • thumbprint_list: GitHub の公式証明書フィンガープリント

IAM Role の作成(Trust Policy)

resource "aws_iam_role" "github_actions" {
  name        = "GitHubActionsRole"
  description = "Role for GitHub Actions to deploy to ECS via OIDC"

  # 信頼ポリシー: どの GitHub リポジトリからこのロールを Assume できるか
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            # OIDC トークンの audience 検証
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            # リポジトリとブランチを制限
            "token.actions.githubusercontent.com:sub" = [
              "repo:RYA234/typescript-container:ref:refs/heads/main"
            ]
          }
        }
      }
    ]
  })

  # セッション継続時間(1時間)
  max_session_duration = 3600
}

Trust Policy の重要ポイント:

  1. Principal.Federated: OIDC Provider の ARN を指定
  2. Action: sts:AssumeRoleWithWebIdentity で一時トークン発行
  3. Condition.StringEquals: audience 検証(sts.amazonaws.com
  4. Condition.StringLike: リポジトリ・ブランチ制限
    • フォーマット: repo:OWNER/REPO:ref:refs/heads/BRANCH
    • 例: repo:RYA234/typescript-container:ref:refs/heads/main

ECR プッシュ権限

resource "aws_iam_policy" "github_actions_ecr" {
  name = "GitHubActionsECRPolicy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ecr:GetAuthorizationToken",           # ECR ログイン
          "ecr:BatchCheckLayerAvailability",     # レイヤー確認
          "ecr:GetDownloadUrlForLayer",          # レイヤーダウンロード
          "ecr:BatchGetImage",                   # イメージ取得
          "ecr:PutImage",                        # イメージプッシュ
          "ecr:InitiateLayerUpload",             # アップロード開始
          "ecr:UploadLayerPart",                 # アップロード
          "ecr:CompleteLayerUpload"              # アップロード完了
        ]
        Resource = "*"
      }
    ]
  })
}

注意: ecr:GetAuthorizationToken はリソース指定不可のため "*" を使用

GitHub Actions Workflow 例

name: Deploy to ECS

on:
  push:
    branches: [main]

permissions:
  id-token: write  # OIDC トークン取得に必須
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build and Push Docker Image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: nodejs-app
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster app-cluster \
            --service nodejs-service \
            --force-new-deployment

重要ポイント:

  1. permissions.id-token: write が必須
  2. role-to-assume に GitHubActionsRole の ARN を指定
  3. シークレットキー不要

2. VPC Endpoints の無効化

変更後の構成

# Public Subnet(新規作成)
resource "aws_subnet" "public_fargate_1a" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.4.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

resource "aws_subnet" "public_fargate_1c" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.5.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = true
}

# VPC Endpoints 削除(vpc_endpoints.tf → vpc_endpoints.tf.disabled)

# ECS Service(Public Subnet)
resource "aws_ecs_service" "nodejs" {
  network_configuration {
    subnets          = [aws_subnet.public_fargate_1a.id]
    assign_public_ip = true  # Public IP 経由でアクセス
  }
}

変更のポイント:

  1. Private Subnet → Public Subnet に変更
  2. assign_public_ip = false → true に変更
  3. VPC Endpoints 削除 (vpc_endpoints.tf.disabled に移動)
  4. インターネット経由 で ECR / CloudWatch にアクセス

技術的なポイント

ポイント1: OIDC トークンの仕組み

1. GitHub Actions がワークフロー実行
   ↓
2. GitHub が OIDC トークンを発行
   (トークンにリポジトリ情報が含まれる)
   ↓
3. AWS に AssumeRoleWithWebIdentity リクエスト
   ↓
4. AWS が OIDC Provider で GitHub のトークンを検証
   ↓
5. Trust Policy の Condition をチェック
   (リポジトリ・ブランチが一致するか)
   ↓
6. 一時認証情報を発行(有効期間 1 時間)
   ↓
7. GitHub Actions が AWS リソースにアクセス

セキュリティ上の利点:

  • シークレットキーが GitHub Secrets に保存されない
  • トークンは 1 時間で自動失効
  • リポジトリ・ブランチ制限で不正アクセス防止

ポイント2: VPC Endpoints vs Public IP のコスト比較

VPC Endpoints(Interface Endpoint)のコスト:

$0.01/時間 × 3 エンドポイント × 24h × 30日 = $21.60/月
  + データ処理料金: 約 $0.30/月
合計: 約 $21.90/月

Public IP のコスト:

固定費: $0/月
データ転送料: 約 $0.10/月(ECR pull, CloudWatch Logs)
合計: 約 $0.10/月

削減額: $21.90 - $0.10 = $21.80/月

ポイント3: セキュリティのトレードオフ

VPC Endpoints 使用時(エンタープライズ構成):

  • ✅ インターネット経由しない(セキュア)
  • ✅ AWS サービスへのアクセスが VPC 内完結
  • ✅ コンプライアンス要件を満たしやすい
  • ❌ 月額 $21.90 の固定費

Public IP 使用時(コスト重視構成):

  • ✅ 月額 $21.90 削減
  • ✅ シンプルな構成
  • ❌ インターネット経由でアクセス
  • ❌ セキュリティグループで厳重な制御が必要

判断基準:

  • 商用環境: VPC Endpoints 推奨(セキュリティ優先)
  • 学習・ポートフォリオ: Public IP 推奨(コスト優先)

コスト試算

Part5 からの変更

項目 Part5 Part6 削減額
VPC Endpoints $21.90 $0 $21.90
データ転送料 $0 $0.10 -$0.10
合計削減 $21.80

全体コスト(月額)

サービス 詳細 Part5 Part6 削減額
ALB 時間料金 + LCU $20.00 $20.00 $0
VPC Endpoint Interface × 3 $21.90 $0 $21.90
ECS Fargate 3 サービス(17h) $8.92 $8.92 $0
ECR イメージ保存 $0.50 $0.50 $0
CloudWatch Logs 7 日保持 $3.26 $3.26 $0
データ転送料 Public IP $0 $0.10 -$0.10
合計 $54.58 $32.78 $21.80

削減率: 約 40%

Part1 からの累計削減効果

項目 Part1(想定) Part6(現在) 削減額
NAT Gateway → VPC Endpoints $32 → $21.90 → $0 $32.00
ECS(SPOT) - -$3.63 $3.63
ECS(時間制御) - -$3.02 $3.02
合計削減額 $38.65/月
年間削減額 $463.80/年

CI/CD フロー

デプロイの流れ

1️⃣ main ブランチに Push
   ↓
2️⃣ GitHub Actions Workflow triggered
   ↓
3️⃣ OIDC Auth → AssumeRole
   (GitHub が OIDC トークン発行 → AWS が検証)
   ↓
4️⃣ Build & Push to ECR
   (Docker イメージをビルドして ECR にプッシュ)
   ↓
5️⃣ Deploy to ECS (UpdateService)
   (ECS サービスを更新して新しいタスクをデプロイ)
   ↓
6️⃣ New task deployed
   (ALB のヘルスチェック完了後にトラフィック切り替え)

デプロイ時間

合計: 約 5-7 分
  - Docker Build: 2-3 分
  - ECR Push: 1-2 分
  - ECS Deploy: 2-3 分

感想

学んだこと

1. OIDC 認証の仕組みが理解できた

Trust Policy の重要性:

Condition = {
  StringLike = {
    "token.actions.githubusercontent.com:sub" = [
      "repo:RYA234/typescript-container:ref:refs/heads/main"
    ]
  }
}

この1行で、特定のリポジトリの特定のブランチのみ が AWS リソースにアクセスできるように制限できます。

2. コストとセキュリティのトレードオフ

結論: 環境に応じて使い分けることが重要

3. Terraform の柔軟性

vpc_endpoints.tf を .disabled にリネームするだけで、簡単に有効/無効を切り替えられます。

# 無効化
mv vpc_endpoints.tf vpc_endpoints.tf.disabled

# 有効化
mv vpc_endpoints.tf.disabled vpc_endpoints.tf

苦労した点

  1. OIDC Trust Policy の記述

    • リポジトリ名の形式が分かりにくかった
    • repo:OWNER/REPO:ref:refs/heads/BRANCH の形式を理解するのに時間がかかった
  2. Public Subnet への移行

    • subnet_ids と assign_public_ip を同時に変更する必要がある
    • 変更順序を間違えると ECS タスクが起動しない

参考資料

AWS 公式ドキュメント

GitHub ドキュメント

読んだ本

  1. AWS コンテナ設計・構築本格入門

まとめ

この記事で実現したこと

  • ✅ GitHub Actions OIDC 認証によるセキュアな CI/CD
  • ✅ シークレットキー不要のデプロイ
  • ✅ VPC Endpoints 削減で $21.90/月 コスト削減
  • ✅ Public Subnet 構成への移行
  • ✅ Terraform による完全な IaC 化

コスト削減効果

  • 月額 $21.80 削減(約 40% 削減)
  • 年間 $261.60 削減

Part1 からの累計削減

  • 月額 $38.65 削減
  • 年間 $463.80 削減

次のステップ

  • 🔧 CloudWatch Alarms + SNS 通知の構築
  • 🔧 Dashboard による可視化
  • 🔧 本番環境への移行検討(VPC Endpoints 再導入)

GitHubリポジトリ

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?