Category: Amazon SNS


Amazon ECS におけるコンテナ インスタンス ドレイニングの自動化方法

同僚のMadhuri Periが素晴らしい記事を書いてくれました。AutoScalingグループのクラスタをスケールダウンする際にインスタンスからタスクを事前に削除するために、コンテナ インスタンス ドレイニングを利用する方法です。

—–

Amazon ECSクラスタでは、クラスタからインスタンスを削除する必要があるタイミングというのがいくつかあります。例えば、システムを更新するとき、Dockerデーモンを更新するとき、あるいはクラスタのサイズをスケールダウンするときなどです。コンテナ インスタンス ドレイニング機能によって、クラスタ上のタスクに影響を与えることなく、コンテナインスタンスを削除することができます。この機能により、コンテナインスタンスがDRAINING状態である間はそのインスタンスに対して新しいタスクの配置がスケジュールされないようになり、利用可能なリソースがあればサービスがタスクをクラスタ上の他のコンテナインスタンスに移動してくれ、インスタンスを削除する前にタスクの移動が成功したことを待機できるようになります。

コンテナインスタンスの状態は、手動でDRAININGに変更することが可能です。しかしこの記事では、これらのプロセスを自動化するためにAutoScalingグループAWS Lambdaを利用してコンテナ インスタンス ドレイニングを行う方法を説明します。

Amazon ECS オーバービュー

Amazon ECSはコンテナ管理サービスです。クラスタやEC2インスタンスの論理グループ上でDockerコンテナの実行、停止、そして管理を容易にしてくれます。ECSを使ってタスクを実行するとき、タスクはクラスタに配置されます。Amazon ECSは指定されたレジストリからコンテナイメージをダウンロードし、そしてそのイメージをクラスタ内のコンテナインスタンス上で実行します。

コンテナ インスタンス ドレイニングの状態を扱う

AutoScalingグループはライフサイクルフックをサポートしています。ライフサイクルフックは、インスタンスの起動や削除の前に独自の処理を完了するために呼び出されます。今回の例では、ライフサイクルフックは、2つの処理を実行するLambdaファンクションを呼び出します。

  1. ECSコンテナインスタンスの状態をDRAININGに変更します。
  2. コンテナインスタンス上にタスクが1つも残っていないことを確認します。もしドレイニング中のタスクがまだ存在する場合は、Lambdaファンクションを再度呼び出すためにSNSにメッセージを送信します。

コンテナインスタンス上で実行中のタスクがなくなるか、あるいはライフサイクルフックのハートビートタイムアウト(サンプルのCloudFormationテンプレートではTTL15分に設定)に達するか、どちらかの状態になるまでLambdaによってステップ2が繰り返されます。その後、制御はオートスケーリングのライフサイクルフックに戻され、そのインスタンスは削除されます。このプロセスを次の図に示します。

Architecture

試してみましょう!

この記事で説明した一連のリソースをセットアップするためにCloudFormationテンプレートを使用します。このCloudFormationテンプレートを使用するには、あなたのアカウントのS3バケットにLambdaデプロイメントパッケージをアップロードする必要があります。このテンプレートは次のリソースを作成します。

  • VPCと関連するネットワーク要素(サブネット、セキュリティグループ、ルートテーブルなど。)
  • ECSクラスタ、ECSサービス、そしてサンプルのECSタスク定義
  • 削除のライフサイクルフックと2つのEC2インスタンスが設定されたAutoScalingグループ
  • Lambdaファンクション
  • SNSトピック
  • Lambdaを実行するために必要なIAMロール

CloudFormationスタックを作成し、インスタンスの終了イベントをトリガーすることによってどのようにこのスタックが機能するのか見ていきます。

Amazon EC2のコンソールにおいて、AutoScalingグループを選択し、CloudFormationによって作成されたAutoScalingグループの名前(CloudFormationテンプレートのリソースのセクションから)を選択します。

操作編集を選択し、インスタンスの希望の数を “1” に減らすようにサービスを更新します。これによって、2つのインスタンスのどちらか一方で終了プロセスが開始されます。

AutoScalingグループのインスタンスタブを選択します。1つのインスタンスのライフサイクルの状態が “Terminating:Wait” という値を示すはずです。

asg-draining
この状態になると、ライフサイクルフックが発火してSNSにメッセージが送信されます。そして、SNSメッセージトリガーに反応してLambdaファンクションが実行されます。

Lambdaファンクションによって、ECSコンテナインスタンスの状態がDRAININGに変更されます。その後、ECSサービススケジューラによってこのインスタンス上のタスクは停止され、利用可能なインスタンス上でタスクが起動されます。

ECSのコンソールに移動すれば、コンテナインスタンスの状態がDRAININGになっていることを確認できます。

ecs-draining
タスクが全て完了すると、AutoScalingグループのアクティビティ履歴でEC2インスタンスの削除を確認できます。

events-draining

どのように動作しているか

少しLambdaファンクションの内部的な動作を見てみましょう。ファンクションはまず最初に、受け取ったイベントのLifecycleTransitionの値が autoscaling:EC2_INSTANCE_TERMINATING にマッチするかをチェックします。

# もし受け取ったイベントがインスタンス終了中ならば・・・
if 'LifecycleTransition' in message.keys():
    logger.debug("message autoscaling %s",message['LifecycleTransition'])
    if message['LifecycleTransition'].find('autoscaling:EC2_INSTANCE_TERMINATING') > -1:

マッチする場合は “checkContainerInstanceTaskStatus” という関数が呼び出されます。この関数は、受け取ったEC2インスタンスIDのコンテナインスタンスIDを取得しコンテナインスタンスの状態を ‘DRAINING’ に変更します。

# ライフサイクルフックの名前を取得
lifecycleHookName = message['LifecycleHookName']
print("Setting lifecycle hook name {} ".format(lifecycleHookName))

# インスタンス上で実行中のタスクがあるかをチェック
tasksRunning = checkContainerInstanceTaskStatus(Ec2InstanceId)

続いてインスタンス上で実行中のタスクがあるかがチェックされます。実行中のタスクが存在する場合、このLabmdaファンクションを再度トリガーするためにSNSトピックにメッセージが発行され、このLambdaファンクションのプロセスは終了となります。

# タスクの詳細を得るためにタスクARNを使用
descTaskResp = ecsClient.describe_tasks(cluster=clusterName, tasks=listTaskResp['taskArns'])
for key in descTaskResp['tasks']:
    print("Task status {}".format(key['lastStatus']))
    print("Container instance ARN {}".format(key['containerInstanceArn']))
    print("Task ARN {}".format(key['taskArn']))

# 実行中のタスクがあるかチェック
if len(descTaskResp['tasks']) > 0:
    print("Tasks are still running..")
    return 1
else:
    print("NO tasks are on this instance {}..".format(Ec2InstanceId))
    return 0

Lambdaファンクションは、コンテナインスタンス上に実行中のタスクがない時には、ライフサイクルフックを終了しこのEC2インスタンスの削除に進みます。

# ライフサイクルフックの完了
try:
    response = asgClient.complete_lifecycle_action(
    LifecycleHookName=lifecycleHookName,
    AutoScalingGroupName=asgGroupName,
    LifecycleActionResult='CONTINUE',
    InstanceId=Ec2InstanceId)
    print("Response = {}".format(response))
    print("Completedlifecycle hook action")
except Exception, e:
    print(str(e))

 

まとめ

コンテナ インスタンス ドレイニングによって、クラスタのスケールダウンや新しいAMIのロールアウトのような運用業務がシンプルになります。例えばこの記事で説明された構成に加えて、CloudFormationやCodePipelineを利用して、新しいインスタンスの起動や終了を一括実行するローリングデプロイメントの環境を構築することもできます。

コンテナ インスタンス ドレイニングについてもっと知りたい場合は、Amazon ECS 開発者ガイドを参照してください。

ご質問やご提案があれば、ぜひ以下にコメントしてください。

(翻訳はSA畑が担当しました。原文はこちら)