メディアドゥ の沓名です。 今回は、AWS ECSでのスケーリングをテーマにしてまとめました。
AWS ECSのスケールコントロールにLambdaを採用した背景
AWS ECS Taskには、そもそもスケーリング用の機能が用意されています。何故今回、標準のスケーリング機能を使わずにLambdaでコントロールするようにしたかを、その特徴と共に整理します。
サービスのオートスケーリングオプション
ECS Taskをサービスとして起動している場合、オートスケーリングオプションが活用できます。サービスのCPU、メモリ使用率に閾値を設けてスケール調整させることも出来ますし、自分で定義したメトリクスを利用していて段階的にスケールコントロールする設定も可能です。
タスクスケジュールによるスケールコントロール
タスクスケジュールを登録する際、「タスクの数」を指定できます。また、処理量に合わせて次回スケジュール時にタスク数を調整することでスケールコントロールすることが可能です。
Lambda選択に到るまで
ECS Taskとして実行させようとしていた処理はいわゆるオフラインサービスで、バックオフィスから上がってきたコンテンツを加工してオンライン側で利用できるように設定する、という仕組みでした。コンテンツの登録は不定期に行われるため、要求がきたらすぐに処理できるようにキューを利用する形を採用しました。
処理対象コンテンツが大量にきた場合は、タスク数を増やして短時間に完了させ、逆に処理要求が0の時にはタスクを起動しない、というスケール調整を行おうとしました。
課題1:タスクスケジュールでは、イベントをトリガーに起動するタスク数を柔軟に調整出来ない。
今回の処理は常駐型ではなく、コンテンツを処理したらすぐにタスクを終了するものだったので、タスクスケジュールによるタスク管理の方法をとりました。しかし、タスクスケジュールでは何かのイベントをトリガーに動的に実行するタスク数が調整できるわけではないため、今回の目的には合わないものでした。
課題2:タスクスケジュールのオートスケール機能でも起動するコンテナ数は変えられない。
次なる選択肢として、タスク管理をスケジュールに切り替えて、オートスケール機能を活用する方法を検討しました。
しかし、オートスケールによるスケールインをした場合、生きているタスクの中からランダムでシャットダウンされます。オンラインサービスのように一つ一つの要求の実行時間が短い処理に対しては有効に機能しますが、今回の対象はコンテンツによっては数分以上かかるものもあるため、処理の途中でタスクが停止されてしまうことは許容できませんでした。
グレイスフルシャットダウンの仕組みを実装する方法も一つだったかもしれませんが、ロジックの複雑性が上がるのを避けたかったこともあり、この方法も断念しました。
Lambdaによるスケーリングの選択
上記の課題から、最終的にLambdaによってECS Taskのスケールを調整する方式を採用することにしました。 Lambdaであれば、SQSの残数をベースに柔軟なスケールコントロールを制御できることが選択の大きな理由でした。
LambdaによるECS Taskスケールコントロール
ECS Taskのスケールをコントロールために実装したLamdaの大まかな流れは次の通りです。
- CloudWatch EventsによるLambdaの定期実行(10分毎に起動など)
- SQSのメッセージ残数チェック
- メッセージ残数によってスケールサイズを計算
- メッセージ残数0なら処理をスキップ
- メッセージ残数が 1以上10未満ならタスクを1つ実行
- メッセージ残数が10以上なら10タスクを実行
(*スケール条件は適当にしています。)
ここからは、Pythonをベースに実装のヒントについて解説していきます。利用しているランタイムに合わせて、適宜読み替えてください。
SQSメッセージ残数確認
SQSのメッセージ残数を取得するには以下のメソッドを利用してApproximateNumberOfMessagesの値をチェックします。
boto3.client('sqs').get_queue_attributes()
LambdaからのECS Task実行
指定したタスク数でECS Taskを起動したい場合、次のメソッドを使います。
boto3.client('ecs').run_task()
メソッドには実行したいタスク定義と、そのタスク数をパラメータとして渡します。
同時に実行できるタスク数の壁と回避方法
ここまでの流れでLambdaによるECS Taskの実行数が制御できるようになるのですが、2019年3月現在では Run Taskで同時実行可能なタスク数が10までと制限されています。*1
そのため、せっかくのスケール調整用Lambdaも最大スケール数が10までに制限されることとなります。これを回避するためにタスクの数だけでスケール調整するのではなく、タスクの中のコンテナ数も含めて調整する方式を取ることにしました。
1タスクの中には最大で10コンテナまで登録できるので、10タスク × 10コンテナの最大100コンテナまでスケールできる計算となります。
1タスクあたりコンテナ数の変更
タスクの中で実行するコンテナの数はタスク定義で設定されています。なので、Lambdaからタスク定義の書き換えが必要となります。 タスク定義自体は次のメソッドで取得できます。
boto3.client('ecs').describe_task_definition()
タスク定義内の登録コンテナを引っ張り出して、必要数分コピーした上でタスク定義に反映する、という流れになります。なお、タスク定義の更新には次のメソッドが利用できます。
boto3.client('ecs').register_task_definition()
留意点として、コンテナの数が変われば当然タスク定義で要求するCPU,メモリの量も変更が必要です。*2
まとめ
AWS ECS Taskには二つのタスク管理方法と、それぞれのスケーリングの仕組みがあることについて触れました。 また、それぞれのスケーリングのエッジケースともう一つの選択肢としてLambda活用方法をご紹介しました。
もし、AWS ECSで提供されているスケールコントロールでうまく目的が達成できない場合、選択肢の一つとして活用いただければ幸いです。