2021年3月21日日曜日

AmazonEcs ExecでFargateコンテナに入る

 

ECSのFargateコンテナに入ることができるようになりました。

詳しくは、下記のAWSブログを参照。


CloudFormationでECSを構築して試してみました。


環境


cfnテンプレート


  • VPC、ECS、Fargateを構築します。
  • AWS::ECS::ServiceのEnableExecuteCommandを trueにすると Fargateに入れるようになります。
  • 今回は nginx コンテナを1つ起動します。

AWSTemplateFormatVersion: "2010-09-09"

Description: VPC,ECS

Parameters:
  VpcCidr:
    Description: VPC CIDR
    Type: String
    Default: 10.0.0.0/16

  SubnetPublicCidr:
    Description: Subnet CIDR. (public)
    Type: String
    Default: 10.0.0.0/24

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Sub ${VpcCidr}
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC

  SubnetPublic:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: !Sub ${SubnetPublicCidr}
      MapPublicIpOnLaunch: true
      AvailabilityZone: !Select
        - 0
        - !GetAZs ""

  SubnetPublicIn1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetPublic
      RouteTableId: !Ref RouteTablePublic

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref InternetGateway

  RouteIgw:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTablePublic
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${AWS::StackName}
      CapacityProviders:
        - FARGATE_SPOT
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE_SPOT
          Weight: 1

  EcsSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ECS Security Group
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - CidrIp: 10.0.0.0/16
          FromPort: -1
          IpProtocol: "-1"
          ToPort: -1

  EcsService:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: !Sub ${AWS::StackName}
      Cluster: !Ref EcsCluster
      DesiredCount: 1
      EnableExecuteCommand: true
      TaskDefinition: !Ref EcsTaskDefinition
      DeploymentController:
        Type: ECS
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref EcsSecurityGroup
          Subnets:
            - !Ref SubnetPublic

  EcsTaskCloudwatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "${AWS::StackName}"
      RetentionInDays: 1

  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-task
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ecs-tasks.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
      Policies:
        - PolicyName: ssm
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ssmmessages:CreateControlChannel
                  - ssmmessages:CreateDataChannel
                  - ssmmessages:OpenControlChannel
                  - ssmmessages:OpenDataChannel
                Resource:
                  - '*'

  ECSTaskExecRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-taskexec
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ecs-tasks.amazonaws.com]
            Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  EcsTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub "${AWS::StackName}"
      TaskRoleArn: !GetAtt ECSTaskRole.Arn
      ExecutionRoleArn: !GetAtt ECSTaskExecRole.Arn
      Cpu: "256"
      Memory: "512"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: nginx
          Essential: true
          Image: nginx
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref EcsTaskCloudwatchLogsGroup
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: nginx
          PortMappings:
            - HostPort: 80
              ContainerPort: 80


Makefile


  • 検証で使用するコマンドをMakefileに記述します。
  • 下記サンプルではTABがスペース8個に置き換わっているので注意。(そのままコピペしても動きません。)
  • cfn-python-lintについては下記URL参照
    https://github.com/aws-cloudformation/cfn-python-lint

#-----------------------------------------------------
# valiables
#-----------------------------------------------------
AWSPROFILE := blue21

#-----------------------------------------------------
# validate cfn template
#-----------------------------------------------------

all: lint
lint:
        docker run --rm -v `pwd`:/data cfn-python-lint /data/cfn_*.yml

#-----------------------------------------------------
# deploy
#-----------------------------------------------------

deploy:
        $(eval STACK_NAME := Blue21EcsPoC)
        rain deploy cfn_ecs.yml $(STACK_NAME) $(RAINOPT) \
        --profile $(AWSPROFILE) \
        --params \
        VpcCidr=10.0.0.0/16,\
        SubnetPublicCidr=10.0.0.0/24

#-----------------------------------------------------
# stack
#-----------------------------------------------------

# LIST Stack
stackls:
        aws cloudformation describe-stacks \
        --profile $(AWSPROFILE) \
        --query 'Stacks[?contains(StackName,`Blue21`)].{StackName:StackName,StackStatus:StackStatus,Desc:Description}' \
        --output table

# DELETE Stack
stackrm:
        rain rm $(STACK_NAME) \
        --profile $(AWSPROFILE)

#-----------------------------------------------------
# util
#-----------------------------------------------------

update_svc:
        $(eval CLUSTER_NAME := Blue21EcsPoC)
        $(eval SERVICE_NAME := Blue21EcsPoC)
        aws ecs update-service --cluster $(CLUSTER_NAME) --service $(SERVICE_NAME) --enable-execute-command --profile $(AWSPROFILE)

list_task:
        $(eval CLUSTER_NAME := Blue21EcsPoC)
        $(eval SERVICE_NAME := Blue21EcsPoC)
        aws ecs list-tasks --cluster $(CLUSTER_NAME) --service-name $(SERVICE_NAME) --profile $(AWSPROFILE)

describe_task:
        $(eval CLUSTER_NAME := Blue21EcsPoC)
        aws ecs describe-tasks --cluster $(CLUSTER_NAME) --tasks $(TASK) --profile $(AWSPROFILE)

exec_task:
        $(eval CLUSTER_NAME := Blue21EcsPoC)
        aws ecs execute-command --cluster $(CLUSTER_NAME) --task $(TASK) --container nginx --command "/bin/bash" --interactive --profile $(AWSPROFILE)


ファイルの配置


cfnテンプレート(cfn_ecs.yml)とMakefileは、下記のようにファイルを配置します。

$ tree .
.
├── Makefile
└── cfn_ecs.yml

0 directories, 2 files


動作確認

 

cfn-python-lintでcfnテンプレートをテストします。

下記のようにcfn-python-lint が、まだ、EnableExecuteCommandに対応してないようなのでエラーになりますが、無視します。

$ make lint
docker run --rm -v `pwd`:/data cfn-python-lint /data/cfn_*.yml
E3002 Invalid Property Resources/EcsService/Properties/EnableExecuteCommand
/data/cfn_ecs.yml:88:7

make: *** [Makefile:22: lint] Error 2


Rainでスタックを作成します。

$ make deploy

下図は実行例です。


スタックを確認します。

$ make stackls
下図は実行例です。

タスク一覧を表示します。
arn の最後の部分(3d5562a63bb94749a67048002f2995bf)を、あとで、使用します。
$ make list_task
aws ecs list-tasks --cluster Blue21EcsPoC --service-name Blue21EcsPoC --profile blue21
{
    "taskArns": [
        "arn:aws:ecs:us-east-1:0123456789:task/Blue21EcsPoC/3d5562a63bb94749a67048002f2995bf"
    ]
}

タスクの詳細を表示します。
platformVersionが1.4.0で、enableExecuteCommand が true であれば、Fargateに入れます。

 make describe_task TASK=3d5562a63bb94749a67048002f2995bf
aws ecs describe-tasks --cluster Blue21EcsPoC --tasks 3d5562a63bb94749a67048002f2995bf --profile blue21
{
    "tasks": [
        {
            "attachments": [
                {
~省略~
            "desiredStatus": "RUNNING",
            "enableExecuteCommand": true,
            "group": "service:Blue21EcsPoC",
~省略~
            "platformVersion": "1.4.0",
~省略~

Fargateタスクに入ります。

$ make exec_task TASK=3d5562a63bb94749a67048002f2995bf

下図は実行例です


Fargateコンテナで実行したコマンドはCloudWatchLogsに記録されます。




あとかたずけ


CloudFormationで構築した環境を削除します。

$ make stackrm STACK_NAME=Blue21EcsPoC

下図は実行例です。