15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

あんまり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のクラスタを縮退させられると思います。
なんか節約のために無駄な苦労を負っている気がしますが……。

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?