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

下図は実行例です。






2021年3月12日金曜日

Vagrant上のDockerコンテナをホストオンリーネットワークに接続してみる


環境


  • Windows10
  • WSL2(ubuntu20.04)
  • Windows版VirtualBox6.1.18
  • Windows版vagrant2.2.14

Vagrant 起動


ubuntu20.04をvagrantで起動します。
Vagrantfile は下記のとおり。
VirtualBoxのホストオンリーネットワークが、192.168.56.0/24 であることが前提です。
仮想マシンには 192.168.56.10の固定IPアドレスを割り当てます。

Vagrant.configure("2") do |config|

  config.vm.box = "bento/ubuntu-20.04"
  config.vm.network "private_network", ip: "192.168.56.10", auto_config: false

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048"
    vb.name = 'ubuntu20'
    vb.cpus = 2
    vb.customize ['modifyvm', :id, '--nicpromisc2', 'allow-all']
  end

  # https://www.vagrantup.com/docs/synced-folders/nfs
  #config.vm.synced_folder ".", "/vagrant", type: "nfs"

  config.vm.provision "shell", inline: <<-SHELL
    apt update

    # japanese
    apt install language-pack-ja-base language-pack-ja -y
    localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"
    timedatectl set-timezone Asia/Tokyo

    # docker
    apt install apt-transport-https ca-certificates curl software-properties-common
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
    apt update
    apt install docker-ce -y
    usermod -aG docker vagrant
    curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose

    # https://qiita.com/sadapon2008/items/b5c83fedcb07de45f74e
    apt install bridge-utils
    ip addr flush dev eth1
    docker network create --driver bridge --subnet=192.168.56.0/24 --gateway=192.168.56.10 --opt "com.docker.network.bridge.name"="docker1" my_bridge
    mkdir -p /etc/systemd/system/docker.service.d
    echo '[Service]' > /etc/systemd/system/docker.service.d/override2.conf
    echo 'ExecStartPost=/bin/sh -c "/usr/bin/sleep 1s; /usr/sbin/ip addr flush dev eth1; /usr/sbin/brctl addif docker1 eth1; ip link set eth1 up"' >> /etc/systemd/system/docker.service.d/override2.conf
    echo 'ExecStopPost=/bin/sh -c "/usr/sbin/brctl delif docker1 eth1"' >> /etc/systemd/system/docker.service.d/override2.conf
    systemctl daemon-reload
    systemctl restart docker
    brctl show
    ip a | grep eth1 -A 5
 SHELL
end

"vagrant up"コマンドで起動します。
初回起動中にdocker をインストールしてネットワーク関連の設定を行います。
起動に成功すると、ネットワーク関連の情報が、下記のように表示されます。

    default: bridge name        bridge id               STP enabled     interfaces
    default: docker0            8000.024207e2ad64       no
    default: docker1            8000.024282583d53       no              eth1
    default: 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master docker1 state UP group default qlen 1000
    default:     link/ether 08:00:27:cd:6b:9f brd ff:ff:ff:ff:ff:ff
    default:     inet6 fe80::a00:27ff:fecd:6b9f/64 scope link tentative
    default:        valid_lft forever preferred_lft forever
    default: 4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    default:     link/ether 02:42:07:e2:ad:64 brd ff:ff:ff:ff:ff:ff

動作確認


"vagrant ssh"コマンドで仮想マシンに入ります。
下記の内容で、docker-compose.yml を作成します。
コンテナには、ホストオンリーネットワークの固定IPドレスを割り当てます。
ついでに、各コンテナの/etc/hosts にFQDNも設定します。

version: '3'
services:

  nginx00:
    image: nginx
    container_name: nginx00
    hostname: nginx00
    networks:
      default:
        ipv4_address: 192.168.56.11
    extra_hosts:
      - nginx00.local:192.168.56.11
      - nginx01.local:192.168.56.12

  nginx01:
    image: nginx
    container_name: nginx01
    hostname: nginx01
    networks:
      default:
        ipv4_address: 192.168.56.12
    extra_hosts:
      - nginx00.local:192.168.56.11
      - nginx01.local:192.168.56.12

networks:
  default:
    external:
      name: my_bridge

"docker-compose up -d"コマンドで起動します。
仮想マシン内でnginx00コンテナに固定IPでアクセスできるか確認してみます。
下図のように表示されたら成功です。


nginx01コンテナ内からnginx00コンテナにアクセスできるか確認してみます。
nginx00にアクセスするときは、docker-compose.ymlで定義したFQDN(nginx00.local)でアクセスできるか確認します。
下図のように表示されたら成功です。


最後にWSL2のターミナル内から仮想マシン上のDockerコンテナに固定IPでアクセスできるか確認します。
下図のように表示されたら成功です。


ちなみに、
Windowsのブラウザでも仮想マシン上のコンテナに固定IPでアクセスできます。



WSL2の /etc/hosts に nginx00.local, nginx01.local を設定すれば、WSL2からFQDNでアクセスできるようになります。