[ハンズオン] ECS を使った nginxコンテナのデプロイ演習 6月 11, 2023 6月 12, 2023 【初学者向け】実践演習 , AWS , CloudFormation , Docker , ECS
概要
対象読者
今回は、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 タスク定義の作成
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 リポジトリは残りますが、不要であれば削除してください。
参考資料