ECSでオートスケーリングを設定/実装するために必要だった事の単なるメモです。
何かの役に立てたら幸いです。

スケールアウト

EC2のスケールアウトと同様に、CloudWatchのアラームをトリガーにしてスケールさせます。
スケール対象がオートスケーリンググループではなく、ScalableTargetを介してECSサービスのDesiredCountを変更するため、CloudFormationのテンプレートはEC2と若干異なります。

{
  "ServiceScalingTarget": {
    "Type": "AWS::ApplicationAutoScaling::ScalableTarget",
    "Properties": {
      "MinCapacity": 2,
      "MaxCapacity": 10,
      "ResourceId": { "Fn::Join": ["", [
        "service/", { "Fn::ImportValue": "SomeECSCluster" }, "/", { "Fn::GetAtt": ["SomeECSService", "Name" ]}
      ] ] },
      "RoleARN": { "Fn::GetAtt": ["SomeAutoScalingRole", "Arn"] },
      "ScalableDimension": "ecs:service:DesiredCount",
      "ServiceNamespace": "ecs"
    },
    "DependsOn": "SomeECSService"
  },
  "ServiceScalingOutPolicy": {
    "Type": "AWS::ApplicationAutoScaling::ScalingPolicy",
    "Properties": {
      "PolicyName": "SomeServerScalingOutPolicy",
      "PolicyType": "StepScaling",
      "ScalingTargetId": { "Ref": "ServiceScalingTarget" },
      "StepScalingPolicyConfiguration": {
        "AdjustmentType": "PercentChangeInCapacity",
        "Cooldown": 60,
        "MetricAggregationType": "Average",
        "StepAdjustments": [
          { "MetricIntervalLowerBound": 0, "ScalingAdjustment": 200 }
        ]
      }
    }
  },
}

EC2の場合と異なりスケールアウトする際のデプロイが必要ないため、DesiredCountを変更するだけの簡単なお仕事です。
EC2の場合はホスト数を変更する以外に以下の実装が必要になります。

  • スケールアウト時にCodeDeployなどを使ってデプロイする処理
  • デプロイ失敗時の処理
  • デプロイ成功後にELBへアタッチする処理

スケールイン

基本的にスケールアウトと同様に、CloudWatchのアラームをトリガーにしてスケールさせます。

ECSサービスがスケールインされる際にコンテナは停止されようとしますが、停止されるまでの最大待ち時間はデフォルト30秒なので注意が必要な場合があります。
もしコンテナを停止させるまで30秒以上かかる可能性がある場合は、ECS Agentに環境変数ECS_CONTAINER_STOP_TIMEOUTを渡し、待ち時間を変更する必要があります。

参考: Amazon ECS Container Agent Configuration - Amazon EC2 Container Service http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html

コンテナ停止に関係する処理(スケールイン、デプロイなど)が一様に遅くなるため、終了までの時間は短く維持できるように心がけた方がよさそうです。

補足: デプロイ時に古いコンテナは新しいコンテナと交換される形で停止されるため、デプロイ速度にも影響が出ます。(コンテナは更新できない仕様)

コンテナ停止待ちによるコンテナ実行待ち

コンテナ停止待ち時間を長くする事による、デメリットの1つです。

まずECSの仕様として、「停止待ち状態」に移行したコンテナは、他のコンテナをホストにアサインする際のリソース消費量に関与しなくなるという振舞いがあります。
そのため、ホストの空きリソースがあまりない状況下では、デプロイしたコンテナが「実行待ち状態」のまま停止してしまって見える現象が起こりえます。

状況としては以下のようになっています。

  • コンテナをホストにアサインするためのリソースは足りていたため、コンテナがホストにアサインされた。
  • コンテナを実行するためのリソースがホストには不足していたため、「実行待ち状態」で待機している。
# 状態としては以下のようなイメージ
ホスト (2048MiB)
- コンテナ1 (512MiB) # 停止待ち
- コンテナ2 (512MiB) # 実行中
- コンテナ3 (512MiB) # 実行中
- コンテナ4 (512MiB) # 実行中
- コンテナ5 (512MiB) # 実行待ち

この「停止待ち状態」は、最大で「コンテナ停止待ち時間」と同じ時間だけ継続されます。

ホストのスケールアウト

ECSとEC2はお互いに殆ど関与していません。
ECSサービスがスケールアウトしようとしている時に、ECSクラスタ上に十分なリソースがなければスケールアウトできません。

ECSクラスタのリソースが不足しないように、クラスタに参加しているEC2インスタンスもスケールアウトさせる必要があります。

閾値の計算方法

基本的には以下の計算方法でスケールアウトの閾値を決定する事が出来るかと思います。

閾値 = 1 - MAX(クラスタ内のコンテナの必要リソース) / ホストのリソース

  • コンテナ1: メモリ512MiB
  • コンテナ2: メモリ256MiB
  • ホスト: 8GiB
1 - 512MiB / 8GiB = 0.9375 = 93.75%

この例だと、メモリの使用量が93.75%を上回らないようにホストを追加するようにすれば、リソースが足りずにECSサービスがスケールアウトできない状態を殆どなくす事ができます。

EC2インスタンスの起動時間があるため、閾値を85%などに下げ、早めにホストを追加する事によって、コンテナ起動までの待ち時間を最小限にできる。

ホストのスケールイン

スケールアウトの時と同様に、EC2はECSに関与しないので、コンテナが起動していようがEC2インスタンス(ホスト)がシャットダウンしてしまいます。
リクエストを捌いている最中のコンテナが停止してしまったらもちろんレスポンスは返せません。困りましたね。

これを解決するためには、ホストがスケールインによってシャットダウンされる前に、ホスト内の全てのコンテナを停止させ、ホスト内の全てのコンテナの停止を待機するよう実装する必要があります。
AWSではこの事を「コンテナインスタンス(ホスト)のドレイニング」と呼ぶようです。

ホストのドレイニング

AutoScalingのライフサイクルフック, AWS SNS, AWS Lambda を組み合わせた方法がAWSの公式ブログにまとめてあり、作業量は多めですが参考にしてうまくいきました。

参考: Amazon ECS におけるコンテナ インスタンス ドレイニングの自動化方法 | Amazon Web Services ブログ https://aws.amazon.com/jp/blogs/news/how-to-automate-container-instance-draining-in-amazon-ecs/