ECS Express Modeはインフラ構築をどこまで楽にしてくれるのか

はじめに

みなさんこんにちは。Platform 開発チーム SREでサブマネージャーの安達(@adachin0817)です。この記事は、ファインディエンジニア Advent Calendar 2025の18日目の記事です。今回はECS Express Modeをスピーディーに試してみたので、使ってみて分かったメリット・デメリットを中心にまとめていきたいと思います。

adventar.org

ECS Express Modeとは

従来のECSを用いたインフラ構築では、ECSクラスターやタスク定義、サービスだけでなく、ロードバランサー、ターゲットグループ、セキュリティグループ、オートスケーリングなど、多数のリソースを個別に定義・管理する手間がありました。

公式ドキュメントによるとECS Express ModeはAPIを通じて、インフラのセットアップを全て、自動化できるようになりました。これにより、アプリケーション開発に集中できる環境を実現し、Amazon ECSを含む各リソースを設定できるため、必須項目はコンテナイメージのみで、シンプルさとスピードを飛躍的に向上させています。

Terraformではaws_ecs_express_gateway_serviceリソースが提供されているため、こちらを使って一連の流れを実装していきます。

Terraform

terraform.tf

ECS Express Modeは、Terraform AWS Providerのバージョン6.23から利用できるようになります。

terraform {
  required_version = ">= 1.14.2"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.26.0"
    }

  }

  cloud {
    organization = "hoge"

    workspaces {
      name = "hoge-ecs"
    }
  }
}

iam.tf

ECS Express Modeでは、主に2つのIAM ロールが必要になります。Task Execution Roleは、コンテナイメージのpullやCloudWatch Logsへの書き込みに使用され、Infrastructure Roleは、ECS Express Modeが各種リソースを作成・管理するために使用されます。

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "test-service-task-execution-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ecs-tasks.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role" "ecs_infrastructure_role" {
  name = "test-service-infrastructure-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Sid       = "AllowAccessInfrastructureForECSExpressServices"
      Effect    = "Allow"
      Principal = { Service = "ecs.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "ecs_infrastructure_role_policy" {
  role       = aws_iam_role.ecs_infrastructure_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSInfrastructureRoleforExpressGatewayServices"
}

ecs.tf

ECS Express Modeでは、中心となるリソースであるaws_ecs_express_gateway_serviceを通じて各種設定を制御します。ECSクラスター自体は別途定義する必要がありますが、サービス側ではコンテナイメージ指定(今回はNginx)、ログ設定、事前に作成したIAM ロール、ネットワーク、CPU・メモリ、スケーリング設定などをまとめて指定します。

ロードバランサーについては ALBとターゲットグループが自動的に作成・管理され、ACM証明書も自動発行される仕様となっていました。また、セキュリティグループもExpress Mode側で自動生成・管理されるため、Terraform側では差分検知を防ぐために、ignore_changesを指定する必要がありました。

※ネットワークはデフォルトVPCを参照しています。

resource "aws_ecs_cluster" "main" {
  name = var.cluster_name
}

resource "aws_ecs_express_gateway_service" "main" {
  cluster      = var.cluster_name
  service_name = var.service_name

  primary_container {
    image          = var.container_image
    container_port = var.container_port
    command        = var.command

    aws_logs_configuration {
      log_group         = aws_cloudwatch_log_group.ecs_service.name
      log_stream_prefix = var.service_name
    }
  }

  execution_role_arn      = aws_iam_role.ecs_task_execution_role.arn
  infrastructure_role_arn = aws_iam_role.ecs_infrastructure_role.arn

  network_configuration {
    subnets         = toset(["subnet-xxxxxx", "subnet-xxxxxx"])
    security_groups = toset([aws_security_group.tests_service.id])
  }

  cpu    = tostring(var.cpu)
  memory = tostring(var.memory)

  scaling_target {
    min_task_count            = var.min_capacity
    max_task_count            = var.max_capacity
    auto_scaling_metric       = "AVERAGE_CPU"
    auto_scaling_target_value = 60
  }

  health_check_path = var.health_check_path

  lifecycle {
    ignore_changes = [
      network_configuration[0].security_groups
    ]
  }
}

variables.tf

variable "cluster_name" {
  type        = string
  description = "The name of the ECS cluster"
  default     = "hoge-cluster"
}

variable "service_name" {
  type        = string
  description = "The name of the service"
  default     = "hoge-service"
}

variable "log_group" {
  type        = string
  description = "The name of the CloudWatch log group"
  default     = "/ecs/hoge-service"
}

variable "container_image" {
  type        = string
  description = "The container image to use for the service"
  default     = "nginx:latest"
}

variable "container_port" {
  type        = number
  description = "The port that the container listens on"
  default     = 80
}

variable "command" {
  type        = list(string)
  description = "The command to run in the container"
  default     = ["nginx", "-g", "daemon off;"]
}

variable "cpu" {
  type        = number
  description = "The number of CPU units to allocate"
  default     = 256
}

variable "memory" {
  type        = number
  description = "The amount of memory (in MiB) to allocate"
  default     = 512
}

variable "min_capacity" {
  type        = number
  description = "Minimum number of tasks"
  default     = 1
}

variable "max_capacity" {
  type        = number
  description = "Maximum number of tasks"
  default     = 10
}

variable "health_check_path" {
  type        = string
  description = "The path for health checks"
  default     = "/"
}

Deploy

デプロイはECSビルトインデプロイであるカナリアデプロイとなっており、アプリケーションURLにHTTPSでアクセスできるようになりました。

❯❯ curl -I https://xxx.ecs.ap-northeast-1.on.aws
HTTP/2 200 
server: nginx
date: Tue, 16 Dec 2025 02:30:25 GMT
content-type: text/html; charset=utf-8
content-length: 14
x-powered-by: Express

削除の挙動と懸念点について

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/express-service-delete-task.html

  • The Amazon ECS cluster (if no other services are running)
  • The Amazon ECS service, task definition, and any running tasks
  • Service security group
  • CloudWatch log group
  • Metric alarm
  • ACM Certificate
  • The Application Load Balancer (if no other services are configured), target group, security group, listener, and listener rule
  • Amazon EC2 Auto Scaling policy, scalable target

削除の挙動については、Terraformでdestroyを実行すればExpress Mode管理下のリソースがすべて削除される想定でした。しかし、実際にはALBやターゲットグループ、ACMが残存し、その後に手動削除と再apply を行った結果、同一サービス名のECSサービスがDraining 状態のままとなり、再作成できない問題に直面しました。

╷
│ Error: creating ECS (Elastic Container) Express Gateway Service
│ 
│   with aws_ecs_express_gateway_service.main,
│   on ecs.tf line 5, in resource "aws_ecs_express_gateway_service" "main":
│    5: resource "aws_ecs_express_gateway_service" "main" {
│ 
│ ID: "hoge-service"
│ Cause: operation error ECS: CreateExpressGatewayService, ,
│ InvalidParameterException: Unable to Start a service that is still Draining."

クラスター名を変更して再度applyし直したところ、applyは完了するものの、ロードバランサーが作成されない状態となりました。その結果、存在しないセキュリティグループを参照したままサービスが起動できず、結果としてサービスがデプロイされないままタイムアウトしない事象が発生しました。この挙動から、Terraformでの削除・再作成は、現時点で課題が残っていると感じています。

まとめ

ECS Express Modeは、App Runnerと比べても、インフラ構築の複雑さを大きく減らし、アプリケーションエンジニアでも素早く環境を立ち上げ、開発に集中できる体験を提供してくれる仕組みだと感じました。一方で、Terraformを前提とした運用ではまだ課題が残る印象もありますが、Platform SRE チームがこれまで整備してきた汎用モジュールの一部を、将来的に置き換えられる可能性も感じています。

今後はAWSサポートとも連携しながら挙動の整理を進め、どのユースケースで安全に使えるのかを見極めつつ、実運用に耐えうる形へ落とし込んでいく予定です。

最後まで、読んでいただきありがとうございました!

herp.careers