K8s 中級 #6 オートスケーリング — HPA / VPA / Cluster Autoscaler

読了 24分

K8s 中級シリーズの 6 番目の記事です。#5 までの流れは 1 つの Pod がどう立っているかの話でした。#4 で 1 つの Pod がどれくらいのリソースを要求してどこまで使えるかを定め、#5 でその Pod が生きているか・トラフィックを受ける準備ができているか・ゆっくり立ち上がっているかを 3 種類の probe で表現しました。単一 Pod のモデルまではそこで仕上がります。しかし運用の負荷は単一 Pod モデルの上で揺れます — ランチタイムにトラフィックが 2 倍に跳ね上がり、深夜には 1/10 に減り、マーケティングキャンペーンが回る 1 日は 1 日中普段の 5 倍です。この変動を人が毎回 kubectl scale deployment ... --replicas=... で追いかける運用は長持ちしません。この記事ではその空白を埋める 3 つの次元の自動調整HPA / VPA / Cluster Autoscaler を 1 編にまとめます。

このシリーズは K8s 中級 7 編です。

オートスケーリングが解いてくれること #

運用クラスタで負荷が変動するパターンは通常次の 3 つのうち 1 つです — 時間帯別変動(昼・夜)、イベント性急増(キャンペーン・セール・ニュース)、そしてワークロード追加に伴う累積増加。人が手で合わせる運用は通常次のステップを経て限界に達します。

  1. 最初は replicas を余裕を持って取っておけば十分です。普段の 2 倍くらいを常に立てておきます。
  2. 時間が経つとその「余裕のある値」がある時間帯には不足し、ある時間帯には無駄だと気づきます。コストもリソースも両方から漏れています。
  3. 誰かが平日昼・夜・週末の 3 つのスロットに別々の replicas を適用する cron を書きます。しばらくは回ります。
  4. キャンペーンが入ったり外部トラフィックが跳ねたりする事故が一度起こると、人が深夜に起きて kubectl scale を打ちます。すぐにそれが繰り返されます。

K8s がこの問題を表現する方式が 3 つの次元のオートスケーラ です。それぞれが別の軸を自動で調整します。

オートスケーラ何を調整信号対象
HPA (Horizontal Pod Autoscaler)Pod 個数 (replicas)CPU・メモリ使用率、custom metricDeployment / StatefulSet
VPA (Vertical Pod Autoscaler)Pod のリソース要求・上限 (requests / limits)過去の CPU・メモリ使用傾向Deployment / StatefulSet
Cluster Autoscaler (CA)ノード個数Pending 状態の Pod、空いているノードクラウドのノードグループ(ASG / MIG / VMSS)

3 つの軸が相互補完関係にあるという点が重要です。HPA が Pod を増やしてもノードに余裕がなければ新しい Pod は Pending で止まります。そのとき CA がノードをさらに立ててくれます。VPA は別のサイクルで「このワークロードは実はメモリを 1Gi 程度必要とする」という事実を推奨値として教えてくれます。3 つが一緒に回るときにはじめて、負荷変動が人の介入なしに吸収されます。

metrics-server という前提 #

オートスケーリングが回るためには、クラスタ内に 現在のリソース使用量を伝えてくれるコンポーネント がなければなりません。K8s 本体はそのメトリクスを直接持っていません。代わりに標準化されたインターフェース(metrics.k8s.io)を提供し、そのインターフェースを満たすコンポーネントをクラスタに別途インストールする形です。もっとも一般的な実装が metrics-server です。

metrics-server はクラスタの各ノードで kubelet の /metrics/resource エンドポイントを定期的に集めて、ノードと Pod の CPU・メモリ使用量をメモリに持っています。その値を kubectl top や HPA コントローラが API で照会します。

metrics-server がインストールされているか確認
kubectl top nodes
kubectl top pods -A
出力例
NAME        CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node-1      450m         22%    1.8Gi           45%
node-2      320m         16%    1.5Gi           37%

値が見えれば metrics-server が生きていて、error: Metrics API not available のようなメッセージが出れば、インストールされていないか死んでいます。環境別のインストール方法は次のとおりです。

環境metrics-server の状態
minikubeminikube addons enable metrics-server 1 度で有効化
kind直接インストール必要 (kubectl apply -f または Helm)
EKS直接インストール必要。Helm または公式マニフェスト
GKEデフォルト有効
AKSデフォルト有効

EKS は運用クラスタを作った直後には metrics-server が抜けています。HPA・VPA を使うには真っ先にインストールしなければならないコンポーネントです。CPU・メモリ以外にキューの長さ・リクエスト数のような custom metric で HPA を回したいなら、metrics-server の代わりに(または一緒に) Prometheus と Prometheus Adapter、KEDA のようなコンポーネントがその役割を担います — 後で再び見ます。

HPA — Pod 個数を自動で調整 #

もっともよく使われ、もっとも先に導入されるオートスケーラが HPA です。replicas フィールドを人が書く代わりに、メトリクスの平均値を見て K8s が自動で埋めてくれる モデルです。

HPA マニフェスト — CPU 基準 #

もっとも単純な形は CPU 使用率基準です。

hpa-cpu.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

主要フィールドを 1 行ずつ押さえておきます。

  • apiVersion: autoscaling/v2 — HPA の現在の stable バージョンです。v1 は CPU 1 つだけ扱え、単一メトリクスに縛られます。v2 から multi-metric、custom metric、scale up・down 非対称動作(behavior)がすべて可能です。新しいマニフェストはほぼ常に v2 を使います。
  • scaleTargetRef — どのワークロードの replicas を調整するかの対象です。Deployment / StatefulSet / ReplicaSet に付けられます。DaemonSet はノード個数に縛られているので HPA の対象ではありません。
  • minReplicas / maxReplicas — 自動調整の下限と上限です。運用で意図せずに 0 や数百個になる事故を防ぐ安全網です。下限を 1 より大きくすれば可用性、上限を合理的にすればコスト・リソース保護になります。
  • metrics — どんな信号を見て決定するかの配列です。上の例は type: Resource(CPU・メモリのような標準リソース)に target.type: Utilization(使用率)、averageUtilization: 70(平均 70%)です。すべての Pod の CPU 使用率の平均が 70% になるように replicas を合わせます。

minReplicas を 1 ではなく 2 以上にする理由を 1 つ押さえておくと — 1 つの Pod が死んだりアップデートで終了する瞬間にもトラフィックを受ける別の Pod が必要だという可用性の要求です。#5 で readiness probe でトラフィック流入を制御しましたが、その制御は同じ Pod 内の話です。Pod 自体の不在は別の Pod が埋めなければなりません。

HPA アルゴリズム — 比例式 1 行 #

HPA が新しい replicas 値を決める式は単純です。

HPA の desired replicas
desiredReplicas = ceil( currentReplicas * (currentMetricValue / targetMetricValue) )

言葉でほぐすと — 現在の平均が目標の何倍か を見てその比率で Pod 個数を伸ばします。例で見ると明確です。

currentReplicascurrentMetric (CPU 平均)targetMetric計算新 replicas
570%70%5 × 1.0 = 55 (変化なし)
5140%70%5 × 2.0 = 1010
535%70%5 × 0.5 = 2.5 → ceil3
10105%70%10 × 1.5 = 1515

分子に入る使用率(Utilization)の定義が重要です。CPU 使用率は Pod の requests です。ある Pod が requests.cpu: 500m を持っていて実際に 700m を使っているなら使用率は 140% です。

この定義のため #4 と直結する 1 つの罠が生じます — ワークロードに resources.requests がなければ HPA の Utilization メトリクスは動作しません。 分母が定義されないからです。HPA を導入する前に対象 Deployment に CPU・メモリ requests が入っているかから確認しなければなりません。このチェックを抜くと HPA が unknown<unknown> 状態で止まります。

requests なしで回したいなら target.typeUtilization の代わりに AverageValue にして絶対値(例: 200m)を書く道があります。使用率ではなく絶対値基準で比較します。ただしこの形は一般的ではなく、運用の標準は requests + Utilization です。

multi-metric — 複数の信号を同時に見る #

metrics 配列に複数の項目を入れると、HPA は各メトリクスごとに desired replicas を別々に計算し、そのうちもっとも大きな値 を採択します。CPU とメモリを同時に見ると次のとおりです。

hpa-cpu-memory.yaml — metrics 部分
metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

CPU 基準では 5 個で十分なのにメモリ基準では 8 個必要な状況なら、HPA は 8 個を採択します。両方のうちより負担の大きい方に合わせる保守的な選択です。

この形がよく意味を持つワークロードはメモリキャッシュを持っているサービスです。CPU は暇なのにメモリが満ちていくパターンが見えるとき HPA がその信号を見落とさないようにするには、メモリも一緒に見る必要があります。

scale up と scale down の非対称 — behavior #

HPA が 1 つの比率で毎回スムーズに調整してくれるわけではありません。そのまま置くと運用で 2 つの事故が起こります — 負荷が一時的に下がったときに Pod を早く減らしすぎて、再び負荷が上がるときに cold start で応答が跳ねること。そして 負荷が一時的に跳ねたときに Pod を過剰に増やして リソースとコストが漏れること。この 2 つを扱うフィールドが behavior です。

hpa-behavior.yaml — behavior 部分
behavior:
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
      - type: Percent
        value: 100
        periodSeconds: 15
      - type: Pods
        value: 4
        periodSeconds: 15
    selectPolicy: Max
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    selectPolicy: Max

3 つを押さえておきます。

  • stabilizationWindowSeconds — 決定の安定化ウィンドウです。scale down のデフォルト値が 300 秒(5 分)なのが運用の核心安全装置です。CPU が 5 分間ずっと低い値のときだけ本当に減らします。一時的に下がってまた上がる信号では減らしません。scale up は通常 0 秒にして即座に反応させます。
  • policies — 1 回の round でどれくらい変化させるかのポリシーです。Percent(現在個数の比率)と Pods(絶対個数)の 2 種類で、periodSeconds はそのポリシーの周期です。上の例は scale up の場合 15 秒ごとに「現在の 100%(2 倍)または +4 個」のうちより大きい変化を許容します。
  • selectPolicy: Max / Min — 複数の policy のうちどれを採択するか。Max はもっとも攻撃的な変化、Min はもっとも保守的な変化です。

非対称の運用上の意味を 1 行に縮めると — 上げるときは速く、下げるときはゆっくり です。負荷が跳ねて応答時間が伸びる事故はユーザーに即座に見えますが、Pod が 1~2 個多く立っているコストは短時間では微々たるものです。逆に早く減らしすぎると再び増やす間に cold start が発生してユーザーにそのまま見えます。この非対称を behavior で明示的に固めておくパターンが運用の標準です。

behavior 自体を書かないと K8s の合理的なデフォルト(scale up 即座、scale down 5 分安定化)が適用されます。最初に導入するときはデフォルトで始めてもよく、ワークロードの特性に合わせて調整していくのが普通の流れです。

HPA 適用と動作確認 #

HPA 適用と状態確認
kubectl apply -f hpa-cpu.yaml
kubectl get hpa
kubectl describe hpa web
get hpa 出力例
NAME   REFERENCE        TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   55%/70%         2         20        4          5m

TARGETS 列の 55%/70% が現在の平均/目標値です。この値が <unknown>/70% で見えれば metrics-server が生きていないか対象ワークロードに requests がないことです。kubectl describe hpa のイベントセクションに FailedGetResourceMetric のようなメッセージが一緒に出ます。

負荷テストで動作を一度確認するコマンドは次のとおりです。

負荷をかけて scale up を観察
kubectl run load-gen --rm -it --image=busybox -- /bin/sh
# コンテナ内で
while true; do wget -q -O- http://web.default.svc.cluster.local; done

別のターミナルで kubectl get hpa -w で見ると、TARGETS が 70% を越える時点から REPLICAS が比例式どおりに増えていく形が見えます。負荷を止めると 5 分くらい後からゆっくり減ります。

Custom metric と KEDA — CPU・メモリの先 #

CPU・メモリだけでは十分に表現されないワークロードがあります。

  • キューコンシューマ — SQS・Kafka・RabbitMQ からメッセージを受けて処理するワーカー。キューの長さが本当の信号で CPU ではありません。キューが積まれていてもワーカーの CPU は暇なことがあります。
  • API ゲートウェイ — 秒間リクエスト数 (RPS) や同時接続数がリソース使用量よりも直接的な信号。
  • イベントベースワークロード — 仕事があるときだけ回る関数型ワークロード。

これらのワークロードに HPA の CPU 基準だけ適用すると、負荷の本当の変化時点より一拍遅れたり、そもそも信号を見落とします。

Prometheus Adapter #

HPA に CPU・メモリ以外のメトリクスを見せる最初の道が Prometheus Adapter です。クラスタに Prometheus がインストールされていてワークロードたちがメトリクスを公開していれば、Prometheus Adapter はその Prometheus の PromQL 結果を K8s の custom.metrics.k8s.io API として公開してくれます。HPA がそのメトリクスを標準メトリクスのように使えるようになります。

マニフェストの metrics 配列に type: Pods または type: External を書いてどの PromQL の結果を見るかを表現します。深く入る部分は K8s 上級トラックに譲りますが、形だけ見せておくと次のとおりです。

custom metric 例 — 抜粋
metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "100"

各 Pod の平均 RPS が 100 になるように replicas を合わせます。メトリクスの定義 (http_requests_per_second) は Prometheus Adapter の設定ファイルで PromQL として書きます。

KEDA — イベントベースの 0→N #

KEDA(Kubernetes Event-Driven Autoscaling)はもう 1 段階進んだコンポーネントです。HPA ができない 2 つを解いてくれます。

  • 0 → N スケーリング — 標準 HPA の minReplicas は 1 以上でなければなりません。仕事がないときに Pod を完全に 0 に減らせません。KEDA はキューが空いている時間帯にワークロードを 0 に減らし、新しいメッセージが到着すると 1 に立てます。コスト面で大きな差になります。
  • 多様なイベントソースを直接接続 — SQS、Kafka、RabbitMQ、Redis Streams、PostgreSQL、Prometheus など 50 種類を超えるソースを組み込みでサポートします。Prometheus Adapter のように PromQL を書いておかなくても KEDA の ScaledObject マニフェスト 1 枚でキューの長さベースのスケーリングができます。
KEDA ScaledObject — SQS 例の抜粋
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: sqs-worker
spec:
  scaleTargetRef:
    name: sqs-worker
  minReplicaCount: 0
  maxReplicaCount: 30
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/.../my-queue
        queueLength: "10"
        awsRegion: ap-northeast-2

KEDA は内部的に標準 HPA を作って動作します — ScaledObject が適用されると、それに対応する HPA が自動で生成され、KEDA が外部メトリクスを K8s メトリクス API として公開します。標準 HPA の上に 1 層の利便性を載せた形と見ればよいです。キューコンシューマやイベントワーカーが多いクラスタではほぼ標準ツールになりつつあります。

VPA — Pod のリソース要求を自動で調整 #

HPA が「Pod 何個」の次元なら、VPA は「Pod 1 つの大きさ」の次元です。#4 で人が使用量データを見て requests を決めるサイクルを扱いました。VPA はその仕事を自動化しようという試みです — ワークロードの過去の CPU・メモリ使用傾向を見て推奨値を算出し、ポリシーに従ってその値を適用して Pod を再生成します。

3 コンポーネント — recommender / updater / admission-controller #

VPA は単一コントローラではなく、3 コンポーネントの束です。

コンポーネント役割
recommenderメトリクスを集めて推奨 requests 値を算出。VPA オブジェクトの status.recommendation に記録
updaterrecommender の推奨値と現在の Pod の値が大きく食い違うと Pod を evict (再生成させる)
admission-controller新しい Pod が作られるときに mutating admission webhook で推奨値を注入

3 コンポーネントがサイクルを作ります — recommender が推奨値を計算しておき、updater が大きな差を発見して Pod を殺し、新しい Pod が作られるときに admission-controller が推奨値を適用したマニフェストで立てます。人の介入なしに requests がワークロードの実際の使用量に合わせて更新されます。

VPA マニフェストと updatePolicy #

vpa-web.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web
  namespace: default
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 50m
          memory: 64Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi

updateMode の 3 つの値が運用意思決定の核心です。

updateMode動作
"Off"推奨値を算出するだけで適用はしません。人が見てマニフェストに反映
"Initial"Pod が新しく作られる時点だけ推奨値を適用。すでに立っている Pod はそのまま
"Auto" (= Recreate)大きな差が見えると Pod を evict して新しい推奨値で再生成

Auto が自動化の終わりに見えますが運用では慎重でなければなりません。VPA が Pod を evict するということは、その Pod が一度死んでまた立たなければならない という意味です。StatefulSet や単一 replica で立っているワークロード、または #5 の startup probe 時間が長いワークロードでは evict が直に可用性に影響を与えます。

最初に VPA を導入するときはほぼ常に "Off" にするのが標準パターンです。数日・数週間にわたって推奨値を集めて、その値が合理的だと確認してから InitialAuto に移すか、あるいはその推奨値を人がマニフェストに反映して commit します。

VPA 推奨値確認
kubectl describe vpa web
status.recommendation 部分 — 抜粋
Status:
  Recommendation:
    Container Recommendations:
      Container Name:  web
      Lower Bound:
        Cpu:     150m
        Memory:  256Mi
      Target:
        Cpu:     350m
        Memory:  512Mi
      Upper Bound:
        Cpu:     800m
        Memory:  1Gi

Target が核心の推奨値です。Lower BoundUpper Bound は統計的信頼区間と見ればよいです。この推奨値が現在マニフェストの requests と大きく異なるなら、人がその差を検討してマニフェストに反映するのが運用の保守的な方式です。

resourcePolicy の minAllowed / maxAllowed #

上のマニフェストの resourcePolicyminAllowedmaxAllowed は推奨値の上・下限です。この安全網がないと VPA が暇な時間帯の値を見て requests をあまりに小さく推奨したり、一時的なメモリリークパターンを見てあまりに大きく推奨したりする事故になります。運用ではこの 2 つの値をほぼ常に書いておく方が良いです。

VPA がインストールされていないクラスタ #

HPA の metrics-server と異なり、VPA は K8s 本体に含まれていません。EKS・GKE・AKS すべて別途インストールが必要です — 通常 公式 GitHub のマニフェスト または Helm chart でインストールします。GKE だけマネージドオプションを提供します。

HPA と VPA の衝突 — 同じメトリクスに両方かけないこと #

運用でよく見る罠を 1 つ押さえておきます。同じワークロードに CPU 基準 HPA と CPU 基準 VPA を同時にかけると振動が発生します。 理由は単純です。

  1. CPU 負荷が上がります。HPA が比例式どおりに Pod 個数を増やします。
  2. Pod が増えたので Pod あたり平均 CPU 使用量が下がります。
  3. VPA(Auto)がその下がった使用量を見て「requests を減らすべきだ」と判断します。推奨値を下げて Pod を再生成します。
  4. requests が下がったので、同じ使用量を分母で割っても使用率(Utilization)は再び上がります。HPA が再び Pod を増やします。

振動が止まらないサイクルです。回避パターンは 2 つです。

  • HPA と VPA のメトリクスを分離 — たとえば HPA は CPU 基準、VPA はメモリ基準。2 つのサイクルがお互いの分母・分子を揺らさないようにします。
  • VPA は updateMode: "Off" — 推奨値だけ算出して自動適用はしません。人が検討してマニフェストに反映。HPA はそのまま動作。

ほとんどの運用クラスタは 2 つ目のパターンを行きます。HPA が動的な負荷調整を担当し、VPA は推奨値ツールとして置いて人が四半期に 1 回ずつマニフェストに反映します。この分離がもっとも安全な出発線です。

Cluster Autoscaler — ノード次元の調整 #

HPA が Pod を増やしてもその Pod が入るノードリソースがなければ Pod は Pending 状態で止まります。#4 で見たスケジュール可能性の式 — ノードの allocatable からすでに予約された requests の合計を引いた余裕分が新しい Pod の requests 以上でなければならない — が満たされない状態です。この空白を埋めるのが Cluster Autoscaler です。

動作モデル #

CA の動作は 2 方向に単純です。

  • scale upPending 状態の Pod が見えれば、その Pod の requests を受け入れられそうなノードがノードグループに追加されるようにクラウド API を呼びます。AWS なら ASG の desired capacity を増やし、GCP なら MIG、Azure なら VMSS を増やします。
  • scale down — 一定時間にわたって使用率が低いノードがあれば、そのノード上の Pod を別のノードに移してノードを終了します。移せない Pod(例: PV がそのノードだけに付いているケース、cluster-autoscaler.kubernetes.io/safe-to-evict: "false" アノテーションが付いた Pod)があれば、そのノードはそのまま置いておきます。

CA の決定はメトリクスではなく requests とスケジュール可能性 に基づきます。実際の使用量が暇でも requests の合計がノードを満たしていれば新しい Pod は Pending になり、CA はノードを追加します。このモデルが #4 の「requests はスケジューラの本当の通貨」という表現とまったく同じ層です。

クラウド環境別ノードグループ #

CA はクラウド provider と対で回ります。環境別マッピングは次のとおりです。

クラウドノードグループ抽象化備考
AWS EKSAuto Scaling Group (ASG) または EKS managed node groupアベイラビリティゾーンごとに別 ASG 推奨
GCP GKEManaged Instance Group (MIG)デフォルト有効。GKE Autopilot はノード自体が抽象化
Azure AKSVirtual Machine Scale Set (VMSS)AKS クラスタオプションで有効化
オンプレミスCluster API + provider環境による

EKS の場合 CA は通常 Helm chart でインストールします。ASG に適切なタグを付けて CA がその ASG を発見・管理するようにするセットアップが 1 度必要です。GKE はクラスタ作成時にオプション 1 行で有効になります。

Karpenter — EKS のより速い代替 #

CA の設計は「ASG に desired capacity 1 段増しを要求 → その ASG の launch template に従ってノードが作られる → kubelet がクラスタに登録」のサイクルを経ます。ノード spec が ASG に事前に決まっている点が制約です — Pending Pod が大きなメモリを要求するのに ASG のインスタンスタイプが小さなノードしか作れないと、そのノードを立てても Pod が依然 Pending に残る事故が起こります。

Karpenter は EKS 環境で CA のより速い代替として定着しつつあります。Karpenter の違いは 2 つです。

  • Pending Pod を見てノード spec を動的に決定 — 事前定義された ASG ではなく、Pending Pod の requests と toleration にもっとも合うインスタンスタイプをその都度選んで EC2 API で直接立てます。
  • 速いプロビジョニング — ASG の 1 段階を経ないので、ノードが立ってクラスタに合流するまでの時間が通常より短いです。

EKS の新規クラスタでは CA の代わりに Karpenter を導入する流れが増えています。GKE・AKS の同等ツールはまだ EKS の Karpenter ほど定着していません。

CA が動作しないよくある理由 #

CA が意図どおりに回らないパターンをいくつか押さえておきます。

  • ノードに cluster-autoscaler-related タグがない — AWS の場合 ASG に k8s.io/cluster-autoscaler/enabled のようなタグがないと CA がその ASG を管理対象として見ません。
  • PodDisruptionBudget が厳しすぎる — scale down 時に Pod を移そうとしても PDB が止めればノードを殺せず、減りません。
  • cluster-autoscaler.kubernetes.io/safe-to-evict: "false" アノテーション — このアノテーションが付いた Pod があるノードは CA が終了させません。システム Pod やローカルディスクに依存する Pod に付くケースが多いです。
  • Pending 理由がリソース不足ではないnodeSelectoraffinity のミスマッチ、または PV の AZ ミスマッチ(#2WaitForFirstConsumer が解いてくれる部分)のため Pending なら、ノードをさらに立ててもその Pod は依然 Pending です。CA の責任外です。

kubectl describe pod のイベントセクションと CA Pod のログ(kubectl logs -n kube-system -l app=cluster-autoscaler)を一緒に見ればどちらの理由かを判別できます。

3 つの次元の協業 — 負荷急増 1 サイクル #

3 つのオートスケーラが一緒に回る形を 1 つのシナリオで追ってみます — 普段の 5 倍トラフィックが入ってくるマーケティングキャンペーン開始直後です。

負荷急増 1 サイクル
t=0s    キャンペーン開始。トラフィック 5x 流入。
        Deployment 'web': replicas=4, requests.cpu=500m
        すべての Pod の CPU 平均 130% (目標 70% 比)

t=15s   HPA がメトリクス収集・計算。
        desired = ceil(4 * (130/70)) = 8
        replicas: 4 -> 8 への変更要求。

t=20s   K8s が新しい Pod 4 つを作ろうとします。
        しかしノードの利用可能 CPU が不足。
        新しい Pod 2 つは Running、2 つは Pending。

t=30s   CA が Pending Pod を発見。
        ASG に desired capacity +1 を要求。
        新しいノードがクラウドで起動開始。

t=120s  新しいノードがクラスタに Ready で合流。
        Pending 状態だった Pod 2 つがそのノードにスケジュール。
        Running。

t=135s  HPA が再び測定。平均 80%。
        desired = ceil(8 * (80/70)) = 10。replicas: 8 -> 10。
        2 つさらに必要 — 今度は新しいノードの余裕リソースに入る。
        ...

キャンペーンが終わってトラフィックが普段に戻ると逆順で減ります — HPA の scale down 安定化ウィンドウ(5 分)後 Pod がゆっくり減り、CA が空いていくノードを発見して終了します。ノード終了は通常使用率が低い状態が一定時間(デフォルト 10 分くらい)維持されてから始まりさらに保守的です。

このサイクルが人の介入なしに回る点が核心です。しかし回るために必要な前提 — ワークロードに requests が書かれていて、metrics-server が生きていて、HPA の behavior が合理的で、ASG に CA タグが付いていて、ノードインスタンスタイプがワークロードに合うこと — がすべて #4 からのモデルの上に立っています。オートスケーリングはそのモデルを動的に回す最後の 1 層と見ればよいです。

運用導入パターン — どこから始めるか #

3 つのオートスケーラをすべて同時にオンにするのが良さそうに見えますが、運用の推奨流れは保守的です。

  1. HPA から導入 — もっとも馴染みがあり、事故のリスクがもっとも小さいです。対象ワークロードに requests が書かれているかを確認し、minReplicas ≥ 2 にして、CPU 70% のような標準値で始めます。数日・数週間にわたって動作を観察しながら behavior を調整します。
  2. VPA は updateMode: "Off" で推奨値だけ — Pod を再生成するポリシーは最初からオンにしません。recommender の推奨値を数日集めてみて、合理的だという判断が立てば人がマニフェストに反映します。Auto に移すのはそのワークロードの evict 影響が小さいという確信が立つときだけにします。
  3. CA はクラウド環境であればほぼ必須 — minikube・kind のような学習環境では意味がありませんが、クラウドクラスタで CA なしに運用するとノードの desired capacity を人が毎回追わなければなりません。EKS は最初から CA(または Karpenter)を一緒にインストールしておくパターンが標準です。
  4. Custom metric / KEDA はワークロード特性に合わせて — CPU・メモリ信号で十分に表現されるワークロードには無理に Prometheus Adapter を導入する理由はありません。キューコンシューマやイベントワーカーのように信号の種類が異なるワークロードに限って導入します。

この流れを 1 行に縮めると — HPA はほぼすべてのワークロードに基本、VPA は推奨値ツールとして始める、CA はクラウドなら必須、KEDA は必要なところに です。

まとめ #

この記事で押さえた流れをまとめます。

  • 3 つの次元の自動調整 — HPA(Pod 個数)、VPA(Pod の requests・limits)、CA(ノード個数)。互いに補完関係であり同時に回ります。
  • metrics-server という前提 — HPA・VPA が動作するためにはクラスタ内に metrics-server(または Prometheus + Adapter、KEDA)がインストールされている必要があります。minikube は addon 1 行、EKS は別途インストール必要、GKE・AKS はデフォルト有効。
  • HPA マニフェストapiVersion: autoscaling/v2scaleTargetRef(対象 Deployment)、minReplicas/maxReplicas(安全網)、metrics(見る信号)。CPU Utilizationrequests 比なので #4 の requests が前提です。
  • HPA アルゴリズムdesired = ceil(current * (currentMetric / targetMetric))。比例式 1 行。multi-metric は各メトリクスの desired のうちもっとも大きな値を採択。
  • scale up・down 非対称behavior フィールド。scale up は即座、scale down は 5 分安定化ウィンドウ。一時的に下がった信号で減らして cold start 事故になることを防止。
  • Custom metric と KEDA — Prometheus Adapter で PromQL 結果を HPA に公開。KEDA は 50 種類超のイベントソース組み込み + 0→N スケーリング。キュー・イベントワークロードに適合。
  • VPA 3 コンポーネント — recommender(推奨値算出)、updater(Pod evict)、admission-controller(新しい Pod に推奨値注入)。updateModeOff(推奨のみ) / Initial(生成時) / Auto(再生成)。運用開始はほぼ常に Off
  • HPA・VPA 衝突 — 同じメトリクス(CPU)で両方回すと振動。回避は 2 つの道 — メトリクスを分離、または VPA を Off にして人が反映。
  • Cluster AutoscalerPending Pod が見えればノードグループ(ASG / MIG / VMSS)にノード追加、空いているノードは一定時間後に終了。決定は requests ベース。EKS のより速い代替として Karpenter。
  • 3 つの次元の協業 — 負荷急増時 HPA が Pod を増やす → ノードリソース不足 → 新しい Pod が Pending → CA がノード追加。減るときは逆順。VPA は別サイクル。
  • 運用導入流れ — HPA から、VPA は Off で推奨値だけ、CA はクラウドならほぼ必須、KEDA は必要なワークロードだけに。

このモデルまで手に入れば、運用クラスタの負荷が揺れるときに人が毎回手で追いかけなくてよい 1 段を備えることになります。同時にその自動化が回るために必要な前提 — requests の存在、metrics-server、behavior の合理的な値、CA タグ — が 1 セットで見えます。

次 — RBAC / NetworkPolicy / ResourceQuota #

この記事までのシリーズは ワークロードをどう回すか のモデルを 1 サイクル追ってきた流れでした。#1 のコントローラ、#2 の永続データ、#3 の外部入口、#4 のリソース要求、#5 の健康信号、この記事の自動調整。これだけで 1 つのワークロードを運用クラスタに立てて回らせるモデルの 1 セットです。

次回のテーマは視点を 1 段上に動かします — 複数のユーザー・複数のチーム・複数のワークロードが 1 つのクラスタを一緒に使う環境のポリシー です。誰がどのオブジェクトにどんな動作ができるかの権限モデル RBAC、Pod 同士のネットワーク通信をホワイトリストで制御する NetworkPolicy、1 つの namespace がクラスタリソースをどこまで使えるかの上限 ResourceQuotaLimitRange。この 3 つがマルチテナント・運用クラスタの標準安全網です。

#7 RBAC / NetworkPolicy / ResourceQuota — セキュリティとリソースポリシー ではこの 3 つのオブジェクトのマニフェストと動作、そして運用の推奨パターンを 1 サイクルで追って K8s 中級シリーズを締めくくります。

X