【AWSハンズオン】第7回 ECS Task DefinitionとService設定

【AWSハンズオン】第7回 ECS Task DefinitionとService設定

前回作成したDockerイメージをECRにプッシュし、ECS Task DefinitionとServiceを設定します。実際にFargateでコンテナを起動し、ALB経由でAPIにアクセスできる環境を構築します。

AWS #AWS#初学者向け#ハンズオン

【AWSハンズオン】第7回 ECS Task DefinitionとService設定

サムネイル

前回作成したDockerイメージをECRにプッシュし、ECS Task DefinitionとServiceを設定します。実際にFargateでコンテナを起動し、ALB経由でAPIにアクセスできる環境を構築します。

更新日: 8/14/2025

今回作業対象のブランチ

前提条件

  • 第1回から第6回までの構築が完了していること
  • Dockerイメージがローカルでbuildできていること
  • AWS CLIが設定済みで、ECRへのアクセス権限があること
  • こちらからDockerコンテナでの実行環境が準備できていること

Terraform実行環境の起動

前回と同様に、Dockerコンテナ内で作業を進めます。

# コンテナを起動してbashに入る
docker-compose run --rm terraform

これ以降のコマンドは、すべてこのコンテナ内で実行します。

基礎知識

Task Definitionとは

Task Definitionは、ECSでコンテナを実行するための設計図として認識しておきましょう。

WEBアプリケーション開発者の視点から見ると

docker-compose.ymlのようなものだと理解してください。ローカル開発ではdocker-composeでコンテナの設定を定義しますが、ECSではTask Definitionがその役割を担います。

docker-compose.yml Task Definition
image container_definitions.image
ports portMappings
environment environment
volumes mountPoints
command command

ECS Serviceとは

Serviceは、Task Definitionに基づいてタスクを常に希望数だけ実行・管理するリソースです。

なぜServiceが必要か

機能 説明
タスク数の維持 指定した数のタスクを常に維持
ヘルスチェック連携 異常なタスクを自動的に再起動
ロードバランサー連携 ALBへの自動登録・解除
デプロイ管理 新バージョンへの安全な更新

ヘルスチェックの階層構造

ECSでは3つのレベルでヘルスチェックが実行されます。それぞれ異なる役割を持ち、組み合わせることで高可用性を実現できます。

レベル 設定場所 監視主体 失敗時の動作 必須/推奨
コンテナレベル Dockerfile HEALTHCHECK Docker Engine コンテナステータスがunhealthy 推奨
タスクレベル Task Definition healthCheck ECS Agent タスクの再起動 オプション
ALBレベル Target Group Health Check ALB トラフィックから除外 必須

本番環境では、DockerfileでHEALTHCHECKを定義するため、Task Definitionのhealthcheckは省略します。

デプロイ戦略

ECSは複数のデプロイ戦略をサポートしています。

戦略 説明 使用場面
Rolling Update 古いタスクを段階的に新しいタスクに置き換え 一般的なアップデート
Blue/Green 新旧環境を並行稼働させて切り替え ダウンタイムゼロが必須の場合
External 外部ツールでデプロイを管理 CI/CDツール連携時

今回はRolling Updateを使用します。

今回作成するリソース

リソース 説明 数量
ECS Task Definition コンテナ実行の設計図 1個
ECS Service タスクの実行・管理 1個
Fargate Tasks 実行されるコンテナインスタンス 2個
ECRイメージ デプロイするDockerイメージ 1個

Terraformコードの実装

変数の追加

Task DefinitionとService用の変数を追加します。

# terraform/variables.tf に追加

variable "task_cpu" {
  description = "タスクのCPU単位(256 = 0.25 vCPU)"
  type        = string
  default     = "256"
}

variable "task_memory" {
  description = "タスクのメモリ(MB)"
  type        = string
  default     = "512"
}

variable "app_count" {
  description = "実行するタスク数"
  type        = number
  default     = 2
}

Fargate CPU/メモリの組み合わせ

CPU 利用可能なメモリ
256 (.25 vCPU) 512MB, 1GB, 2GB
512 (.5 vCPU) 1GB〜4GB (1GB刻み)
1024 (1 vCPU) 2GB〜8GB (1GB刻み)

Task Definition

コンテナの実行設定を定義します。Secrets Managerとの連携も設定します。

# terraform/ecs_service.tf

# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
  family                   = "${var.project_name}-${var.environment}-app"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.task_cpu
  memory                   = var.task_memory
  execution_role_arn       = aws_iam_role.ecs_execution_role.arn
  task_role_arn            = aws_iam_role.ecs_task_role.arn

  container_definitions = jsonencode([
    {
      name      = "${var.project_name}-${var.environment}-app"
      image     = "${aws_ecr_repository.app.repository_url}:latest"
      essential = true

      portMappings = [
        {
          containerPort = 3000
          protocol      = "tcp"
        }
      ]

      environment = [
        {
          name  = "NODE_ENV"
          value = "production"
        },
        {
          name  = "PORT"
          value = "3000"
        }
      ]

      secrets = [
        {
          name      = "DB_HOST"
          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:host::"
        },
        {
          name      = "DB_PORT"
          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:port::"
        },
        {
          name      = "DB_NAME"
          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:dbname::"
        },
        {
          name      = "DB_USER"
          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:username::"
        },
        {
          name      = "DB_PASSWORD"
          valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:password::"
        }
      ]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.ecs.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "ecs"
        }
      }

      # DockerfileのHEALTHCHECKがあるため、ここでの定義は省略
      # 本番環境でより厳密な制御が必要な場合のみ追加
    }
  ])

  tags = {
    Name = "${var.project_name}-${var.environment}-app-task"
  }
}

healthCheckを省略する理由

DockerfileにHEALTHCHECKが定義されているため、Task Definitionでの重複定義は不要です。これにより、ヘルスチェックのロジックが一元管理され、メンテナンス性が向上します。

ECS Service

タスクの実行とALBとの連携を管理するServiceを作成します。

# terraform/ecs_service.tf

# ECS Service
resource "aws_ecs_service" "app" {
  name            = "${var.project_name}-${var.environment}-app-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = var.app_count
  launch_type     = "FARGATE"

  network_configuration {
    security_groups  = [aws_security_group.ecs.id]
    subnets          = aws_subnet.private[*].id
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.ecs.arn
    container_name   = "${var.project_name}-${var.environment}-app"
    container_port   = 3000
  }

  deployment_maximum_percent         = 200
  deployment_minimum_healthy_percent = 100

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }

  lifecycle {
    ignore_changes = [task_definition]
  }

  depends_on = [
    aws_lb_listener.http,
    aws_iam_role_policy.ecs_task_role_policy
  ]

  tags = {
    Name = "${var.project_name}-${var.environment}-app-service"
  }
}

deployment_circuit_breakerの役割

デプロイに失敗した際、自動でロールバックする機能です。新しいタスクが正常に起動しない場合、自動的にデプロイ前の安定したバージョンに戻します。

NAT Gateway 再登場

現状、NAT Gatewayが無効化されているため、プライベートサブネットのECSタスクがAWSサービスに接続できないので、覚悟を決めて有効化します。

# terraform/terraform.tfvars
enable_nat_gateway = true

※ 学習中でもこれだけで月額コスト約$45発生するため、コストが気になる場合は検証が済んだらすぐにリソースを消しましょう

IAM権限の追加

ECS Execution RoleからSecrets Managerへのアクセスを可能にするように、IAM権限を追加していきます。

# terraform/ecs.tf

# ECS Execution Role用のカスタムポリシー
resource "aws_iam_role_policy" "ecs_execution_role_policy" {
  name = "${var.project_name}-${var.environment}-ecs-execution-policy"
  role = aws_iam_role.ecs_execution_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = aws_secretsmanager_secret.db_credentials.arn
      }
    ]
  })
}

出力の追加

作成したリソースの情報を出力に追加します。

# terraform/outputs.tf に追加

# ECS Service関連の出力
output "ecs_service_name" {
  description = "ECS Service名"
  value       = aws_ecs_service.app.name
}

output "ecs_task_definition_arn" {
  description = "Task Definition ARN"
  value       = aws_ecs_task_definition.app.arn
}

output "ecs_task_definition_family" {
  description = "Task Definition Family"
  value       = aws_ecs_task_definition.app.family
}

リソースを作成

Terraformコンテナ内で以下のコマンドを実行します。

# terraformディレクトリへ移動
cd terraform

# 実行計画の確認
terraform plan

# リソースの作成
terraform apply

この時点では、ECRにコンテナイメージが存在しないため、ECS Serviceはタスクを正常に起動できません。これは想定通りの動作です。

ECRへのイメージプッシュ

ECRリポジトリURLの確認

Terraformコンテナ内でECRリポジトリのURLを確認します。

# ECRリポジトリURLの確認
terraform output ecr_repository_url

出力例

123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-project-dev-app

イメージのビルドとプッシュ

別ターミナル(ローカル環境)で作業します。

AWS Management ConsoleのECRサービスから、作成したリポジトリを選択し、「プッシュコマンドの表示」をクリックします。表示された手順に従って、以下の作業を行います。

  1. ECRへのログイン
  2. Dockerイメージのビルド
  3. イメージのタグ付け
  4. ECRへのプッシュ

APIリポジトリ(aws-hands-on)のbackendディレクトリで作業することを前提としていますが、コンテナ化の知識があれば独自の実装を利用しても問題ありません。(Terraformリポジトリとは別のリポジトリです)

プッシュ完了後、ECRでイメージが登録されていることを確認します。

ISSUE - 課題

Docker ビルドエラー

  • docker build実行時に「buildx プラグインが見つからない」エラー
  • レガシービルダー使用の警告メッセージ
  • パッケージ依存関係エラーでビルド失敗
CAUSE - 原因

Dockerのプラグインファイルが破損している可能性がある

SOLUTION - 解決策

レガシービルダーを強制使用する

DOCKER_BUILDKIT=0 docker build -t xxx-dev-app .

# Apple Silicon の場合
DOCKER_BUILDKIT=0 docker build --platform linux/amd64 -t xxx-dev-app .

ECS Serviceの再起動

イメージをプッシュした後、ECS Serviceを更新してタスクを起動します。

# Terraformコンテナ内で実行
cd terraform

# サービスの強制更新(新しいタスクを起動)
aws ecs update-service \
  --cluster $(terraform output -raw ecs_cluster_id) \
  --service $(terraform output -raw ecs_service_name) \
  --force-new-deployment

# デプロイ状況の確認
aws ecs describe-services \
  --cluster $(terraform output -raw ecs_cluster_id) \
  --services $(terraform output -raw ecs_service_name) \
  --query 'services[0].{Status:status,RunningCount:runningCount,DesiredCount:desiredCount}' \
  --output table

3〜5分程度でタスクが起動し、RunningCount2になればデプロイ完了です。

動作確認

ALB経由でのアクセス確認

ALBのDNS名を取得し、APIにアクセスします。

# ALB DNS名の取得
ALB_DNS=$(terraform output -raw alb_dns_name)

# ルートエンドポイントへのアクセス
curl http://$ALB_DNS/

期待されるレスポンス

{
  "message":"Hono CRUD API Server",
  "version":"1.0.0",
  "environment":"production",
  "endpoints": {
    "health":"/health",
    "users":"/api/users"
  }
}

ヘルスチェックの確認

# ヘルスチェックエンドポイント
curl http://$ALB_DNS/health

期待されるレスポンス

{
  "status":"healthy",
  "timestamp":"2025-07-28T16:19:23.881Z"
}

CRUD操作の確認

ユーザー管理APIの動作を確認します。

# ユーザー作成
curl -X POST http://$ALB_DNS/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test User",
    "email": "[email protected]"
  }'

# ユーザー一覧取得
curl http://$ALB_DNS/api/users

# 特定ユーザー取得(IDは作成時のレスポンスから取得)
curl http://$ALB_DNS/api/users/1

CloudWatch Logsの確認

アプリケーションログが正しく出力されていることを確認します。

# ログストリームの一覧
aws logs describe-log-streams \
  --log-group-name $(terraform output -raw cloudwatch_log_group_name) \
  --order-by LastEventTime \
  --descending \
  --query 'logStreams[0:3].logStreamName' \
  --output table

# 最新ログの確認(Ctrl+Cで停止)
aws logs tail $(terraform output -raw cloudwatch_log_group_name) \
  --follow \
  --format short

トラブルシューティング

タスクが起動しない場合

タスクの停止理由を確認

# 停止したタスクの理由確認
aws ecs describe-tasks \
  --cluster $(terraform output -raw ecs_cluster_id) \
  --tasks $(aws ecs list-tasks \
    --cluster $(terraform output -raw ecs_cluster_id) \
    --desired-status STOPPED \
    --query 'taskArns[0]' \
    --output text) \
  --query 'tasks[0].{StopCode:stopCode,StoppedReason:stoppedReason}' \
  --output table

よくある原因

  • ECRイメージが存在しない(プッシュ忘れ)
  • Secrets Managerへのアクセス権限不足
  • ECRイメージのpull権限不足
  • メモリ不足(タスクのメモリ設定を増やす)

ヘルスチェックが失敗する場合

Target Groupのヘルスステータスを確認

# ターゲットヘルスの確認
aws elbv2 describe-target-health \
  --target-group-arn $(terraform output -raw target_group_arn) \
  --query 'TargetHealthDescriptions[*].{Target:Target.Id,Health:TargetHealth.State,Description:TargetHealth.Description}' \
  --output table

その他

タスクは起動するもののDB接続がうまくいかない場合はコンテナ側の問題説がある。
今回、サンプルコードではDrizzleを使用していましたが、drizzle.config.ts を認識しない問題やRDS用の設定などの考慮漏れが発生し少しハマりました。

そんな時はコンテナのbuild -> push -> 「ECS Serviceの再起動」セクションに立ち返って確認しましょう。

次のステップ

次回は、VPCエンドポイントを設定することで、NAT Gatewayを使用せずに完全なプライベート通信を実現します。

重要:次回の作業前に、コスト削減のため一度 terraform destroy でリソースを削除してから再構築します。

  • ECR、CloudWatch Logs、S3のVPCエンドポイント作成
  • NAT Gatewayを使用しないプライベート通信
  • VPC Flow Logsによる通信の可視化
  • ネットワークコストの最適化

検索

検索条件に一致する記事が見つかりませんでした