« AWS Toolkit for Windows PowerShellによるAmazon WorkSpacesのオペレーション | メイン | AWS SDK for .NETでのASP .NET 5の新規サポート »

Amazon ECSのカスタムスケジューラを作成する方法

私の同僚のDaniele Stroppaが、Amazon ECSのカスタムスケジューラを作成する方法についての素晴らしいゲスト投稿を送ってくれました。


Amazon EC2 Container Service (ECS)は高いスケーラビリティと高いパフォーマンスをもつコンテナ管理サービスで、Dockerコンテナをサポートしており、管理されたAmazon EC2インスタンスのクラスタ上で簡単にアプリケーションを実行することができます。Amazon ECSは現代的な分散アプリケーションを実行する上で必要とされる2つの鍵となる機能を実現しています: 信頼できる状態管理と柔軟なスケジューリングです。Wernerが最近のブログで説明しているように、ECSはクラスタの状態をシンプルなAPI群を通じて提供しており、それによってクラスタ無いの全てのインスタンスとそれらの上で動いている全てのコンテナの詳細を取得することができます。この投稿では、ECS APIを使ってカスタムスケジューラを作成する方法を紹介します。

スケジューリング

スケジューラはシステムの要求や必要条件を理解 - 例えば、あるコンテナは200MBのRAMと80番ポートを必要とする - 効率よくそれらを満たす様に努力します。スケジューラはクラスタ状態管理システムに必要となるリソースの確保をリクエストします。

ECSは楽観的な並行制御を提供しているので、複数のスケジューラが全く同時に処理をすることが可能です; クラスタ管理システムはリソースが利用可能であることを確認し、スケジューラに対してコミットします。スケジューラはクラスタ管理システムからのイベントを受け取ることができアクションを取ることができます、例えばアプリケーションの可用性を担保したり、Elastic Load Balancingの負荷分散の様な他のリソースとの連携を行えます。

ECSはリソースの要求と可用性の要件に応じて最適なインスタンスの場所を発券する、2つのスケジューラを現在提供しています: タスクスケジューラとサービススケジューラです。何人かのお客様は現状のスケジューラのどちらでも要件が満たされないことがあると思います。例えば、Elastic Load Balancingの代わりにRoute 53に使って、カスタムスケジューラはタスクがスケジュールされたらSRVレコードを作成しタスクが停止したらレコードを削除したい時などです。

このブログでは、タスクが実行されている数が最小のインスタンスにタスクを実行するというカスタムスケジューラを例に、カスタムスケジューラの作成プロセスをご紹介します。

カスタムスケジューラの実装

カスタムスケジューラはECSのList*とDescribe* API操作を利用して、クラスタの現在の状態を判断します。続いて、スケジューラに実装されたロジックによって1つまたは複数のコンテナインスタンスを選択し、StartTask APIを使って選択されたコンテナインスタンス上にタスクを開始します。API操作のより詳しい情報については、Amazon ECS API Referenceをご覧ください。

例えばとして、実行しているタスクの数が最も少ないインスタンス上でタスクを開始したいとしましょう。こちらがこのロジックを実装したカスタムスケジューラの実装方法になります。まずはクラスタ上の全てのコンテナインスタンスのリストを取得することから始まります。


  def getInstanceArns(clusterName):
    containerInstancesArns = []
    # Get instances in the cluster
    response = ecs.list_container_instances(cluster=clusterName)
    containerInstancesArns.extend(response['containerInstanceArns'])
    # If there are more instances, keep retrieving them
    while response.get('nextToken', None) is not None:
        response = ecs.list_container_instances(
            cluster=clusterName, 
            nextToken=response['nextToken']
        )
        containerInstancesArns.extend(response['containerInstanceArns'])

    return containerInstancesArns

 クラスタ内のそれぞれのインスタンスについて、いくつのタスクがRUNNING状態にあるかを数えることができ、タスクの実行数が最も少ないインスタンス上でタスクを開始することができます。


  def startTask(clusterName, taskDefinition):
    startOn = []

    # Describe all instances in the ECS cluster
    containerInstancesArns = getInstanceArns(clusterName)
    response = ecs.describe_container_instances(
        cluster=clusterName, 
        containerInstances=containerInstancesArns
    )
    containerInstances = response['containerInstances']

    # Sort instances by number of running tasks
    sortedContainerInstances = sorted(
        containerInstances, 
        key=lambda containerInstances: containerInstances['runningTasksCount']
    )

    # Get the instance with the least number of tasks
    startOn.append(sortedContainerInstances[0]['containerInstanceArn'])
    logging.info('Starting task on instance %s...', startOn)

    # Start a new task
    response = ecs.start_task(
        cluster=clusterName, 
        taskDefinition=taskDefinition, 
        containerInstances=startOn, 
        startedBy='LeastTasksScheduler'
    )

 それぞれを一緒にくっつけると、カスタムスケジューラはこの様な形になります:


#!/usr/bin/env python
import boto3
import argparse
import logging

# Set up logger
logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logging.INFO)

# Set up ECS boto client
ecs = boto3.client('ecs')

def getInstanceArns(clusterName):
    containerInstancesArns = []
    # Get instances in the cluster
    response = ecs.list_container_instances(cluster=clusterName)
    containerInstancesArns.extend(response['containerInstanceArns'])
    # If there are more instances, keep retrieving them
    while response.get('nextToken', None) is not None:
        response = ecs.list_container_instances(
            cluster=clusterName, 
            nextToken=response['nextToken']
        )
        containerInstancesArns.extend(response['containerInstanceArns'])

    return containerInstancesArns

def startTask(clusterName, taskDefinition):
    startOn = []

    # Describe all instances in the ECS cluster
    containerInstancesArns = getInstanceArns(clusterName)
    response = ecs.describe_container_instances(
        cluster=clusterName, 
        containerInstances=containerInstancesArns
    )
    containerInstances = response['containerInstances']

    # Sort instances by number of running tasks
    sortedContainerInstances = sorted(
        containerInstances, 
        key=lambda containerInstances: containerInstances['runningTasksCount']
    )

    # Get the instance with the least number of tasks
    startOn.append(sortedContainerInstances[0]['containerInstanceArn'])
    logging.info('Starting task on instance %s...', startOn)

    # Start a new task
    response = ecs.start_task(
        cluster=clusterName, 
        taskDefinition=taskDefinition, 
        containerInstances=startOn, 
        startedBy='LeastTasksScheduler'
    )

# 
# LeastTasks ECS Scheduler
#
if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='ECS Custom Scheduler to start a task on the instance with the least number of running tasks.'
    )
    parser.add_argument('-c', '--cluster', 
        nargs='?', 
        default='default', 
        help='The short name or full Amazon Resource Name (ARN) of the cluster that you want to start your task on. If you do not specify a cluster, the default cluster is assumed.'
    )
    parser.add_argument('-d', '--task-definition', 
        required=True, 
        help='The family and revision (family:revision) or full Amazon Resource Name (ARN) of the task definition that you want to start.'
    )
    args = parser.parse_args()  

    logging.info('Starting task %s on cluster %s...', args.task_definition, args.cluster)
    startTask(args.cluster, args.task_definition)

 まとめ

これはとても単純な例になりますが、カスタムスケジューラを作成するためにどうやってこの強力なAmazon ECS APIを使うかのアイデアを与えてくれると思います。

コミュニティでは最近ecs_stateというものが作られています。これは”小さなGoのライブラリでECSのListとDescribe API操作を使って実行中のタスクと利用可能なリソースの情報をオンメモリのSQLiteに保存します。状態をリフレッシュするタイミングを制御するAPI群や、タスクを受け入れるのに十分なリソースをもつマシンを検索する操作のAPI群があります。最終的にStartTaskやStopTaskを呼び出す前に追加のロジックやフィルタリングも行うことができます。このライブラリによってスケジューラが実行すべきAPI操作を削減することができ、SQLを使ってシンプルにリソースを探すことが可能となります。"

 

我々は、既存のECSスケジューラに対する改善のフィードバックや、コミュニティによって作られた新しいスケジューラ達についての学びをとても楽しみにしています。
 

 

コメント

Twitter, Facebook

このブログの最新情報はTwitterFacebookでもお知らせしています。お気軽にフォローください。

2018年4 月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30