この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
どうも、もこ@札幌オフィスです。
当エントリは弊社コンサルティング部による『AWS 再入門ブログリレー 2020』の 15日目のエントリです。
このブログリレーの企画は、普段 AWS サービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。AWS をこれから学ぼう!という方にとっては文字通りの入門記事として、またすでに AWS を活用されている方にとっても AWS サービスの再発見や 2020 年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。
では、さっそくいってみましょう。 15日目のテーマは『Amazon ECS』です。
Amazon ECSとは?
Amazon ECSとは「フルマネージドなコンテナオーケストレーションサービス」で、物凄くざっくり言うと「EC2上にコンテナを展開してALBなどと連携してトラフィックを制御して、コンテナのアップデートも良い感じに出来るサービス」です。
Amazon ECSを利用する事でAWS環境でコンテナのクラスターを簡単に作成させることができ、AutoScalingを利用して負荷に応じたコンテナのスケーリングなどもできるサービスになっています。
基本的にはECS Agentが導入されたAMIをベースにEC2のAutoScaling構成を組み、EC2をクラスターに参加させると、ECSからコンテナの実行・停止・デプロイ・各種AWSサービスとの連携が出来るようになります。
上記の図のように、ECSでは主に「クラスター」「サービス」「タスク」の3つで構成されています。それぞれ触って入門していきましょう。
ECSを実際に触ってみる
ECSクラスターを用意してみる
早速となりますが、ECSクラスターに再入門していきましょう。
ECSクラスターは名前の通りECSの骨組みのような物で、ECSクラスターの上に後述する「EC2インスタンス」を起動した上で「タスク定義」を元に作成される「サービス」から「タスク(コンテナ)」をクラスター内のインスタンスにデプロイする事が出来ます。
実践あるのみという事で、ECSのクラスターを作ってみます。
AWS CDKを利用する事で簡単に構築する事ができるので、AWS CDK(TypeScript)の初期設定をしていきます。
$ mkdir ecs
$ cd ecs
$ npm install -g aws-cdk
$ cdk init app --language typescript
$ npm install @aws-cdk/aws-ec2 @aws-cdk/aws-ecs @aws-cdk/aws-elasticloadbalancingv2
cdk initを実行した後に作成される lib/ecs-stack.ts
を下記のように編集します。
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
export class EcsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC作成
const vpc = new ec2.Vpc(this, 'ecs-vpc', {
cidr: "10.0.0.0/16"
});
// ECS Cluster作成
const cluster = new ecs.Cluster(this, 'cluster', {
vpc
});
}
}
編集が終わりましたら、早速AWS環境にデプロイしていきましょう。
$ cdk deploy
そうすると、ざっくりこんな感じの構成がAWS上に出来上がります
マネジメントコンソールのECSクラスター一覧から作成されたクラスターを確認する事が出来ます。
まだ何も作成していないの状態なので、サービスやタスク、EC2インスタンスは登録されていない状態です。
とりあえずコンテナを動かすためにEC2を追加してクラスターに取り込みましょう。
先程のCDKに cluster.addCapacity
を下記のように追加して、デプロイしていきます。
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
export class EcsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC作成
const vpc = new ec2.Vpc(this, 'ecs-vpc', {
cidr: "10.0.0.0/16"
})
// ECS Cluster作成
const cluster = new ecs.Cluster(this, 'cluster', {
vpc
});
// ClusterにAutoScalingGroupなt3.microを追加
cluster.addCapacity('DefaultAutoScalingGroupCapacity', {
instanceType: new ec2.InstanceType("t3.micro"),
desiredCapacity: 4
});
}
}
$ cdk deploy
そうすると、AutoScaling Groupで作成されたEC2インスタンスが desiredCapacity
で指定した数起動してきます。
ECSのクラスター画面からもEC2インスタンスが表示されて、利用可能なCPU/メモリなどの状況が見れるようになります。
CDKでサクッと作ってしまいましたが、手動で作成される場合はAmazon ECS-optimized AMIsなどで提供されてるECS Agentが導入されているAMIを利用してEC2を起動して、UserData等でECS Agentに参加するクラスターの指定をする形となります。
現在の環境はこんな感じで、上記CDKコードで起動したインスタンスで起動する「ECS Agent」にUserData経由でECSクラスターに紐付けられている状態です。
タスク定義とサービスを作る
それでは仕上げに行きましょう。
「タスク定義」と「サービス」を作っていき、実際にコンテナをECSクラスター上で走らせて、サービスとALBを紐付けてコンテナにALB経由でアクセス出来るようにしましょう。
今回はHTTPでの疎通確認のテストを行いたいので、コンテナイメージは nginx:1.19.2-alpine
を利用します。(通常ECSを利用する場合は合わせてECRを利用するパターンが多いので、本番環境で利用する際は是非ECRもご活用下さい)
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
export class EcsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC作成
const vpc = new ec2.Vpc(this, 'ecs-vpc', {
cidr: "10.0.0.0/16"
})
// ECS Cluster作成
const cluster = new ecs.Cluster(this, 'cluster', {
vpc
});
// ClusterにAutoScalingGroupなt3.microを追加
cluster.addCapacity('DefaultAutoScalingGroupCapacity', {
instanceType: new ec2.InstanceType("t3.micro"),
desiredCapacity: 4
});
// タスク定義作成
const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
// タスク定義設定
taskDefinition.addContainer('DefaultContainer', {
image: ecs.ContainerImage.fromRegistry("nginx:1.19.2-alpine"),
memoryLimitMiB: 256,
cpu: 128
}).addPortMappings({
containerPort: 80
});
// ECS Service作成
const ecsService = new ecs.Ec2Service(this, 'Service', {
cluster,
taskDefinition
});
// LoadBalancer作成
const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
vpc,
internetFacing: true
});
// TargetGroup作成
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
vpc,
port: 80
});
// LoadBanacerと紐付け
loadBalancer.addListener('Listener', {
port: 80,
defaultTargetGroups: [targetGroup]
})
// TargetGroupとECS Serviceを紐付け
targetGroup.addTarget(ecsService);
}
}
編集が終わったら毎度の事ながらcdk deployして環境に適用していきます。
$ cdk deploy
環境への適用が終わったら、マネジメントコンソールのECSからサービスとタスク定義が追加されていることを確認出来ます!
早速、マネジメントコンソールのALB画面からDNS名を拾ってブラウザでアクセスしてみます。
Welcome to Nginx! の画面が表示されました! nginxのコンテナにALB経由でアクセス出来ていることを確認出来ます!
コンテナイメージをアップデートしたときの挙動
さて、Hello Worldが済んだところでタスクアップデート時の挙動を見てみましょう。
上記CDKのコードでさらっと流してしまいましたが、CDKは内部的にコンテナのイメージ情報やポートマッピングの設定を記載する「タスク定義」と呼ばれるものをECSに登録して、サービスがタスク定義の内容を見ながらコンテナを作成をしたりALBなどとの連携をしています。
基本的にはタスク定義の新しいバージョンを作っていき、サービスからバージョンをアップデートしてタスクを更新していく形になります。
今回はCDKを利用する事を想定して、コンテナイメージを nginx:1.19.2-alpine
から nginx:1.19.2
に変更してみました。
// タスク定義作成
const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
// タスク定義設定
taskDefinition.addContainer('DefaultContainer', {
image: ecs.ContainerImage.fromRegistry("nginx:1.19.2"),
memoryLimitMiB: 256,
cpu: 128
}).addPortMappings({
containerPort: 80
});
この状態で cdk deployして挙動を確認してみましょう。
$ cdk deploy
まずはじめに、新しいコンテナイメージが設定されたタスク定義が作成されていることを確認出来ます。
続いて、サービスのバージョンが上がり新しいタスク定義でタスク(コンテナ)が上がってきます。
ターゲットグループのヘルスチェックで合格してからしばらくすると、古いバージョンのタスクのDrainingが始まります。
このような流れで、最終的には設定したタスク数だけの新しいバージョンのタスクが立ち上がってきました。
「タスク定義を更新してサービスのタスクバージョンを変更」するだけでコンテナのローリングアップデートが可能で、ダウンタイムなしで簡単出来るので、非常に便利です。
まとめ
以上、『AWS 再入門ブログリレー 2020』の 15日目のエントリ『Amazon ECS』編でした。
明日 (8/25) は青柳さんの「Amazon Cognito」の予定です。お楽しみに!!