LoginSignup
2
0

More than 3 years have passed since last update.

Amazon ECS Workshop #2 ~コンテナのデプロイ~

Last updated at Posted at 2020-08-23

環境構成図

本投稿では下図の構成図のコンポーネントを構築していきます。
環境構成図.png

タイトルにあるコンテナのデプロイに進む前の環境準備部分がかなり多くなりますが、ご了承ください。
※IAM周りの権限は適宜付与している前提で進めていきます。

コンテナ&ECSに関する各コンポーネントの簡単な説明は下記をご参照ください。
introduction編

環境準備

CloudFormation実行環境

クライアント環境.png

本投稿内で作成するリソースは基本的にCloudFormation(以下CFn)を利用して構築していきます。
CFnのテンプレートのコーディング環境として、クライアント端末に下記を準備します。
※テンプレートはコンソール操作からアップロードし、スタック作成しません。エディタにこだわりありません。

  • エディタ:Visual Studio Code
    拡張パッケージ:CloudFormation(コード補完), CloudFormation Linter(エラーチェック)

VPC/Subnet作成

VPC-Subnet作成.png

とりあえずのSubnetを作成します。本来であればLB-コンテナの構成を意識して、LB用のPublicなSubnetとコンテナ配置用のPrivateなSubnetを作るところですが、まずはコンテナ配置できたことをコンソール上から確認できることを目標に1Subnetの構成にしています。
構築用のCFnテンプレートは下記に貼っておきます。

VPC&Subnet構築テンプレート
vpc-create-cfn-template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  VPC and Subnet Create

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
      - Label:
          default: "Network Configuration"
        Parameters:
          - VPCCIDR
          - PublicSubnetACIDR
    ParameterLabels:
      VPCCIDR:
        default: "VPC CIDR"
      PublicSubnetACIDR:
        default: "PublicSubnetA CIDR"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  VPCCIDR:
    Type: String
    Default: "10.65.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.65.1.0/24"

Resources:
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

# InternetGateway Create
  InternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#         
# Public SubnetA Create
  PublicSubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-a"

# ------------------------------------------------------------#
#  RouteTable
# ------------------------------------------------------------#         
# Public RouteTableA Create
  PublicRouteTableA:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-a"

# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------#
# PublicRouteA Create
  PublicRouteA:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableA
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------#
# PublicRouteTable Associate SubnetA
  PublicSubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTableA

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "${PJPrefix}-vpc-cidr"

# Subnet
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a"

  PublicSubnetACIDR:
    Value: !Ref PublicSubnetACIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a-cidr"

# Route
  PublicRouteTableA:
    Value: !Ref PublicRouteTableA
    Export:
      Name: !Sub "${PJPrefix}-public-route-a"

ECR作成

ECR作成.png

こちらもとりあえずのイメージ格納用のレポジトリを作成します。
ライフサイクルポリシーなどは設定していないレポジトリです。

ECR構築テンプレート
ecr-create-cfn-template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create ECR

Resources:
# ------------------------------------------------------------#
#  ECR
# ------------------------------------------------------------#
# ECR Create
  MyRepository: 
    Type: AWS::ECR::Repository
    Properties: 
      RepositoryName: "myrepository"

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#               
Outputs:
# ECR
  MyRepository:
    Value: !Ref MyRepository
    Export:
      Name: !Sub "myrepository"

踏み台EC2作成

踏み台作成.png

ECRにイメージをpushするための踏み台Serverを構築します。
Userdataを利用してDockerのインストールまで済ませておきます。
構築用のテンプレートは下記の通りです。

踏み台EC2構築テンプレート
bastionec2-cfn-template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create Bastion-EC2

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix

      - Label:
          default: "EC2 Configuration"
        Parameters:
          - ImageId
          - InstanceTypeParameter
          - DiskSize
          - SSHLocation
          - KeyName
          - IAMRoleParameter

    ParameterLabels:
      ImageId:
        default: "AMI ID"
      InstanceTypeParameter:
        default: "Instance Type"
      DiskSize:
        default: "Disk Size(GiB)"
      SSHLocation:
        default: "Permit SSH Src IP Address"
      KeyName:
        default: "SSH Key Name"
      IAMRoleParameter:
        default: "IAM Role"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  ImageId:
    Type: AWS::EC2::Image::Id
    Default: ami-0f310fced6141e627


  InstanceTypeParameter:
    Default: t2.small
    Type: String

  DiskSize:
    Default: 8
    Type: String

  SSHLocation:
    Default: 127.0.0.0/32
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName

  IAMRoleParameter:
    Type: String

# ------------------------------------------------------------#
#  EC2 Create
# ------------------------------------------------------------#
Resources:
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      DisableApiTermination: "true"
      InstanceInitiatedShutdownBehavior: stop
      ImageId: !Ref ImageId
      KeyName: !Ref KeyName
      InstanceType: !Ref InstanceTypeParameter

      BlockDeviceMappings:
      - DeviceName: "/dev/xvda"
        Ebs:
          DeleteOnTermination: "false"
          VolumeSize: !Ref DiskSize
          VolumeType: "gp2"

      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeleteOnTermination: "true"
          DeviceIndex: "0"
          SubnetId:  { "Fn::ImportValue": !Sub "${PJPrefix}-public-subnet-a" }
          GroupSet:
            - !Ref EC2SG

      IamInstanceProfile: 
        !Ref IAMRoleParameter

      UserData:
        Fn::Base64: |
          #!/bin/sh
          #TimeZone Setting JST
          sudo timedatectl set-timezone Asia/Tokyo
          #Docker Install and Boot
          sudo yum update -y
          sudo yum -y install docker
          sudo systemctl start docker
          sudo systemctl enable docker

      Tags:
          - Key: Name
            Value: !Sub ${PJPrefix}-BastionEC2

## EIP Create
  ElasticIp:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref EC2
      Domain: vpc


## EC2 SecurityGroup Create
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ec2-ssh-permit
      GroupDescription: Allow SSH and HTTP access only MyIP
      VpcId:  { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SecurityGroupIngress:
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation


# ------------------------------------------------------------#
#  EC2 Output
# ------------------------------------------------------------#

Outputs:
  ElasticIp:
    Value: !GetAtt EC2.PublicIp
    Description: Public IP of EC2 instance

イメージのプッシュ

pushイメージ.png

1. ECRログイン

aws ecr get-login --region ap-northeast-1 --no-include-email

※上記コマンドのレスポンスコードを入力することでログインが完了する

docker login -u AWS -p XXXXXXXXX

2. イメージ用のDockerfile生成

Dockerfileの内容は下記の通り。

FROM ubuntu:18.04

# Install dependencies
RUN apt-get update && \
 apt-get -y install apache2

# Install apache and write hello world message
RUN echo 'Test Ver1' > /var/www/html/index.html

# Configure apache
RUN echo '. /etc/apache2/envvars' > /root/run_apache.sh && \
 echo 'mkdir -p /var/run/apache2' >> /root/run_apache.sh && \
 echo 'mkdir -p /var/lock/apache2' >> /root/run_apache.sh && \ 
 echo '/usr/sbin/apache2 -D FOREGROUND' >> /root/run_apache.sh && \ 
 chmod 755 /root/run_apache.sh

EXPOSE 80

CMD /root/run_apache.sh

3. ビルド~プッシュ

docker build -t test-ver1 .
docker tag test-ver1 XXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/myrepository
docker push XXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/myrepository:latest

やっとイメージのプッシュが完了しました。ここまでが長かったです…w
push確認.png

ECSに対するデプロイ

コンテナデプロイ.png
ようやくになりましたが、ECRにプッシュされたイメージをECS on Fargateでデプロイします。
下記Cfnテンプレートを利用して、ECSCluster/タスク定義/Serviceといった各コンポーネントを構築します。

ecs-cfn-template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description:
   Create Container with ECS on Fargate

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix

      - Label:
          default: "ECS Configuration"
        Parameters:
          - ECSTaskCPUUnit
          - ECSTaskMemory
          - HTTPLocation
          - IAMRoleParameter

    ParameterLabels:
      ECSTaskCPUUnit:
        default: "ECSTaskCPUUnit"
      ECSTaskMemory:
        default: "ECSTaskMemory"
      HTTPLocation:
        default: "Permit HTTP Src IP Address"
      IAMRoleParameter:
        default: "IAM Role"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  ECSTaskCPUUnit:
    AllowedValues: [ 256, 512, 1024, 2048, 4096  ]
    Type: String
    Default: "256"

  ECSTaskMemory:
    AllowedValues: [ 512, 1024, 2048, 4096  ]
    Type: String
    Default: "512"

  HTTPLocation:
    Default: 127.0.0.0/32
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})

  IAMRoleParameter:
    Type: String

# ------------------------------------------------------------#
#  ECS Create
# ------------------------------------------------------------#
Resources:
## ECS Cluster
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${PJPrefix}-Cluster
## ECS LogGroup
  ECSLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /ecs/logs/${PJPrefix}
## ECS TaskDefinition
  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref IAMRoleParameter
      Family: !Sub ${PJPrefix}-task
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: !Sub ${PJPrefix}-ecscontainer
          Image: !Sub ${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/myrepository
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Ref PJPrefix
          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
## ECS Service
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: 1
      LaunchType: FARGATE
      NetworkConfiguration:
       AwsvpcConfiguration:
           AssignPublicIp: ENABLED
           SecurityGroups:
             - !Ref ECSSG
           Subnets:
             - { "Fn::ImportValue": !Sub "${PJPrefix}-public-subnet-a" }
      ServiceName: !Sub ${PJPrefix}-ecsservice
      TaskDefinition: !Ref ECSTaskDefinition

## ECS SecurityGroup Create
  ECSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ecs-http-permit
      GroupDescription: Allow HTTP access only MyIP
      VpcId:  { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SecurityGroupIngress:
        # 
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref HTTPLocation

コンソール画面からデプロイされていることを確認します。
定義したクラスタ名やタスク定義に従ってタスク(今回は1コンテナ)が起動していることが分かります。
デプロイ確認1.png

最後にデプロイしたコンテナに踏み台EC2よりアクセスしてみます。(一時的にSGのインバウンドを開けています)
デプロイ確認2.png

コンソールで確認したPublic IPに対して、curlでリクエストを投げたところ「Test Ver1」でレスポンスが返却されており、
ECRにpushしたイメージ(Dockerfile内でindex.htmlを書き換えています。)がデプロイされていることが分かります。

まとめ

今回はコンテナをECS on Fargateにデプロイできるところまで実施しました。
CFnのテンプレートを参考にしていただければ、アカウントしかない状況からかなり簡単に構築まで進むかと思います。

次回はコンテナのリソース状況のモニタリングをCloudwatchを利用してやってみようと思います!

2
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0