ECS ExecによるプライベートVPCにおけるFargateのコンテナにアクセス

#概要
2021年3月AWS の公式ブログに、「NEW – Amazon ECSExecを使用してAWSFargateとAmazonEC2のコンテナにアクセスする」記事が発表された。これまでSSM（System Manager Session Manager）とコンテナー側のSSM エージェントのインストールの併用によるコンテナーへの
面倒なアクセス手順より、アクセス手順は大分簡略化された。本記事は、この新しいECS Exec機能を利用し、プライベートVPCにおけるFargateのコンテナーへのアクセス方法を紹介と検証する。

https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/

以下はこの方法の利点である。

１．	ECSのコンテナーにSSMエージェントのインストールは要らない。既存コンテナーにすぐアクセスできる。
２．	インターネットゲートウェイやNATなどを設置されていないプライベートVPCに格納されたコンテナーにもアクセスできる。
３．	Blue/Green CodeDeploy のECSサービスにも適用。
４．	VPCにcom.amazonaws.xxxxxxxx.ssmサービスのエンドポイント設定が必要である。
５．	AWS CLI、マネジメントコンソールやCloudFormationやどちらでも作ったECSコンテナーに適用する。

#機能概要と制限
Amazon ECS Execは、AWS Systems Manager（SSM）Session Managerを使用してコンテナとの接続を確立し、AWSIAMポリシーを使用してコマンドを実行するためのアクセス許可を制御する。
AmazonECSエージェントまたはAWSFargateエージェントは、SSMエージェントを指定されたコンテナーに配置する。これによりECS内部と外部を連携する。ただ、以下の制限がある。

1.	現時点ECS Execは、Linuxコンテナーでのみサポートする。
2.	ECS Execは現在、AWSマネジメントコンソールの使用を対応しない。
3.	ECS Execは現在、アジア太平洋（大阪）地域ではサポートされないようである。
4.	既存のタスクに対してはECS Execを有効にすることはできません。 新しいタスクにのみ有効

#事前環境準備
ECRリポジトリ
VPC
Subnet1a
Subnet1c
SecurityGroup

#検証方法
１．	AWS CLI とセッションマネージャープラグインを用意する。
対応するAWS CLIバージョン；
AWS CLI v1 1.19.28以降～最新版
AWS CLI v2 2.1.31以降
AWS CLI 用の Session Manager pluginは、以下の手順によりインストールする。

https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html

$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm"

インストール:
$ sudo yum install –y session-manager-plugin.rpm

インストール確認:
$ session-manager-plugin

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

２．	以下のssm-task-policyとこのポリシーを含むタスクロールecs-ssm-task-roleを作成する。

```ssm-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "ssmmessages:CreateControlChannel",
              "ssmmessages:CreateDataChannel",
              "ssmmessages:OpenControlChannel",
              "ssmmessages:OpenDataChannel"
          ],
          "Resource": "*"
      }
  ]
}
```
３．	ECSのサービスに対し、Amazon ECS ExecをEnableにする。

$ aws ecs update-service \
  --cluster your-cluster-name \
  --service your-service-name \
  --task-definition your-td-family-name:N \
  --force-new-deployment \
  --enable-execute-command

Blue/Green　Deployされたサービスには“--force-new-deployment”オプションを使えないことをご注意ください。例:

$ aws ecs update-service --service xxx-dev-xxxx-app-service --cluster xxx-dev-xxxx-app-fargate1 --enable-execute-command --force-new-deployment

出力に「”enableExecuteCOmmand”: true」あることをご確認ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/b90d1cad-8f32-e4d3-70ef-4a92d1bc458d.png)
 
４．	既存ECSタスクを停止させ、新しい起動されるタスクIdをメモする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/5989f235-9a2d-cef7-a13f-0bd1ed2b0860.png)

５．	既存ECSタスクを停止させ、新しい起動されるタスクIdをメモする。
６．	Fargate のコンテナにアクセスする。

$ aws ecs execute-command --cluster your-cluster-name \
    --task your-task-id \
    --container your-container-name \
    --interactive \
    --command "/bin/bash"

例:
$ aws ecs execute-command --cluster xxx-dev-xxxx-app-fargate1 --task xxxxxxxxxxxxxxxxxx --container xxx-dev-xxxx-app-container --interactive --command /bin/bash

コンテナーにアクセスされたら、AWS SSM GUIでセッションが接続されたこと確認もできる。

以上でプライベートVPCのFargateにあるコンテナーにログインすることができた。

#補足

1.	タスクのContainerDefinitionにLinuxParameterのInitProcessEbanbled: true
          LinuxParameters:  
          InitProcessEnabled: true

このパラメータは、コンテナ内でinitプロセスを実行し、ゾンビになる可能性のあるSSMエージェントとその子プロセスをクリアするものである、設定しなくてもコンテナーにアクセスすることができるが、SSMエー人とのゾンビプロセスが生じる可能性がある。

2.	タスクのContainerDefinitionのReadonlyRootFilesystem: false
     ReadonlyRootFilesystem: false
SSMエージェントでは、必要なディレクトリとファイルを作成するために、コンテナファイルシステムに書き込むことができる必要がある。 したがって、readonlyRootFilesystem: falseまたはその他の方法を使用してSSMエージェント用のファイルシステムを書き込む必要である。
ただ、タスク定義する時に、このパラメータを明確に設定していない場合はfalseになる。

“An error occurred (TargetNotConnectedException) when calling the ExecuteCommand operation: The execute command failed due to an internal error. Try again later.”のエラーが出るときに、このパラメータを”true”に設定しているかをご確認ください。

3.	Private VPCに格納するコンテナーをアクセスする際、ECSサービスを定義する際、VPCのデフォルトPermitAll SecurityGroupをインクルードする必要がある。
例：

SecurityGroups: 
VPC default SG, must included. - sg-xxxxxxxxxx
            - sg-xxxxxxxxxx
            - sg-xxxxxxxxxx


４.	VPCのエンドポイントについて
本検証では、com.amazonaws.xxxxxxxx.ssmmessage、com.amazonaws.xxxxxxxx.ec2サービスのエンドポイントが必須、その他の2つサービスのエンドポイントを設定しなくてもアクセスできることを確認された。
com.amazonaws.xxxxxxxx.ssmmessage (must)
com.amazonaws.xxxxxxxx.ec2 (must)
com.amazonaws.xxxxxxxx.ec2messages (optional)
com.amazonaws.xxxxxxxx.ssm (optional)
SSMエンドポイントの詳細は下記ご参照ください。

https://aws.amazon.com/premiumsupport/knowledge-center/ec2-systems-manager-vpc-endpoints/


#完成したECSとタスクのCloudFormation作成

今回の検証に、CloudFormationによる作成されたECSサービス、タスクのテンプレートを添付する。ご参照ください。

```ECS.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Xxxx - ECS Cluster Set up.

  #########################################################################################
  # パラメータセクション
  #########################################################################################

Parameters:

  ###################################################################
  # 共通パラメータ
  ###################################################################

  # プロジェクト定義
  HojokinService:
    Type: String
    Default: xxxx
  # 環境定義
  HojokinEnvironment:
    Type: String
    Default: dev
  # リージョン定義
  # ap-northeast-1(東京リージョン)
  AwsTokyoRegion:
    Type: String
    Default: xxxxxxxx
    # アベイラビリティゾーン(ap-northeast-1a)
  TokyoRegionAvailabilityZone1a:
    Type: String
    Default: xxxxxxxxa
  # アベイラビリティゾーン(ap-northeast-1c)
  TokyoRegionAvailabilityZone1c:
    Type: String
    Default: xxxxxxxxc

  EcsExecBucketName:
    Type: String
    Default: xxx-xxxx-dev-hook-s3-xxxxxxxx

  #########################################################################################
  # リソースセクション
  #########################################################################################

Resources: 

  #################################################
  # ECS Cluster作成(画面)
  #################################################

  XxxDevxxxxAppEcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: xxx-dev-xxxx-app-fargate1

  #################################################
  # ECS LogGroup作成
  #################################################

  XxxDevAppEcsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /ecs/logs/xxx-dev-xxxx-app-ecs-group

# IAM Role for TaskExecutionRole definition
  XxxDevXxxxEcsTaskExecIamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: xxx-dev-xxxx-ecs-taskexec-role
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        

  #################################################
  # ECS TaskDefinition作成
  #################################################

  XxxDevxxxxAppTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: 4096
#      ExecutionRoleArn: !Ref XxxDevxxxxEcsTaskExecIamRole
      ExecutionRoleArn: arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole
      Family: dev-xxxx-ecs-task
      Memory: 8192
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      # コンテナ設定
      ContainerDefinitions:
        - Name: xxx-dev-xxxx-app-container
          Image: xxxxxxxxxxxx.dkr.ecr.xxxxxxxx.amazonaws.com/xxx-dev-xxxx-jdk-ecr:latest
#          LinuxParameters: 
#            InitProcessEnabled: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              # awslogs-group: !ImportValue TestEcsLogGroup
              awslogs-group: /ecs/logs/xxx-dev-xxxx-app-ecs-group
              awslogs-region: !Ref AwsTokyoRegion
              awslogs-stream-prefix: xxxx-unk
#          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
#          ReadonlyRootFilesystem: false
      TaskRoleArn: arn:aws:iam::xxxxxxxxxxxx:role/ecs-ssm-task-role

  #################################################
  # ECS Service作成
  #################################################

  XxxDevxxxxAppEcsService:
    Type: AWS::ECS::Service
    Properties:
      # Cluster: !ImportValue TestEcsCluster
      Cluster: !Ref XxxDevxxxxAppEcsCluster
      DeploymentController: 
        Type: CODE_DEPLOY
#        Type: ECS
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        -
          # TargetGroupArn: arn:aws:elasticloadbalancing:xxxxxxxx2:xxxxxxxxxxxx:targetgroup/xxx-dev-xxxx-nlb-tg/xxxxxxxx
          TargetGroupArn: !ImportValue DevxxxxTargetGroupArn
          ContainerPort: 80
          ContainerName: xxx-dev-xxxx-app-container
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups: 
#            - !ImportValue DevxxxxAppSG
#            - !ImportValue DevxxxxPermitAllSG
            - sg-xxxxxxxx
          Subnets: 
            - !ImportValue DevxxxxAppSubnet1a
            - !ImportValue DevxxxxAppSubnet1c
      ServiceName: xxx-dev-xxxx-app-service
      TaskDefinition: !Ref XxxDevxxxxAppTask
    DependsOn:
      - XxxDevxxxxAppEcsCluster
      - XxxDevxxxxAppTask

Outputs:
  ECSAppClster1:
    Value: !Ref XxxDevxxxxAppEcsCluster
    Export:
      Name: DevxxxxEcsAppCluster1

  ECSAppTask1:
    Value: !Ref XxxDevxxxxAppTask
    Export:
      Name: DevxxxxEcsAppTask1

  ECSAppService1:
    Value: !Ref XxxDevxxxxAppEcsService
    Export:
      Name: DevxxxxEcsAppService1
```

#所感
今回、ECS Exec機能を使って、簡単且つ迅速にプライベートVPCのECS Fargateに格納されたコンテナーにログインすることができた。実際複雑な商用システムでも、このアクセス方法は充分活用できると思います。