ECS Express Modeで未対応のARM64 / FARGATE_SPOT / ECS Execを利用してみた
ECS Express Modeは最小限の設定で迅速に環境を構築できる便利な機能ですが、ARM64(Graviton)やFargate Spot、ECS Execなどの機能を、作成時に設定して利用することができません。
今回、ECS Express Modeを採用した開発環境で、コスト改善やデバッグ効率を向上させるため、これらの機能の有効化を試みる機会がありましたので、その手順を紹介します。
前提条件
- ARM64(aarch64)アーキテクチャでビルドした本番用コンテナイメージが、ECRに登録済みであること
- ECS Express Mode の初期セットアップ用スタック(TaskExecutionRole、InfrastructureRole等)がデプロイ済みであること
課題:Express Modeの制約
ECS Express Mode(AWS::ECS::ExpressGatewayService)は、タスク定義・サービス・ALB・Auto Scalingなどの低レベルリソースを内部で自動生成・管理する高レベルコンポーネントです。そのため、通常のCloudFormationで指定できる runtimePlatform や capacityProviderStrategy といったプロパティが、Express Modeのテンプレートには存在しません。
アーキテクチャは x86_64 に固定、update-express-gateway-service APIを通じても変更できません。
この制約を回避するため、以下の2段階デプロイを実施しました。
- ダミーデプロイ: 本番と同一ポート・パスを持つx86ダミーイメージでスタックを作成(タスク数0で起動を抑制)。
- アーキテクチャ転換:自動生成されたタスク定義をCLIでARM64に編集し、
update-serviceで適用。
手順1:CloudFormationによる初期スタック作成
まずはx86のダミーイメージを使用して、Express Modeの管理下にあるリソースを正常にデプロイしました。
検証コード(CloudFormationテンプレート)
- ECS Execを利用するため、SSM用のIAMは初期テンプレートの設置時点で設定しました。
MinTaskCount: 0を指定し、ダミーイメージでのタスク起動を防止しています。MinTaskCount: 1にすると、ダミーイメージがヘルスチェックに失敗した場合にサーキットブレーカーが発動し、ロールバックループに陥る可能性があります。
初期テンプレート全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Express Mode - 初回設置用(x86ダミー)'
Parameters:
ServiceName:
Type: String
Default: 'my-app-dev'
InitialStackName:
Type: String
Default: 'ecs-express-initial'
Resources:
ECSLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/ecs/default/${ServiceName}-service'
RetentionInDays: 14
TaskRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ServiceName}-task-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: ECSExecPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: '*'
ExpressModeService:
Type: AWS::ECS::ExpressGatewayService
DependsOn: ECSLogGroup
Properties:
ServiceName: !Sub '${ServiceName}-service'
Cluster: 'default'
ExecutionRoleArn:
Fn::ImportValue: !Sub '${InitialStackName}-TaskExecutionRoleArn'
InfrastructureRoleArn:
Fn::ImportValue: !Sub '${InitialStackName}-InfrastructureRoleArn'
TaskRoleArn: !GetAtt TaskRole.Arn
PrimaryContainer:
Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-app:dummy'
ContainerPort: 8000
AwsLogsConfiguration:
LogGroup: !Sub '/aws/ecs/default/${ServiceName}-service'
LogStreamPrefix: 'ecs'
NetworkConfiguration:
Subnets:
Fn::Split:
- ','
- Fn::ImportValue: !Sub '${InitialStackName}-PrivateSubnets'
Cpu: '256'
Memory: '512'
HealthCheckPath: '/health'
ScalingTarget:
MinTaskCount: 0
MaxTaskCount: 1
AutoScalingMetric: 'AVERAGE_CPU'
AutoScalingTargetValue: 70
Outputs:
ServiceEndpoint:
Value: !GetAtt ExpressModeService.Endpoint
このテンプレートをデプロイし、タスク定義の runtimePlatform がデフォルトの x86_64 で生成されていることを確認しました。MinTaskCount: 0 のため、この時点ではタスクは起動しません。
手順1.5:ベイクタイムの無効化
変換前に作業時間を短縮するため、マネジメントコンソールでデプロイのベイクタイムを0に設定しました。
- ECS → クラスター → サービス → デプロイ設定 → ベイクタイム: 0分
手順2:タスク定義のARM64化と適用
Express Modeが自動生成したタスク定義リビジョンを取得し、cpuArchitecture を ARM64 に、イメージを本番用ARMイメージに置換、あわせてCPU・メモリを本番スペックに変更して登録しました。
SERVICE_NAME="my-app-dev-service"
TD_FAMILY="default-${SERVICE_NAME}"
REGION="ap-northeast-1"
ARM_IMAGE="123456789012.dkr.ecr.$REGION.amazonaws.com/my-app:latest"
# 1. 現在のタスク定義を取得
LATEST=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
--region $REGION --query 'taskDefinitionArns[0]' --output text)
aws ecs describe-task-definition --task-definition $LATEST \
--region $REGION --query 'taskDefinition' > /tmp/td.json
# 2. ARM64 + 本番イメージに変更(メタデータの削除を含む)
jq --arg img "$ARM_IMAGE" '
.runtimePlatform.cpuArchitecture = "ARM64" |
.containerDefinitions[0].image = $img |
.cpu = "512" | .memory = "1024" |
.containerDefinitions[0].cpu = 512 |
.containerDefinitions[0].memoryReservation = 1024 |
del(.taskDefinitionArn, .revision, .status, .requiresAttributes,
.compatibilities, .registeredAt, .registeredBy, .enableFaultInjection)
' /tmp/td.json > /tmp/td_arm.json
# 3. 新リビジョンを登録
aws ecs register-task-definition \
--cli-input-json file:///tmp/td_arm.json \
--region $REGION
# 4. サービスを更新
NEW_REV=$(aws ecs list-task-definitions --family-prefix $TD_FAMILY --sort DESC \
--region $REGION --query 'taskDefinitionArns[0]' --output text)
aws ecs update-service \
--cluster default \
--service $SERVICE_NAME \
--task-definition $NEW_REV \
--force-new-deployment \
--region $REGION
update-stack 実行時の挙動確認
CLIでARM64へ切り替えた後、CloudFormationの update-stack を実行し、テンプレート内のイメージをARM64用の本番イメージに変更しました。これにより、以降のCloudFormation操作でロールバックが発生した場合でも、テンプレートが参照するイメージがARM64用となるため、exec format error のリスクを軽減できます。
update-stackの実行により、新しいタスク定義リビジョンが生成されました。- 新しいリビジョンにおいても、
runtimePlatform内のcpuArchitectureがARM64として維持されていることを確認しました。 - テンプレートで指定したイメージへの更新が、新リビジョンに反映されていることを確認しました。
Express Modeのスタック更新時、現在稼働中のタスク定義からアーキテクチャ設定を引き継ぐ挙動を確認できました。
手順3:FARGATE_SPOTとECS Execの有効化
ARM64への転換後、さらにコスト最適化とデバッグ機能を有効化しました。
aws ecs update-service \
--cluster default \
--service my-app-dev-service \
--capacity-provider-strategy capacityProvider=FARGATE_SPOT,base=0,weight=1 \
--enable-execute-command \
--force-new-deployment \
--region ap-northeast-1
確認
設定反映後、タスクを起動して各設定を確認しました。
aws ecs update-service \
--cluster default \
--service my-app-dev-service \
--desired-count 1 \
--region ap-northeast-1
タスク起動後、FARGATE_SPOTで動作していることを確認しました。
aws ecs describe-tasks \
--cluster default \
--tasks <task-id> \
--query 'tasks[0].capacityProviderName' \
--region ap-northeast-1
# => "FARGATE_SPOT"
ECS Execでコンテナにログインし、ARM64で動作していることを確認しました。Express Modeではコンテナ名が自動的に Main に設定されるため、--container Main を指定しています。
aws ecs execute-command \
--cluster default \
--task <task-id> \
--container Main \
--interactive \
--command "/bin/sh"
# コンテナ内で実行
$ uname -m
aarch64
aarch64 と表示され、ARM64(Graviton)で動作していることを確認できました。
まとめ
ECS Express Modeにおいても、2段階のデプロイプロセスを経ることでARM64、Fargate Spot、ECS Execを利用することができました。初期スタックでは MinTaskCount: 0 を設定してダミーイメージのタスク起動を防止し、ARM64変換後に必要なタスク数に変更するのが安全なデプロイパターンです。
ただし現時点で公式ドキュメントに明記されていない手順となるため、今後のアップデートにより挙動が変化したり、IaC外の変更による干渉や、ドリフトなどの問題が生じる可能性があります。開発、検証環境など自己責任で利用できる範囲に留めた利用をおすすめします。






