[ハンズオン] ECS を使った nginxコンテナのデプロイ演習
概要
対象読者
- 今回は、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 リポジトリは残りますが、不要であれば削除してください。