クラスタオートスケーラとビンパッキング
ノードを増やしすぎず減らしすぎない判断を原理から。PendingポッドのリソースリクエストからノードグループとAZを選ぶ仕組み、ビンパッキングによる集約とコスト最適化、PDBやローカルストレージを守る安全なスケールダウンまで掴めます。
- 1.クラスタオートスケーラはスケジュール不能なPendingポッドの『リソースリクエスト』を起点にノードを追加し、どのノードへ移しても要求が満たせる過少利用ノードを退避させて削除する。実使用量ではなくリクエストで判断するのが肝。
- 2.ノードグループ選択はexpander(least-waste/most-podsなど)で行い、各候補グループへ仮想ノードを足して全Pendingが載るかをシミュレートする。AZ選択はリクエストのzoneアフィニティとASGのバランシングに従う。
- 3.スケールダウンはPDB(最小可用数)・ローカルストレージ・退避不能ポッドを尊重し、退避先がある場合のみ実行する。ビンパッキングで少数の大ノードへ集約すると断片化が減りコストは下がるが、可用性とのトレードオフになる。
なぜ「使用量」ではなく「リクエスト」で増減するのか
クラスタオートスケーラ(Cluster Autoscaler、以下 CA)は、Kubernetes のノード台数を需要に合わせて増減させるコンポーネントです。ここで最初に押さえるべき原理は、CA がノード追加の判断に使うのは CPU/メモリの実使用量ではなく、ポッドの「リソースリクエスト」だという点です。これは Horizontal Pod Autoscaler(HPA)がポッド数を実メトリクスで増やすのとは別の層であり、両者はしばしば連携します(/devops/autoscaling-control-theory/)。
なぜリクエストなのか。Kubernetes のスケジューラがポッドをノードへ配置できるかを判定する材料が、まさに各コンテナの requests だからです。スケジューラはノードの 割り当て可能量(allocatable)から既存ポッドのリクエスト合計を引いた残り にリクエストが収まるかでフィルタします。実使用が低くてもリクエストが大きければ「空きがない」とみなされ、ポッドは Pending(スケジュール不能) のまま残ります。CA はこの Pending ポッドを検知してノードを足すので、判断の通貨は終始リクエストです。
リクエストを過大に設定すると、実使用は低いのにスケジューラが「満杯」と判断し、CA は不要にノードを増やして無駄なコストを払います。逆に過小だと一台へ詰め込みすぎ、実使用がノード容量を超えて OOMKill や CPU スロットリングを招きます。CA の挙動はリクエストの正しさに全面依存するため、VPA(Vertical Pod Autoscaler)や実測に基づくリクエスト調整は、オートスケーリング全体の前提条件です(/devops/capacity-planning/)。
ノード追加:PendingポッドからノードグループとAZを選ぶ
CA のスケールアップは一定間隔(既定は数秒〜十数秒)のループで動きます。流れは次のとおりです。
スケールアップの判断ループ(概念)
1. スケジュール不能なPendingポッドを集める
(リソース不足・アフィニティ・taint等で載らないもの)
2. 各ノードグループ(ASG/MIG/ノードプール)について:
そのグループの「テンプレートノード」を1台仮想的に追加し、
Pendingポッドがそのノードへスケジュールできるかを
スケジューラと同じ述語(predicate)でシミュレート
3. 載せられる候補グループを expander で1つ選ぶ
4. 選んだグループの希望台数(desired)を増やす
→ クラウド側がノードを起動し、kubeletがNotReady→Readyへ
鍵は 2 のシミュレーション です。CA は各ノードグループの代表的なノード(既存ノードかテンプレート)を雛形に、「このグループを1台増やしたら、Pending ポッドのうち何個が載るか」をスケジューラと同じ述語(taint/toleration、nodeSelector、ノードアフィニティ、トポロジ分散制約、リソース残量など)で評価します。つまり CA は スケジューラの判断を先読みで再現 しているため、ポッドが要求するアフィニティや zone 制約はそのままノードグループ選択に効きます。
複数のノードグループが候補になったとき、どれを増やすかを決めるのが expander です。
| expander | 選び方 | 向くケース |
|---|---|---|
| random | 候補からランダムに選ぶ | 差が小さく単純化したい |
| least-waste | 追加後の余り(無駄)リソースが最小のグループ | ビンパッキング重視・コスト最適化 |
| most-pods | 最も多くのPendingを載せられるグループ | 一度に大量のPendingを捌きたい |
| price | 起動コストが最も安いグループ(クラウド連携) | スポット/インスタンス種別の価格差を使う |
| priority | ユーザー定義の優先順位リストに従う | 特定プールを優先したい運用 |
AZ(可用性ゾーン)の選択 は二段構えで決まります。第一に、ポッドが zone のノードアフィニティやトポロジ分散制約(topologySpreadConstraints)を持つなら、CA はそれを満たせるノードグループだけを候補にします。第二に、ノードグループが複数 AZ にまたがる単一の ASG/MIG の場合、どの AZ に実際のノードが立つかはクラウド側のバランシング(例: AWS の Balanced across AZs)に委ねられ、CA は台数だけを指示します。ゾーンごとの厳密な制御が要るなら、AZ ごとにノードグループを分けるのが定石です。ステートフルワークロードで EBS のような ゾーン固定のボリュームを使う場合、ボリュームのある AZ にしかノードを立てられないため、この分割はとくに重要です。
Pending が解消されない時、まず疑うのは「どのノードグループのテンプレートにも載らない」ケースです。要求リクエストが最大インスタンス種別の allocatable を超える、対応する taint への toleration が無い、zone 制約を満たすグループが上限(max)に達している、などが代表です。CA はログに「ポッドが載らない理由」を出すので、シミュレーションがどの述語で落ちたかを読むのが近道です。
ビンパッキングによる集約とコスト最適化
CA とスケジューラが目指すのは、少ない台数のノードへポッドを隙間なく詰めることです。これは古典的な ビンパッキング問題(容量の決まった箱に、できるだけ少ない箱数で品物を詰める)に対応します。ビンパッキングは NP 困難なので、実装は厳密最適ではなく ヒューリスティック(first-fit、best-fit など近似)でリクエストを配置します。
集約が効く理由は単純です。各ノードには kubelet・OS・DaemonSet などの 固定オーバーヘッドがあり、台数が増えるほどこのオーバーヘッドの総和が膨らみます。同じワークロードを少数の大きなノードに集約すれば、オーバーヘッドの本数が減り、断片化(fragmentation)——どのノードにも中途半端な空きが残ってポッドが載らない状態——も減って、結果としてノード台数=コストが下がります。least-waste expander や、後述する デフラグ(consolidation) はこの集約を能動的に進めます。
断片化の例(各ノードのallocatable=8、ポッドのリクエスト=5)
詰め方A: ノード1[5+空き3] ノード2[5+空き3] → 2台、空き計6(無駄)
詰め方B: もしリクエストが3なら 1ノードに[3+3+空き2] と集約可
※ リクエスト粒度とノード容量の比が断片化量を決める
ただしビンパッキングを攻めるほど 可用性とのトレードオフが顕在化します。少数の大ノードへ集約すると、1台の障害で同時に失うポッド数が増え、ブラスト半径が大きくなります。これを抑えるのがトポロジ分散制約(同一サービスのレプリカを複数ノード/AZへ散らす)で、集約(密に詰めたい)と分散(散らして守りたい)は本質的に綱引きします。CA はこの分散制約もシミュレーションに織り込むため、分散を強く要求すれば、空きがあっても新ノードが立つことがあります。
ノードの allocatable に対してポッドのリクエストが大きい(例: 容量の40%を1ポッドが占める)と、2個で容量超過し1個ぶんの空きが必ず余る——断片化が構造的に避けられません。逆にリクエストが容量に対し十分小さいと隙間なく詰めやすい。インスタンス種別の選定(大きすぎても小さすぎても損)は、扱うポッドのリクエスト分布とセットで考えるのが正解です。最近は CA の代替として Karpenter のように、Pending のリクエストから「ちょうど良いインスタンス種別」を都度選んでプロビジョニングする方式も使われ、ノードグループ固定の制約を緩めます。
スケールダウン:過少利用ノードを安全に畳む
スケールダウンはスケールアップより慎重さが要ります。CA は各ノードを定期的に評価し、一定時間(既定はおよそ10分)連続して利用率がしきい値(既定でリクエスト合計が割り当て可能量の50%)を下回るノードを削除候補にします。ここでも判断は実使用ではなくリクエスト合計です。
削除の絶対条件は、そのノード上の全ポッドを、他の既存ノードへ退避(reschedule)できることです。CA は削除候補ノードを空にしたと仮定し、載っていたポッドを他ノードへ再配置できるかをシミュレートします。退避先が無ければ削除しません——畳んだ瞬間にポッドが Pending になって、すぐスケールアップし直す「フラッピング」を避けるためです。退避できると判明したら、ノードを cordon(新規スケジュール停止)→ drain(退避)→ 削除 の順で安全に畳みます(/devops/graceful-shutdown-draining/)。
ここで決定的に効くのが、退避を阻む二つの要因です。
- PodDisruptionBudget(PDB): サービスごとに「同時に減らしてよいレプリカ数」の下限を宣言する仕組みです。
minAvailable: 2なら、退避によって可用レプリカが2を割る操作はブロックされます。CA はこの 自発的退避(voluntary disruption)の予算を尊重し、PDB を破る退避はしません。PDB が厳しすぎる(例: 3レプリカで minAvailable=3)と、退避が常にブロックされてノードが永久に畳めなくなります。 - ローカルストレージ(emptyDir / hostPath): ノードのローカルディスクにデータを持つポッドは、別ノードへ移すとそのデータが失われます。CA は既定でローカルストレージを使うポッドが載ったノードを削除しません。明示的に許可する場合は、消えても良いと宣言するアノテーション(
safe-to-evict系)を付ける必要があります。
| 退避を阻む要因 | CAの既定の扱い | 畳むための対処 |
|---|---|---|
| PDBで可用数が下限に達する | 退避をブロックしノード保持 | PDBを現実的な値に、レプリカ数に余裕を |
| emptyDir/hostPathを使う | ノード削除を回避 | safe-to-evictアノテーションで明示許可 |
| kube-systemの単一ポッド | 退避不能として保持 | PDB付与か、別ノードへ複製可能に |
| safe-to-evict=false 指定 | ユーザー意思を尊重し保持 | 意図的。畳ませたいなら外す |
| 退避先ノードが無い | 再Pendingを避け保持 | 他ノードに空きを作る/上限緩和 |
「ノードが余っているのに台数が減らない」障害の二大原因が PDB とローカルストレージです。PDB が minAvailable=レプリカ数 のように退避余地ゼロで設定されていると、CA は永久に退避できず、過少利用ノードがコストを払い続けます。同様に、デフォルトのまま emptyDir を使うポッド(ログ集約のサイドカーやキャッシュなど)が広く撒かれていると、ほぼ全ノードが削除不能になります。スケールダウンが効かない時は、CA ログの「このノードを削除できない理由」を読み、PDB と safe-to-evict を起点に潰すのが定石です。
連携と原則:HPA・スケジューラ・priorityとの噛み合わせ
CA は単独では完結しません。HPA がリクエストの大きいレプリカを増やせば Pending が生まれ CA が追従し、HPA が縮めれば過少利用ノードが生まれ CA が畳む——HPA がポッド軸、CA がノード軸で噛み合います。また、緊急時にすぐ起動できる容量を確保したいなら、**低優先度の「バルーン(pause)ポッド」**で空き容量を予約し、本番ポッドが来たらプリエンプトで押し出して即スケジュール、CA が裏でノードを足す、というオーバープロビジョニング手法が使われます。これは PriorityClass とプリエンプションの仕組みに乗っています。
CA の判断軸が実使用量ではなくリソースリクエストであること、スケールアップはPendingポッドが既存ノードに載らないときに起き、候補ノードグループをテンプレートノードのシミュレーションと expanderで選ぶこと、を即答できると強い。スケールダウンの絶対条件は全ポッドの退避先が存在することで、PDB・ローカルストレージ・safe-to-evictが退避を阻む三要因。HPA はポッド軸・CA はノード軸で、ビンパッキング(NP困難を近似で解く)による集約はコストを下げるが可用性(ブラスト半径)と綱引きする、という対比を押さえること。
まとめ
- CA は リソースリクエストを通貨に判断する。スケジュール不能な Pending ポッドを検知してノードを足し、リクエスト合計が低い過少利用ノードを畳む。実使用量ではない点が肝。
- スケールアップは各ノードグループの テンプレートノードでスケジュール可否をシミュレートし、expander(least-waste/most-pods/price 等)で1グループを選ぶ。AZ はポッドの zone 制約とクラウド側バランシングで決まり、ゾーン固定が要るなら AZ ごとにグループを分ける。
- ビンパッキング(NP 困難を first-fit 等で近似)で少数の大ノードへ集約すると、固定オーバーヘッドと断片化が減りコストが下がる。ただし集約は ブラスト半径=可用性と綱引きし、トポロジ分散制約がそれを調停する。
- スケールダウンの絶対条件は 全ポッドの退避先が存在すること。PDB(最小可用数)・ローカルストレージ・safe-to-evict が退避を阻む三要因で、設定ミスはノードが畳めない静かなコスト漏れになる。
- CA は **HPA(ポッド軸)**と噛み合い、低優先度バルーンポッドで先行容量を予約する手法とも組み合わさる。判断はすべてリクエストとスケジューラ述語の先読みに根ざす。
DevOps/インフラ Article
クラスタオートスケーラとビンパッキングを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Kubernetes
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
ノードグループ選択はexpander(least-waste/most-podsなど)で行い、各候補グループへ仮想ノードを足して全Pendingが載るかをシミュレートする。AZ選択はリクエストのzoneアフィニティとASGのバランシングに従う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Kubernetes / オートスケーリング」に近いか確認する。
- 強みである「クラスタオートスケーラはスケジュール不能なPendingポッドの『リソースリクエスト』を起点にノードを追加し、どのノードへ移しても要求が満たせる過少利用ノードを退避させて削除する。実使用量ではなくリクエストで判断するのが肝。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。