Amazon ECS Execを使って、プライベートサブネット内のECS(Fargate)コンテナにアクセスする
Amazon ECS Execを使用すると、稼働中のECSコンテナにアクセスすることができます。
Amazon ECS Exec を使用すれば、最初にホストコンテナのオペレーティングシステムとやり取りしたり、インバウンドポートを開いたり、SSH キーを管理したりすることなく、コンテナと直接やり取りできます。ECS Exec を使用して、Amazon EC2 インスタンスまたは AWS Fargate で実行されているコンテナでコマンドを実行したり、シェルを取得したりできます。これにより、診断情報を収集し、エラーを迅速にトラブルシューティングすることが容易になります。
デバッグ用にAmazon ECS Exec を使用
本ページでは、プライベートサブネット内のECS(Fargate)コンテナにアクセスする方法を確認します。
構築する環境
2つのプライベートサブネット内にそれぞれFargateタイプのECSサービス・タスクを定義します。
ECS Execを使用して、それぞれのコンテナにアクセスします。
各コンテナには、以下の経路でアクセスします。
- NATゲートウェイ
- VPCエンドポイント
ECRリポジトリ(S3)についても同様の経路でアクセスします。
今回使用するDockerイメージをCodeBuildを使ってビルドします。
ビルドする際に、Secrets Managerに保存されたDockerHubのアカウント情報を参照します。
CloudFormationカスタムリソースに関連づけたLambda関数を作成します。
この関数の働きは、CodeBuildによるビルドを開始することです。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/127
テンプレートファイルのポイント解説
ECS Execを使用するためのネットワーク設定
NATゲートウェイ
Resources:
IGW:
Type: AWS::EC2::InternetGateway
IGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NATGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP.AllocationId
SubnetId: !Ref PublicSubnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref CidrIp1
VpcId: !Ref VPC
AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"
ContainerSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref CidrIp2
VpcId: !Ref VPC
AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
ContainerRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
RouteToInternet:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
RouteToNATGateway:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref ContainerRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NATGateway
PublicRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
ContainerRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ContainerSubnet1
RouteTableId: !Ref ContainerRouteTable1
Code language: YAML (yaml)
パブリックサブネット上にNATゲートウェイを作成します。
プライベートサブネット上のインスタンスが、NATゲートウェイを経由してECS Exec(System Manager)と通信できるように、ルートテーブルを以下の通りに設定します。
- プライベートサブネット用のルートテーブル:NATゲートウェイ向けにデフォルトルートを設定
- パブリックサブネット用のルートテーブル:インターネットゲートウェイ向けにデフォルトルートを設定
VPCエンドポイント
Resources:
ContainerRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
ContainerRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ContainerSubnet2
RouteTableId: !Ref ContainerRouteTable2
ContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${Prefix}-ContainerSecurityGroup"
GroupDescription: Deny all.
VpcId: !Ref VPC
EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${Prefix}-EndpointSecurityGroup"
GroupDescription: Allow HTTPS from ContainerSecurityGroup.
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref HTTPSPort
ToPort: !Ref HTTPSPort
SourceSecurityGroupId: !Ref ContainerSecurityGroup
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref ContainerRouteTable2
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcId: !Ref VPC
SSMMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
SubnetIds:
- !Ref ContainerSubnet2
VpcEndpointType: Interface
VpcId: !Ref VPC
ECRDkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
SubnetIds:
- !Ref ContainerSubnet2
VpcEndpointType: Interface
VpcId: !Ref VPC
ECRApiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.api"
SubnetIds:
- !Ref ContainerSubnet2
VpcEndpointType: Interface
VpcId: !Ref VPC
Code language: YAML (yaml)
2つの用途で合計4つのVPCエンドポイントを作成します。
- ECS Execを使用してイメージにアクセスするため
- ECRリポジトリからイメージをプルするため
なおECS Exec用のVPCエンドポイントですが、以下の通りに言及されています。
ECS Exec 機能を使用する場合は、Systems Manager セッションマネージャーのインターフェイス VPC エンドポイントを作成する必要があります。
ECS Exec 機能を使用する場合は、Systems Manager Session Manager VPC エンドポイントを作成します
上記に従い、ssmmessages用のVPCエンドポイントを定義します。
Fargate
本ページでは、ECS Execを使用して、プライベートサブネット内のFargateコンテナにアクセスする方法を中心に取り上げます。
プライベートサブネットでFargateを展開する方法については、以下のページをご確認ください。
タスク用IAMロール
Resources:
TaskRole:
Type: AWS::IAM::Role
Properties:
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: "*"
Code language: YAML (yaml)
ECS Execを使用するためには、タスク用のIAMロールに関して要件があります。
ECS Exec 機能には、マネージド型 SSM エージェント (execute-command エージェント) と SSM サービス間の通信に必要なアクセス許可をコンテナに付与するためのタスク IAM ロール ロールが必要です。
ECS Exec の使用
引用元の公式ページを参照して、4つのアクションを許可します。
タスク定義
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Cpu: !Ref TaskCpu
ContainerDefinitions:
- Name: !Sub "${Prefix}-amazon-linux"
Command:
- "/bin/sh"
- "-c"
- "sleep 3600"
Essential: true
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName}:latest"
LinuxParameters:
InitProcessEnabled: true
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Sub "${Prefix}-amazon-linux"
ExecutionRoleArn: !Ref FargateTaskExecutionRole
Memory: !Ref TaskMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
RuntimePlatform:
CpuArchitecture: ARM64
OperatingSystemFamily: LINUX
TaskRoleArn: !Ref TaskRole
Code language: YAML (yaml)
こちらも以下のページを参考に設定しました。
ポイントはLinuxParametersプロパティ内のInitProcessEnabledプロパティです。
本プロパティに「true」を指定すると、以下に引用する効果が得られます。
タスク定義パラメーター initProcessEnabled を true に設定すると、コンテナ内で init プロセスが開始され、見つかったゾンビ SSM Agent の子プロセスがすべて削除されます。
ECS Exec の使用
サービス
Resources:
Service1:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
LaunchType: FARGATE
DesiredCount: 1
EnableExecuteCommand: true
TaskDefinition: !Ref TaskDefinition
ServiceName: !Sub "${Prefix}-service1"
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref ContainerSecurityGroup
Subnets:
- !Ref ContainerSubnet1
Service2:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
LaunchType: FARGATE
DesiredCount: 1
EnableExecuteCommand: true
TaskDefinition: !Ref TaskDefinition
ServiceName: !Sub "${Prefix}-service2"
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref ContainerSecurityGroup
Subnets:
- !Ref ContainerSubnet2
Code language: YAML (yaml)
2つのサービスを定義します。
設定そのものは全く同じですが、配置先のサブネットが異なります。
それぞれNATゲートウェイ経由、VPCエンドポイント経由で通信することになります。
ポイントはEnableExecuteCommandプロパティです。
本プロパティに「true」を指定することで、サービスおよびタスクでECS Execが有効化されます。
(参考)CFNカスタムリソースとCodeBuildを使って、テストイメージをECRに自動的にプッシュする
Resources:
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: NO_ARTIFACTS
Cache:
Type: NO_CACHE
Environment:
ComputeType: !Ref ProjectEnvironmentComputeType
EnvironmentVariables:
- Name: DOCKERHUB_PASSWORD
Type: SECRETS_MANAGER
Value: !Sub "${Secret}:password"
- Name: DOCKERHUB_USERNAME
Type: SECRETS_MANAGER
Value: !Sub "${Secret}:username"
Image: !Ref ProjectEnvironmentImage
ImagePullCredentialsType: CODEBUILD
Type: !Ref ProjectEnvironmentType
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
Status: DISABLED
S3Logs:
Status: DISABLED
Name: !Ref Prefix
ServiceRole: !GetAtt CodeBuildRole.Arn
Source:
Type: NO_SOURCE
BuildSpec: !Sub |
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com
- REPOSITORY_URI=${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName}
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${!COMMIT_HASH:=latest}
- echo Logging in to Docker Hub...
- echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
- |
cat << EOF > Dockerfile
FROM amazonlinux:latest
EXPOSE 80
EOF
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
Visibility: PRIVATE
Code language: YAML (yaml)
CodeBuildを使って、イメージをビルド後、コンテナのイメージをECRリポジトリに用意します。
今回は最新バージョンのAmazon Linuxをベースとします。
詳細につきましては、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- ECRリポジトリ:fa-127
- ECSクラスター:fa-127
- ECSサービス1:fa-127-service1
- ECSサービス2:fa-127-service2
AWS Management Consoleから各リソースを確認します。
ECRリポジトリを確認します。
正常にイメージがプッシュされています。
つまりCloudFormationカスタムリソースに関連づいたLambda関数によって、CodeBuildの開始がトリガーされました。
そしてイメージのビルドが行われた後に、こちらにプッシュされたということです。
ECSクラスターを確認します。
クラスター内に2つのサービスが動作していることがわかります。
それぞれサービスを確認します。
各サービスに1つずつタスクが作成されており、そのタスクにはコンテナが1つ起動しています。
情報を整理します。
- サービス1
- タスクID:a986b1c860bf4f8cb02740b037514351
- コンテナ名:fa-127-amazon-linux
- サービス2
- タスクID:b6f91fb2dc89471f811b052cca7be0ee
- コンテナ名:fa-127-amazon-linux
動作確認
NATゲートウェイ経由でコンテナにアクセス
準備が整いましたので、ECS Execを使用して、サービス1のコンテナにアクセスします。
アクセスはAWS CLIを使用します。
% aws ecs execute-command \
--cluster fa-127-cluster \
--task a986b1c860bf4f8cb02740b037514351 \
--container fa-127-amazon-linux \
--interactive --command "/bin/sh"
...
sh-5.2#
Code language: Bash (bash)
サービス1のコンテナにアクセスすることができました。
このようにNATゲートウェイ経由でも、ECS Execを使用して、コンテナにアクセすることができます。
VPCエンドポイント経由でコンテナにアクセス
同様にサービス2のコンテナにアクセスします。
% aws ecs execute-command \
--cluster fa-127-cluster \
--task b6f91fb2dc89471f811b052cca7be0ee \
--container fa-127-amazon-linux \
--interactive \
--command "/bin/sh"
...
sh-5.2#
Code language: Bash (bash)
サービス2のコンテナにもアクセスすることができました。
このようにVPCエンドポイント経由でも、ECS Execを使用して、コンテナにアクセすることができます。
まとめ
プライベートサブネット内のECS(Fargate)コンテナにアクセスする方法を確認します。
プライベートサブネット内のコンテナに対しては、NATゲートウェイおよびVPCエンドポイントを経由することでアクセス可能です。