概要
対象読者
- 今回は、Amazon ECS + Fargate のハンズオンです。対象読者は、Amazon Elastic Container Service (Amazon ECS) および AWS Fargate の初学者、Docker の初学者になります。
今回のテーマ
- 今回のハンズオンで経験できることは、以下となります。
- CloudFormation を使って、VPC および関連リソースを作成します。このVPC が、Fargate (コンテナ)を実行する環境になります。
- AWS Cloud9 を使って、nginx のDocker イメージを作成し、Amazon ECR (リポジトリ) にプッシュします。Cloud9 はクラウドベースの統合開発環境 (IDE) です。Cloud9の実行環境は、先に作成した VPC のパブリックサブネットに配置します。
- ECS クラスタおよびタスク定義を作成し、サービス/タスクをデプロイします。タスク定義に指定したコンテナが起動します。タスクは、先に作成した VPC のプライベートサブネットに配置します。今回コンテナへのアクセスは、ALB 経由で行います。このALB およびALB用のセキュリティグループは、CloudFormation によって作成されます。
- ALB にアクセスし、nginxコンテナから “Hello ECS.” が返ることをテストします。
- 最後に、ECS 関連のリソースを削除、Cloud9の実行環境の削除、VPC および関連リソースの削除を行います。特に、NAT Gateway、ALB は時間単位で費用が掛かります。勉強に使ったら、使わないリソースは削除しましょう。
ECS on Fargateの概念
- 今回のハンズオンの様に、コンテナをサーバーレスで実行する際に、ECS on Fargate の構成を選択します。Amazon Elastic Container Service (Amazon ECS)はコンテナのオーケストレーションになり、AWS Fargate はコンテナの実行環境になります。
- コンテナの実行環境にAWS Fargate を使用することで、コンテナホストがマネージド(サーバーレス)になり、実行環境の管理が不要、スケールもシームレスに行われます。
- ECS on Fargate の概念は、AWS Black Belt の資料も参考にしてください。
演習に利用するシステムの構成図
ハンズオン1:VPC および関連リソースの作成
CloudFormation を使ったVPC および関連リソースの作成
- CloudFormation を使って、VPC および関連リソースを作成します。AWSマネジメントコンソールからCloudFormationを選択します。
- 画面右上の「スタックの作成」から「新しいリソースを使用 (標準)」を選択します。
- 「テンプレートの準備完了」を選択します。「テンプレートの指定」から「テンプレートファイルのアップロード」を選択、「ファイルの選択」を選択し、CFnテンプレートファイルを選択します。
- 今回のテンプレートファイルは、以下の YAMLを使用します。この YAMLは、こちらの AWSドキュメントに記載されているAWS CloudFormation VPC テンプレートをカスタマイズし、ALB およびALB用のセキュリティグループの定義を追加したものです。
Description: This template deploys a VPC, with a pair of public and private subnets spread across two Availability Zones. It deploys an internet gateway, with a default route on the public subnets. It deploys a pair of NAT gateways (one in each AZ), and default routes for them in the private subnets. Parameters: EnvironmentName: Description: An environment name that is prefixed to resource names Type: String VpcCIDR: Description: Please enter the IP range (CIDR notation) for this VPC Type: String Default: 10.192.0.0/16 PublicSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone Type: String Default: 10.192.10.0/24 PublicSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone Type: String Default: 10.192.11.0/24 PrivateSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone Type: String Default: 10.192.20.0/24 PrivateSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone Type: String Default: 10.192.21.0/24 Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-vpc InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${EnvironmentName}-igw InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnet1CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-sntpub1 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PublicSubnet2CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-sntpub2 PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet1CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName}-sntpri1 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet2CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName}-sntpri2 NatGateway1EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway2EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway1: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway1EIP.AllocationId SubnetId: !Ref PublicSubnet1 NatGateway2: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway2EIP.AllocationId SubnetId: !Ref PublicSubnet2 PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName}-rtbpub DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet2 PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName}-rtbpri1 DefaultPrivateRoute1: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable1 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway1 PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1 PrivateRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName}-rtbpri2 DefaultPrivateRoute2: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable2 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway2 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable2 SubnetId: !Ref PrivateSubnet2 LoadBalancerSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: !Sub ${EnvironmentName}-sgalb1 GroupName: !Sub ${EnvironmentName}-sgalb1 VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '80' ToPort: '80' CidrIp: 0.0.0.0/0 ALB: Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' Properties: LoadBalancerAttributes: - Key: access_logs.s3.enabled Value: 'false' - Key: deletion_protection.enabled Value: 'false' - Key: idle_timeout.timeout_seconds Value: '60' Name: !Sub ${EnvironmentName}-alb1 Scheme: internet-facing SecurityGroups: - !Ref LoadBalancerSecurityGroup Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Name Value: !Sub ${EnvironmentName}-alb1 Outputs: VPC: Description: A reference to the created VPC Value: !Ref VPC PublicSubnets: Description: A list of the public subnets Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]] PrivateSubnets: Description: A list of the private subnets Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]] PublicSubnet1: Description: A reference to the public subnet in the 1st Availability Zone Value: !Ref PublicSubnet1 PublicSubnet2: Description: A reference to the public subnet in the 2nd Availability Zone Value: !Ref PublicSubnet2 PrivateSubnet1: Description: A reference to the private subnet in the 1st Availability Zone Value: !Ref PrivateSubnet1 PrivateSubnet2: Description: A reference to the private subnet in the 2nd Availability Zone Value: !Ref PrivateSubnet2 LoadBalancerSecurityGroup: Description: Security Group for ALB Value: !Ref LoadBalancerSecurityGroup ALB: Description: ALB (internet facing) Value: !Ref ALB
- スタックの名前を入力します。
- パラメータを指定します。EnvironmentName に任意の名前を指定し、その他のパラメータ (VPC,Subnet のCIDR) は変更しなくても構いません。
- スタックオプションは特に指定しません。
- レビューを確認し、「送信」を押します。
- スタック一覧より、該当のスタックの「ステータス」が「CREATE_COMPLETE」と表示されたことを確認します。スタックの実行完了には、しばらく時間が掛かります。
ハンズオン2:Cloud9 を使ってDocker イメージを作成
ECR のリポジトリ作成
- AWSマネジメントコンソールからECS(Elastic Container Service) を選択します。続いて、Amazon ECR のリポジトリを選択します。
- 「リポジトリを作成」を選択します。
- 可視性設定に「プライベート」を選択、リポジトリ名を指定し、「リポジトリを作成」を押します。
- リポジトリが作成されました。
Cloud9 のIDE (開発環境) を作成
- ECR のリポジトリに Dockerイメージを登録するため、Cloud9 を使用します。先ず、Cloud9 のIDE (開発環境) を作成します。
- AWSマネジメントコンソールからCloud9 を選択します。
- 続いて、「環境を作成」を選択します。
- 環境の名前を入力、環境タイプに「新しい EC2 インスタンス」を指定します。
- 新しい EC2 インスタンスのインスタンスタイプに「t2.micro (1 GiB GiB RAM + 1 vCPU)」、プラットフォームに「Amazon Linux 2」、タイムアウトに「30 分」を指定します。
- ネットワーク設定の接続に「AWS Systems Manager (SSM)」を選択、VPCに先ほど作成したVPC([EnvironmentName]-vpc)を選択、サブネットに先ほど作成したパブリックサブネット([EnvironmentName]-sntpub1)を選択します。「作成」を押します。
- Cloud9 のIDE (開発環境) が作成されました。
Cloud9 でDockerイメージを作成してリポジトリにプッシュ
- 作成されたCloud9 IDE のリンクを押します。以下の通り、IDE が起動しました。
- Cloud9 のターミナルから下記コマンドを実行します。
mkdir ecs_hello cd ecs_hello/ mkdir conf
- ecs_hello/ 配下に、Dockerfile ファイルを作成します。
- Dockerfile ファイルは、コンテナイメージを管理するための定義ファイルです。
- ベースとして使用する既存のイメージの指定(→ FROM コマンド)、イメージの作成プロセス時に実行されるコマンド(→ ADD コマンド)、コンテナイメージの新しいインスタンスが展開されるときに実行されるコマンド(→ RUN コマンド)などの定義が含まれます。
- 今回使用するDockerfile ファイルは、下記となります。
FROM nginx:latest ADD conf/nginx.conf /etc/nginx/ RUN echo "Hello ECS." > /usr/share/nginx/html/index.html
- ecs_hello/conf/ 配下に、nginx.conf を作成します。
- 今回使用する nginx.conf は、下記となります。
- デフォルトから変更している箇所は、worker_processes を1にしています。autoindex on を指定しています。
- 公開ディレクトリは、/usr/share/nginx/html です。
# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes 1; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { autoindex on; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } }
- 以下は、エディタの画面となります。
- Docker イメージの作成、プッシュを行うコマンドを確認するため、ECR のコンソールに戻り、作成したECR リポジトリを選択します。
- 「プッシュコマンドの表示」ボタンを押します。下記の画面が表示されます。
- Cloud9 のターミナルに戻り、Docker イメージの作成、ECR リポジトリへプッシュを行います。先ほどECR リポジトリの画面で確認したコマンド 1~4を使用します。(以下コマンドの111111111111 にはアカウント番号が入ります。リージョンはお使いの環境に合わせて、修正ください。)
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded $ $ docker build -t niikawa-testenv . Sending build context to Docker daemon 5.12kB Step 1/3 : FROM nginx:latest latest: Pulling from library/nginx f03b40093957: Pull complete eed12bbd6494: Pull complete fa7eb8c8eee8: Pull complete 7ff3b2b12318: Pull complete 0f67c7de5f2c: Pull complete 831f51541d38: Pull complete Digest: sha256:af296b188c7b7df1234567890abcdefg1234567890abcdefg1234567890abcde Status: Downloaded newer image for nginx:latest ---> f9c14fe76d50 Step 2/3 : ADD conf/nginx.conf /ec/nginx/ ---> 099789c1113a Step 3/3 : RUN echo "Hello ECS." > /usr/share/nginx/html/index.html ---> Running in ac1cfb8392fa Removing intermediate container ac1cfb8392fa ---> d2a54dd451ac Successfully built d2a54dd451ac Successfully tagged niikawa-testenv:latest $ $ docker tag niikawa-testenv:latest 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/niikawa-testenv:latest $ docker push 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/niikawa-testenv:latest The push refers to repository [111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/niikawa-testenv] 4648e8b920c2: Pushed 72944a72048c: Pushed 4fd834341303: Pushed 5e099cf3f3c8: Pushed 7daac92f43be: Pushed e60266289ce4: Pushed 4b8862fe7056: Pushed 8cbe4b54fa88: Pushed latest: digest: sha256:e72417f8fbbc47f1234567890abcdefg1234567890abcdefg1234567890abcde size: 1985
- ECR リポジトリに、イメージがプッシュがされました。ダイジェストが docker pushコマンドの結果と一致します。
ハンズオン3:ECS によるコンテナのデプロイ
ECS クラスタの作成
- AWSマネジメントコンソールからECS(Elastic Container Service) を選択します。続いて、クラスターを選択します。
- 「クラスターの作成」を選択します。
- クラスター名を入力します。
- ネットワーキングのVPCに先ほど作成したVPC([EnvironmentName]-vpc)を選択、サブネットに先ほど作成したプライベートサブネット([EnvironmentName]-sntpri1, [EnvironmentName]-sntpri2)を選択します。
- インフラストラクチャは、デフォルトを使用します。
- これは、キャパシティープロバイダーに関する設定であり、詳細はこちらのドキュメントを参照ください。
- ECS クラスタが作成されました。
ECS タスク定義の作成
- AWSマネジメントコンソールからECS(Elastic Container Service) を選択します。続いて、タスク定義を選択します。
- 「新しいタスク定義の作成」を選択します。
- タスク定義ファミリーの名前を入力、コンテナの名前を入力、コンテナイメージの指定(ECR からイメージURL のコピー/貼りつけ)を行います。
- ポートマッピングはデフォルトのコンテナポート 80 を使用します。awsvpc ネットワークモードを使用するタスク定義では、コンテナポートのみを指定します。
- タスクサイズのCPU、メモリに、Linuxでサポートされる最小の0.25 vCPU、0.5 GBを選択します。タスクサイズの詳細は、こちらのドキュメントを参照ください。
- タスク実行ロールは、コンテナに付与する権限です。
- ログ収集はデフォルト設定を使用して、CloudWatch Logs にログを送信する様ににタスク内のコンテナを設定します。
- タスク定義の設定値を確認して、問題なければ「作成」を押します。
- 以下の通り、新規にタスク定義が作成されました。最初のタスク定義は、リビジョン 1 になります。
サービスの作成
- タスク定義の「デプロイ」ボタンを押し、「サービスの作成」を選択します。
- 既存のクラスターから作成済みのクラスタを選択します。
- サービス名を入力、必要なタスクの数に「1」を指定します。
- ネットワーキングのVPCに先ほど作成したVPC([EnvironmentName]-vpc)を選択、サブネットに先ほど作成したプライベートサブネット([EnvironmentName]-sntpri1, [EnvironmentName]-sntpri2)を2つ選択します。
- セキュリティグループは、「新しいセキュリティグループの作成」を選択します。タスクのENI に割り当てるセキュリティグループの名前、説明を入力、インバウンドルールを登録します。このインバウンドルールには、CloudFormationにて作成したALB 用のセキュリティグループ([EnvironmentName]-sgalb1)を指定します。(ALB から転送されるリクエストのみ許可するため)
- パブリックIP は、オフに設定します。
- ロードバランシング – オプションを設定します。
- ロードバランサーの種類に「Application Load Balancer」を選択します。Application Load Balancerに「既存のロードバランサーを使用」を指定し、ロードバランサーにCloudFormationにて作成したALB ([EnvironmentName]-alb1)を指定します。
- リスナーは「新しいリスナーを作成」を選択します。ポートは「80」を指定、プロトコルは「HTTP」を選択します。
- ターゲットグループに「新しいターゲットグループの作成」を選択します。ターゲットグループ名を入力します。プロトコルは「HTTP」を選択します。ヘルスチェックの設定はデフォルトを使用します。
- サービスのその他の設定はデフォルトを使用します。最後に「作成」を選択します。
- サービス/タスクがデプロイされ、タスクが起動しました。
- サービス/タスクが正常に動作しているかを確認します。サービスの正常性とメトリクスを選択します。以下の通り、ステータスは正常、タスク 1個が実行中、ALB のヘルスチェックも成功しています。
- ターゲットグループを選択します。ターゲットに、先ほどデプロイされたタスクに割り当てられたENI のIPアドレスが登録されています。
nginxコンテナの疎通テスト
- ブラウザからALB にアクセスし、nginxコンテナから “Hello ECS.” が返ることをテストします。
ハンズオン4:後片付け
- 演習の後片付けを行います。先ずECS のタスクを停止させます。対象サービスの「サービスを更新」を選択し、デプロイ設定の必要なタスクの数を「0」に変更します。
- 次に、対象サービスの「サービスを削除」を選択し、サービスの削除を行います。
- 続いて、ECSクラスタを削除します。対象クラスタの「クラスターの削除」を選択し、ECSクラスタの削除を行います。
- Cloud9 の開発環境を削除します。EC2 のインスタンス(aws-cloud9-xxxxxxxxxx) が起動している場合は、インスタンスを選択、「インスタンスの終了」を選択します。(Cloud9 の開発環境にEC2 を使用しているため、VPC および関連リソースを削除する前にインスタンスを削除する必要があります)
- 最後に、CloudFormation のスタックを削除します。スタックを選択し、「削除」を選択します。事前に、VPC/サブネット上にデプロイされていたリソースが削除されていれば、10分程度でDELETE_COMPLETE になります。EC2 のインスタンス(aws-cloud9-xxxxxxxxxx) が残っている場合、VPC/サブネットが削除されずに残ります。その場合は個別にリソースの削除等を行い、再度CloudFormation のスタック削除を行ってください。
- これで、費用に関わるリソースの後片付けが出来ました。ECS のタスク定義、ECR リポジトリは残りますが、不要であれば削除してください。