Help us understand the problem. What is going on with this article?

ECSのクラスタを安全に縮退する

More than 3 years have passed since last update.

あんまりEC2ノードごと増やしたり減らしたりってのは、やらないで済むに越したことはないんですが、お金に限りがある世界では、無駄に何十台もサーバー立ててられないので、適宜減らしたりしたいわけです。

増やす分には、Serviceのdesired countを上げて、Autoscaling Group(以下ASG)のdesired capacityを上げればほっとけば勝手に収束してくれるので、特に問題ありません。

問題は減らす場合で、ASGのcapacityを単純に下げると、どのノードがterminateされるかを厳密にコントロールすることができません。
そうすると、現在タスク稼動中のノードが落ちたりして無駄なコンテナの上げ下げが発生したり、いざログの転送とかが詰まってたりすると、データロストの危険が高まって心理的に余り嬉しくない。

というわけで、順番に行儀良く落とす方法を考えてみました。

まずECSからderegisterが可能なコンテナインスタンスの一覧を取得する。
実は割とめんどくさくて二種類のAPIコールが必要。

def fetch_container_instances
  arns = []
  resp = nil
  loop do
    options = {cluster: cluster}
    options.merge(next_token: resp.next_token) if resp && resp.next_token
    resp = client.list_container_instances(options)
    arns.concat(resp.container_instance_arns)
    break unless resp.next_token
  end

  chunk_size = 50
  container_instances = []
  arns.each_slice(chunk_size) do |arn_chunk|
    is = client.describe_container_instances(cluster: cluster, container_instances: arn_chunk).container_instances
    container_instances.concat(is)
  end

  container_instances
end

ここから、稼働中のタスクが0のインスタンスを探す。

container_instances = fetch_container_instances
deregisterable_instances = container_instances.select do |i|
  i.pending_tasks_count == 0 && i.running_tasks_count == 0
end

こいつらが、登録解除しても問題ないインスタンスなので、まずこいつらをクラスターからderegisterします。

diff = current_desired_capacity - target_desired_capacity
deregistered_instance_ids = []
deregisterable_instances.each do |i|
  break if deregistered_instance_ids.size >= diff

  begin
    ecs_client.deregister_container_instance(cluster: service_config.cluster, container_instance: i.container_instance_arn, force: false)
    deregistered_instance_ids << i.ec2_instance_id
  rescue Aws::ECS::Errors::InvalidParameterException
  end
end

force: falseにしておくのが大事です。
そうしておくと、タイミングの問題で何らかのタスクが動き始めてしまった場合はエラーにすることができます。

実際に登録が解除できたインスタンスIDのものを、まずASGからdetachしてから、terminateします。

client.detach_instances(
  auto_scaling_group_name: name,
  instance_ids: deregistered_instance_ids,
  should_decrement_desired_capacity: true
)

sleep 1

ec2_client.terminate_instances(instance_ids: deregistered_instance_ids)

ASGからdetachする時にshould_decrement_desired_capacity: trueとセットしておくと、detachした数の分だけdesired capacityを減らしてくれます。
これで任意のインスタンスをASGから安全に除外しつつdesired capacityを調整できます。
その後で、terminateして完了です。

一応、これで安全に落とせるのですが、ネットワークの通信不良などで、どこかの処理がエラーになった場合、孤立したインスタンスが残ることになります。

なので、ECSのクラスタに所属しているインスタンスのIDと対応するASGに所属しているインスタンスのIDを比較して、ECSから接続が切れているのに稼動しっぱなしになっているインスタンスを落としたり、タグでインスタンスを探してASGから外れてるインスタンスを落とす等の処理が必要になるかもしれません。

ASGの設定でインスタンスの削除保護を有効にしておけば、事故る可能性はほぼ無くなるので、それと組み合わせれば、安全にECSのクラスタを縮退させられると思います。
なんか節約のために無駄な苦労を負っている気がしますが……。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away