はじめに
Docker Composeを使うと、ローカル環境で簡単にコンテナを起動・管理できます。
一方、本番環境では、以下のような要件が出てきます。
- コンテナ障害時に自動復旧したい
- 複数のコンテナへ負荷分散したい
- コンテナ数を簡単に増やしたい
- CloudWatch Logsでログを確認したい
- AWS IAMで権限制御したい
このような用途で利用されるのが、Amazon ECSです。
本記事では、Amazon ECS Managed Instancesを使ってNginxを構築します。
また、HTMLファイルはDockerイメージに含めず、Amazon S3に保存した index.html を S3 Files でコンテナへマウントします。
これにより、S3上のHTMLを書き換えるだけで、数秒後にNginxの表示内容へ反映される構成を作ります。
ECSとは
Amazon ECSは、AWSが提供するコンテナオーケストレーションサービスです。
Docker Composeが主に1台のサーバー上でコンテナを起動する仕組みであるのに対し、ECSはAWS上で複数のコンテナを管理できます。
Docker Compose
Server
└── nginx
Amazon ECS
ECS Cluster
└── ECS Service
└── ECS Task
└── nginx
ECSでは、コンテナを直接起動するのではなく、以下のような構成で管理します。
| ECSの要素 | 説明 |
|---|---|
| Cluster | コンテナを実行する基盤 |
| Task Definition | コンテナの設計図 |
| Task | 実行中のコンテナ |
| Service | Task数を維持する仕組み |
| Capacity Provider | コンテナ実行基盤の管理 |
今回作成する構成
今回は以下の構成を作成します。
既存VPC
├── Private Subnet-A
│ ├── 踏み台サーバー
│ ├── Internal ALB
│ └── ECS Managed Instance
│ └── nginx Task
│
├── Private Subnet-C
│ ├── Internal ALB
│ └── ECS Managed Instance
│ └── nginx Task
│
└── Amazon S3
└── index.html
↑
S3 Filesでマウント
ポイントは以下です。
| 項目 | 内容 |
|---|---|
| ECS実行基盤 | Managed Instances |
| OS | Amazon Linux |
| VPC | 既存VPC |
| Subnet | Private Subnet |
| ALB | Internal ALB |
| Target Group | IPターゲット |
| Webサーバー | nginx:latest |
| HTML配置先 | Amazon S3 |
| HTMLマウント先 | /usr/share/nginx/html |
| 動作確認 | 踏み台サーバーからInternal ALBへアクセス |
| スケール確認 | Desired Countを1から2へ変更 |
S3バケット名について
S3バケット名はグローバルで一意である必要があります。
そのため、AWS推奨のアカウントIDを含めた名前にします。
ecs-nginx-demo-<AWSアカウントID>-ap-northeast-1-an
例です。
ecs-nginx-demo-123456789012-ap-northeast-1-an
このようにすることで、他のAWSアカウントとバケット名が重複しにくくなります。
S3バケットを作成する
AWSマネジメントコンソールで以下を開きます。
S3 > 汎用バケット > バケットを作成
設定値は以下です。
| 項目 | 設定値 |
|---|---|
| バケットタイプ | 汎用 |
| バケット名 | ecs-nginx-demo-<AWSアカウントID>-ap-northeast-1-an |
| AWSリージョン | アジアパシフィック(東京)ap-northeast-1 |
| オブジェクト所有者 | ACL無効 |
| パブリックアクセスをすべてブロック | 有効 |
| バケットのバージョニング | 無効 |
| デフォルト暗号化 | Amazon S3 マネージドキー |
| バケットキー | 有効 |
作成後、バケットを開きます。
index.htmlをアップロードする
ローカルPCで以下のファイルを作成します。
ファイル名は index.html です。
<!DOCTYPE html>
<html>
<head>
<title>ECS S3 Files Demo</title>
</head>
<body>
<h1>Hello ECS</h1>
</body>
</html>
S3バケットを開き、以下の順に操作します。
S3 > 対象バケット > オブジェクト > アップロード
設定値は以下です。
| 項目 | 設定値 |
|---|---|
| アップロードファイル | index.html |
| 保存先 | バケット直下 |
| アクセス許可 | 変更なし |
| ストレージクラス | 標準 |
アップロード後、以下のようになっていれば問題ありません。
s3://ecs-nginx-demo-<AWSアカウントID>-ap-northeast-1-an/index.html
IAMポリシーを作成する
S3 FilesがS3バケットを読み取れるように、IAMポリシーを作成します。
AWSマネジメントコンソールで以下を開きます。
IAM > ポリシー > ポリシーの作成
「JSON」を選択し、以下を入力します。
<AWSアカウントID> は自分のAWSアカウントIDに置き換えます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::ecs-nginx-demo-<AWSアカウントID>-ap-northeast-1-an"
},
{
"Sid": "AllowReadObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::ecs-nginx-demo-<AWSアカウントID>-ap-northeast-1-an/*"
}
]
}
ポリシー名は以下にします。
S3FilesReadOnlyForEcsNginx
設定値です。
| 項目 | 設定値 |
|---|---|
| ポリシー名 | S3FilesReadOnlyForEcsNginx |
| 説明 | Allow ECS task to read nginx html files from S3 |
IAMロールを作成する
次に、ECS TaskからS3を読むためのIAMロールを作成します。
AWSマネジメントコンソールで以下を開きます。
IAM > ロール > ロールを作成
設定値は以下です。
| 項目 | 設定値 |
|---|---|
| 信頼されたエンティティタイプ | AWSサービス |
| ユースケース | Elastic Container Service |
| ユースケース詳細 | Elastic Container Service Task |
許可ポリシーで、先ほど作成した以下のポリシーを選択します。
S3FilesReadOnlyForEcsNginx
ロール名は以下にします。
ecs-task-role-nginx-s3files
| 項目 | 設定値 |
|---|---|
| ロール名 | ecs-task-role-nginx-s3files |
| 説明 | Task role for nginx to mount S3 Files |
このロールは、後ほどTask Definitionの Task role に指定します。
Security Groupを作成する
今回必要なSecurity Groupは2つです。
| Security Group名 | 用途 |
|---|---|
sg-ecs-nginx-alb |
Internal ALB用 |
sg-ecs-nginx-task |
ECS Task用 |
通信経路は以下です。
踏み台サーバー
↓ HTTP:80
Internal ALB
↓ HTTP:80
ECS Task nginx
ALB用Security Groupを作成する
AWSマネジメントコンソールで以下を開きます。
EC2 > セキュリティグループ > セキュリティグループを作成
基本情報です。
| 項目 | 設定値 |
|---|---|
| セキュリティグループ名 | sg-ecs-nginx-alb |
| 説明 | Security group for internal ALB of ECS nginx |
| VPC | 既存VPC |
インバウンドルールです。
| タイプ | プロトコル | ポート | ソース |
|---|---|---|---|
| HTTP | TCP | 80 | 踏み台サーバーのSecurity Group |
踏み台サーバーのSecurity Groupをソースに指定します。
例です。
sg-bastion
アウトバウンドルールです。
| タイプ | プロトコル | ポート | 宛先 |
|---|---|---|---|
| すべてのトラフィック | すべて | すべて | 0.0.0.0/0 |
ALBからECS Taskへ通信するため、アウトバウンドはデフォルトのままで問題ありません。
ECS Task用Security Groupを作成する
同じく以下を開きます。
EC2 > セキュリティグループ > セキュリティグループを作成
基本情報です。
| 項目 | 設定値 |
|---|---|
| セキュリティグループ名 | sg-ecs-nginx-task |
| 説明 | Security group for ECS nginx tasks |
| VPC | 既存VPC |
インバウンドルールです。
| タイプ | プロトコル | ポート | ソース |
|---|---|---|---|
| HTTP | TCP | 80 | sg-ecs-nginx-alb |
ここでは、踏み台サーバーからの直接アクセスは許可しません。
ECS TaskにはALBからのみアクセスさせます。
アウトバウンドルールです。
| タイプ | プロトコル | ポート | 宛先 |
|---|---|---|---|
| すべてのトラフィック | すべて | すべて | 0.0.0.0/0 |
S3 FilesでS3へアクセスするため、ECS Task側から外向き通信が必要です。
Private Subnetの場合、S3へ到達するために以下のいずれかが必要です。
- NAT Gateway
- NAT Instance
- S3 Gateway Endpoint
閉域構成にしたい場合は、S3 Gateway Endpointを利用します。
Target Groupを作成する
ALBからECS Taskへ転送するため、Target Groupを作成します。
AWSマネジメントコンソールで以下を開きます。
EC2 > ターゲットグループ > ターゲットグループの作成
基本設定
| 項目 | 設定値 |
|---|---|
| ターゲットタイプ | IPアドレス |
| ターゲットグループ名 | tg-ecs-nginx |
| プロトコル | HTTP |
| ポート | 80 |
| IPアドレスタイプ | IPv4 |
| VPC | 既存VPC |
| プロトコルバージョン | HTTP1 |
今回はECS Serviceと連携するため、ターゲットタイプは IPアドレス にします。
ECS Taskは awsvpc ネットワークモードでENIを持つため、ALBはTaskのIPアドレスへ直接転送します。
ヘルスチェック設定
| 項目 | 設定値 |
|---|---|
| ヘルスチェックプロトコル | HTTP |
| ヘルスチェックパス | / |
| 正常しきい値 | 5 |
| 非正常しきい値 | 2 |
| タイムアウト | 5秒 |
| 間隔 | 30秒 |
| 成功コード | 200 |
Nginxは / にアクセスすると index.html を返します。
そのため、ヘルスチェックパスは / で問題ありません。
ターゲット登録
作成時点ではターゲットを手動登録しません。
ECS Serviceを作成すると、ECSが自動でTaskのIPアドレスをTarget Groupへ登録します。
そのため、ターゲット登録画面では何も選択せずに作成します。
Internal ALBを作成する
AWSマネジメントコンソールで以下を開きます。
EC2 > ロードバランサー > ロードバランサーの作成
「Application Load Balancer」を選択します。
基本設定
| 項目 | 設定値 |
|---|---|
| ロードバランサー名 | alb-ecs-nginx-internal |
| スキーム | 内部 |
| IPアドレスタイプ | IPv4 |
スキームは 内部 を選択します。
これにより、ALBはPrivate Subnet内でのみ利用されます。
ネットワークマッピング
| 項目 | 設定値 |
|---|---|
| VPC | 既存VPC |
| サブネット | Private Subnet-A、Private Subnet-C |
ALBは高可用性のため、2つ以上のAZに配置します。
今回はPrivate Subnetを2つ選択します。
Security Group
| 項目 | 設定値 |
|---|---|
| Security Group | sg-ecs-nginx-alb |
Listener
| 項目 | 設定値 |
|---|---|
| プロトコル | HTTP |
| ポート | 80 |
| デフォルトアクション |
tg-ecs-nginx へ転送 |
作成後、ALBのDNS名を控えます。
例です。
internal-alb-ecs-nginx-internal-xxxxxxxx.ap-northeast-1.elb.amazonaws.com
ECSクラスターを作成する
AWSマネジメントコンソールで以下を開きます。
ECS > Clusters > Create Cluster
設定値は以下です。
| 項目 | 設定値 |
|---|---|
| Cluster name | ecs-cluster-nginx |
| Infrastructure | Managed Instances |
| OS | Amazon Linux |
| VPC | 既存VPC |
| Subnets | Private Subnet-A、Private Subnet-C |
Managed Instancesを選ぶと、ECSがManaged Instances用のCapacity Providerを自動作成します。
今回の記事では、Capacity Providerを個別に事前作成せず、クラスター作成時にECSへ自動作成させます。
Task Definitionを作成する
AWSマネジメントコンソールで以下を開きます。
ECS > task difinitions > Create
Task Definition基本設定
| 項目 | 設定値 |
|---|---|
| Task definition family | nginx-task |
| Launch type | Managed Instances |
| Network mode | awsvpc |
| Task role | ecs-task-role-nginx-s3files |
| Task execution role | ecsTaskExecutionRole |
Task roleには、S3 FilesでS3を読むためのロールを指定します。
Task execution roleは、ECSがコンテナイメージ取得やCloudWatch Logs出力に使うロールです。
通常は ecsTaskExecutionRole を利用します。
Task size
| 項目 | 設定値 |
|---|---|
| CPU | 0.25 vCPU |
| Memory | 0.5 GB |
Nginxの簡単な検証なので、小さいサイズで十分です。
コンテナを追加する
Task Definition作成画面で、コンテナを追加します。
コンテナ基本設定
| 項目 | 設定値 |
|---|---|
| Container name | nginx |
| Image URI | nginx:latest |
| Essential container | Yes |
| Private registry authentication | Off |
Image URIについて
今回はDocker Hubで公開されている公式Nginxイメージを利用します。
nginx:latest
これは、Docker Hub上のNginx公式イメージの最新版タグを意味します。
ECRを使う場合は以下のようなURIになります。
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest
今回はECRは使わず、Docker Hubの nginx:latest を直接指定します。
Port mappings
| 項目 | 設定値 |
|---|---|
| Container port | 80 |
| Protocol | TCP |
| Port name | nginx-80-tcp |
| App protocol | HTTP |
Nginxはコンテナ内で80番ポートをListenします。
ALBからもHTTP:80でアクセスするため、Container portは80にします。
S3 Files Volumeを設定する
Task DefinitionのVolume設定で、S3 Filesを追加します。
Volume設定
| 項目 | 設定値 |
|---|---|
| Volume name | s3-html-volume |
| Volume type | S3 Files |
| File System | fs-XXXXXXXXXXXXXXXX |
| S3 prefix | / |
| Read only | 有効 |
File Systemには、先ほど作成したファイルシステムを指定します。
S3 prefixはバケット直下を利用するため / にします。
つまり、以下のファイルがコンテナ内に見えるようになります。
s3://ecs-nginx-demo-<AWSアカウントID>-apne1/index.html
↓
/usr/share/nginx/html/index.html
Mount points
コンテナ nginx に対して、以下のマウント設定を追加します。
| 項目 | 設定値 |
|---|---|
| Container | nginx |
| Source volume | s3-html-volume |
| Container path | /usr/share/nginx/html |
| Read only | 有効 |
Nginx公式イメージでは、HTMLの公開ディレクトリは以下です。
/usr/share/nginx/html
ここへS3 Filesをマウントすることで、S3上の index.html がNginxのトップページになります。
CloudWatch Logsを設定する
コンテナのログ設定を行います。
| 項目 | 設定値 |
|---|---|
| Log collection | 有効 |
| Log driver | awslogs |
| Log group | /ecs/nginx-task |
| Region | ap-northeast-1 |
| Stream prefix | ecs |
NginxのアクセスログやエラーログをCloudWatch Logsで確認できるようになります。
ECS Serviceを作成する
AWSマネジメントコンソールで以下を開きます。
ECS > Clusters > ecs-cluster-nginx > Services > Create
Service基本設定
| 項目 | 設定値 |
|---|---|
| Compute options | Capacity provider strategy |
| Capacity provider | Managed Instances作成時に自動作成されたもの |
| Task definition Family | nginx-task |
| Service name | nginx-service |
| Desired tasks | 1 |
Networking
| 項目 | 設定値 |
|---|---|
| VPC | 既存VPC |
| Subnets | Private Subnet-A、Private Subnet-C |
| Security group | sg-ecs-nginx-task |
| Public IP | Disabled |
今回はPrivate Subnet構成のため、Public IPは無効にします。
Load balancing
すべて先ほど作成したロードバランサ・ターゲットグループを選択します。
| 項目 | 設定値 |
|---|---|
| Load balancer type | Application Load Balancer |
| Load balancer | alb-ecs-nginx-internal |
| Listener | HTTP:80 |
| Target group | tg-ecs-nginx |
| Container | nginx |
| Container port | 80 |
ECS Serviceを作成すると、Taskが起動し、TaskのIPアドレスがTarget Groupへ自動登録されます。
動作確認する
ECS Service作成後、以下を確認します。
ECS > Clusters > ecs-cluster-nginx > Services > nginx-service
Tasksタブで、Taskの状態が以下になっていることを確認します。
RUNNING
次にTarget Groupを確認します。
EC2 > ターゲットグループ > tg-ecs-nginx > Targets
TaskのIPアドレスが登録され、状態が以下になっていれば正常です。
healthy
踏み台サーバーからALBへアクセスします。
curl http://<Internal ALBのDNS名>
以下のように表示されれば成功です。
<h1>Hello ECS</h1>
S3のHTMLを書き換えて反映確認する
S3の index.html を更新します。
<!DOCTYPE html>
<html>
<head>
<title>ECS S3 Files Demo</title>
</head>
<body>
<h1>Hello ECS Version2</h1>
</body>
</html>
アップロード後、数秒待ってから踏み台サーバーで再度確認します。
curl http://<Internal ALBのDNS名>
以下のように変われば成功です。
<h1>Hello ECS Version2</h1>
Dockerイメージの再作成も、ECS Serviceの更新も不要です。
タスク数を増やす
ECS Serviceを更新し、Task数を増やします。
ECS > Clusters > ecs-cluster-nginx > Services > nginx-service > Update
Desired tasksを変更します。
| 項目 | 変更前 | 変更後 |
|---|---|---|
| Desired tasks | 1 | 2 |
更新後、TasksタブでTaskが2つ起動していることを確認します。
RUNNING Task: 2
Target Groupでも、登録済みターゲットが2つになります。
EC2 > ターゲットグループ > tg-ecs-nginx > Targets
構成は以下のようになります。
Internal ALB
│
├── nginx Task 1
└── nginx Task 2
ALBが2つのTaskへ負荷分散します。
CloudWatch Logsを確認する
CloudWatch Logsを開きます。
CloudWatch > Logs > Log groups > /ecs/nginx-task
ログストリームを開くと、Nginxのログを確認できます。
踏み台サーバーから何度かアクセスします。
curl http://<Internal ALBのDNS名>
ログが出力されれば、CloudWatch Logs連携も正常です。
Docker Composeとの違い
Docker Composeでは、基本的に1台のサーバー上でコンテナを起動します。
Docker Compose
Server
└── nginx
ECSでは、ServiceがTask数を維持します。
Amazon ECS
Cluster
└── Service
├── Task 1
└── Task 2
Taskが停止しても、Serviceが指定された数に戻そうとします。
今回であれば、Desired tasksを2にしている場合、1つのTaskが停止しても、ECSが新しいTaskを起動しようとします。
ECSを利用するメリット
ECSを利用すると、以下のようなメリットがあります。
- コンテナ障害時の自動復旧
- Desired tasksによる簡単なスケールアウト
- ALBとの連携
- CloudWatch Logsとの統合
- IAMによる権限制御
- S3 FilesによるHTML更新の簡素化
- Managed Instancesによるインスタンス管理負荷の軽減
まとめ
本記事では、Amazon ECS Managed Instancesを利用して、Nginxを構築しました。
また、HTMLファイルをDockerイメージに含めず、Amazon S3に保存し、S3 FilesでNginxコンテナへマウントしました。
Amazon ECSは、簡単にコンテナ環境を構築・運用でき、スケールもできるため、従来のDocker環境に比べ本番運用に適していることがわかります。
