/var/www/yatta47.log

/var/www/yatta47.log

やったのログ置場です。スクラップみたいな短編が多いかと。

ECSローリングアップデートのminimumHealthyPercentとmaximumPercentの挙動を図解

ECSローリングアップデートの minimumHealthyPercentmaximumPercent、「タスク数の上下限でしょ」って思ってたんですけど、実際は旧タスクと新タスクのどっちが先に動くかを決めてるパラメータなんですよね。

この2つの組み合わせを正確に理解しておかないと、デプロイのたびにダウンタイムが出たりリソースが枯渇したりします。

minimumHealthyPercentとmaximumPercentの正体

数字を見ると「何台まで許すか」という量の話に見えるんですが、考えてみるとこの制約値が「操作の順序」を決めてるんですよね。

  • minimumHealthyPercent(min)= 旧タスクを落とすタイミングを決める
  • maximumPercent(max)= 新タスクを立てられる量を決める

minが100なら「desired countを下回るな」という制約になるので、旧を落とす前に新を立てないといけない。

minが100未満なら「一時的にdesired countを下回ってもいい」ので、旧を先に落とせる。

つまりminはタスク数の下限を設定するパラメータなんですが、その制約が結果として「どっちを先に動かすか」という操作の順序を決めてるんです。

代表的な4パターンの挙動をdesired count=4で図解

min=100, max=200 — "新を先に全部立てる"パターン
──────────────────────────────────────────
[旧1][旧2][旧3][旧4]                    ← 最初の状態
[旧1][旧2][旧3][旧4][新1][新2][新3][新4] ← 新を4台追加(max=200%で8台OK)
                     [新1][新2][新3][新4] ← 旧を全部落とす
ピーク: 8台  最低: 4台  ダウンタイム: なし

min=50, max=200 — "旧を先に半分落とす"パターン
──────────────────────────────────────────
[旧1][旧2][旧3][旧4]         ← 最初の状態
[旧1][旧2]                   ← 旧を半分落とす(min=50%で2台OK)
[旧1][旧2][新1][新2][新3][新4]← 新を立てる
           [新1][新2][新3][新4]← 残りの旧を落とす
ピーク: 6台  最低: 2台  ダウンタイム: なし

min=100, max=150 — "交互に入れ替える"パターン
──────────────────────────────────────────
[旧1][旧2][旧3][旧4]         ← 最初の状態
[旧1][旧2][旧3][旧4][新1][新2]← 新を2台追加(max=150%で6台OK)
[旧3][旧4][新1][新2]         ← 旧2台落とす
[旧3][旧4][新1][新2][新3][新4]← 新2台追加
           [新1][新2][新3][新4]← 旧2台落とす → 完了
ピーク: 6台  最低: 4台  ダウンタイム: なし  ラウンド: 2回

min=0, max=100 — "全落とし→全立て"パターン
──────────────────────────────────────────
[旧1][旧2][旧3][旧4]         ← 最初の状態
                              ← 全部落とす(min=0%でOK)
[新1][新2][新3][新4]         ← 新を全部立てる
ピーク: 4台  最低: 0台  ダウンタイム: あり

min=0, max=100 だけはダウンタイムが発生します。dev/stagingで意図的にやるなら問題ないですが、productionで設定していると痛い目を見ます。

どのパターンを選ぶか

迷ったときの判断フローはこうです。

ダウンタイムを許容できる?
  ├─ Yes → min=0, max=100(シンプル。dev/staging向き)
  └─ No
      │
      リソースに余裕がある?(タスク数を一時的に2倍にできる?)
        ├─ Yes → min=100, max=200(最速。B/Gに近い挙動)
        └─ No
            │
            一時的にタスク数を減らせる?
              ├─ Yes → min=50, max=100〜150(リソース節約型)
              └─ No → min=100, max=150(交互入替。ラウンド数増えるが安全)

productionで迷うなら min=100, max=200 を基本にして、リソースが厳しいなら min=100, max=150 に落とすのが安全側の選択です。

deregistrationDelayが遅延の主犯になりやすい

min/maxを最適化してもデプロイが遅いと感じたら、犯人は deregistrationDelay であることが多いです。

デフォルトは300秒(5分)。ALBのTarget Groupに設定されていて、旧タスクがderegisterされてからコネクションが切れるまでの待ち時間です。

ほとんどのHTTPサービスでは30〜60秒で十分です。旧タスクのdrainは並行して走ることが多いですが、デフォルト300秒の待ち時間がデプロイ全体を律速する要因になりやすい。ここを60秒に下げるだけでデプロイ時間が劇的に短縮されます。

Circuit Breakerは保険として必ず入れておく

deploymentCircuitBreaker を有効にしておくと、新タスクがRUNNING状態に遷移できない場合(コンテナ起動失敗、リソース不足、ヘルスチェック失敗など)に自動ロールバックしてくれます。

{
  "deploymentConfiguration": {
    "minimumHealthyPercent": 100,
    "maximumPercent": 200,
    "deploymentCircuitBreaker": {
      "enable": true,
      "rollback": true
    }
  }
}

rollback: true のセットを忘れがちなので注意です。Circuit Breakerがない場合、壊れたデプロイが延々とリトライし続けて「デプロイが終わらない」状態になります。

まとめ

minimumHealthyPercentmaximumPercent はタスク数の上下限を設定するパラメータですが、その制約が結果として「旧と新の入れ替え順序」を決めています。

minが100かどうかで「新が先か旧が先か」が変わる。maxが何パーセントかで「一度に何台立てられるか」が変わる。この2軸の組み合わせで代表的なパターンが整理できます。

Kubernetesの maxSurge / maxUnavailable も同じ構造なので、この考え方はECSに限らず使えます。

参考