Horizontal Pod Autoscaler を動かして理解する

v1.37 時点(2026年10月28日 確認)

Kubernetes 基本仕様第35回 / 全45回 ・ 第7部 スケーリングと可用性
目次

この記事では負荷に合わせて Pod の数を自動で増やしたり減らしたりする Horizontal Pod Autoscaler(以下、HPA)の仕組みを、図で説明します。HPA のコントローラーがどこで動き、何を読んで何を書き換えるのか、目標の Pod の数をどう計算するのかがわかります。既定では、数を増やすときはすぐに増え、減らすときは時間を置いてから減る理由もわかります。後半では kind のクラスタに metrics-server を入れ、負荷をかけて Pod が増え、負荷を止めると減る様子を観察する手順を示します。

この回から第7部(スケーリングと可用性)に入ります。コンテナの requests と limits を扱った第29回の内容を前提にしています。HPA が見る CPU の使用率は、requests に対する割合で計算するからです。まだの方は先にこちらをどうぞ。

基本仕様 ・ 第29回 requests と limits の仕組みコンテナが使う CPU とメモリの量を書く requests と limits の仕組みを図で説明します。requests で Pod を置くノードが選ばれること、limits を超えようとした CPU とメモリに起きること、QoS クラスの決まり方を押さえ、kind のクラスタで OOMKilled を観察する手順を紹介します。

以下の図は、HPA のコントローラーがどこで動き、何を読んで、何を書き換えるかを表しています。後半の手順で作る Deployment hpa-demo と HPA hpa-demo を例にしています。

使用量を問い合わせて Deployment の replicas を書き換える HPA のコントローラーと、使用量を集める metrics-server

HPA そのものは目標(この例では CPU の使用率 50%)を書いておくリソースです。実際に数を変えるのは、kube-controller-manager の中で動く HPA のコントローラーです。コントローラーは metrics-server が集めた Pod の使用量をもとに目標の数を計算し、Deployment の replicas を書き換えます。Pod を作ったり消したりするのは、これまでの回と同じく Deployment と ReplicaSet のコントローラーです。図では kube-apiserver を省いています。kube-controller-manager から metrics-server への線も、実際には kube-apiserver の Metrics API を通ります(「誰が何を読み、何を書き換えるか」で説明します)。矢印は問い合わせる側・集める側から引いてあり、使用量の値はその逆向きに流れます。Pod へ向かう線は Pod 1 つずつではなく枠に向けて引いてあります。枠の中の Pod の数(2 つ)は例です。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、Resource metrics pipeline | Kubernetes、kube-controller-manager | Kubernetes

Horizontal Pod Autoscaler とは

HorizontalPodAutoscaler は、ワークロードのリソース(Deployment や StatefulSet など)の数を、需要に合わせて自動で変えるもの です。公式ドキュメントによると、Horizontal(水平)のスケーリングとは負荷が増えたときに Pod を増やして対応することです。動いている Pod に CPU やメモリを多く割り当てる垂直(vertical)のスケーリングとは違います。第29回で見た in-place の Pod の垂直スケーリング(動いている Pod の資源を変えること)は、その名前のとおり垂直の側です。負荷が減り、Pod の数が設定した最小の数より多ければ、HPA は Deployment などに数を減らすよう指示します。

参考:Horizontal Pod Autoscaling | Kubernetes

HPA は Kubernetes の API のリソースと、コントローラーの組み合わせで実装されています。リソースがコントローラーの振る舞いを決めます。コントローラーはコントロールプレーンの中で動き、平均の CPU の使用率や平均のメモリの使用率などの値に合わせて、対象(たとえば Deployment)の望ましい数を定期的に調整します。数を変えられないもの(たとえば第15回の DaemonSet)には使えません。

参考:Horizontal Pod Autoscaling | Kubernetes

v1.37 時点で安定版の API のバージョンは autoscaling/v2 で、メモリやカスタムメトリクス(CPU やメモリ以外の値)によるスケーリングにも対応しています。この回では autoscaling/v2 を使い、CPU の使用率だけを扱います。

参考:Horizontal Pod Autoscaling | Kubernetes

なぜ HPA が必要なのか

第12回で見たとおり、Deployment は spec.replicas に書いた数の Pod を保ちます。数を変えるには、利用者がマニフェストを書き換えるか、第21回のように kubectl scale を実行します。アクセスが多い時間帯だけ Pod を増やしたいなら、誰かが負荷を見張って、そのたびに数を変えることになります。HPA はこの「負荷を見て数を変える」仕事を、コントローラーの調整ループ(第6回)として自動で行います。

数を多めに固定しておく方法もあります。しかし第29回で見たとおり、kube-scheduler は requests の合計がノードに収まるように Pod を置きます。使わない Pod の分まで requests がノードの割り当て可能な量を取ってしまうので、ほかの Pod を置きにくくなると考えられます。HPA を使うと、負荷が減ったときに数を減らせます。

参考:Horizontal Pod Autoscaling | Kubernetes、Resource Management for Pods and Containers | Kubernetes

HPA の仕組み

誰が何を読み、何を書き換えるか

公式ドキュメントによると、HPA は途切れ途切れに動くコントロールループとして実装されています(ずっと動き続ける処理ではありません)。間隔は kube-controller-manager の --horizontal-pod-autoscaler-sync-period で決まり、既定では 15 秒です。kube-controller-manager のコントローラーの一覧によると、このコントローラーは既定で有効です。

参考:Horizontal Pod Autoscaling | Kubernetes、kube-controller-manager | Kubernetes

1 回の間隔のあいだに、コントローラーは次の順に動きます。

  1. HPA の scaleTargetRef に書かれた対象(Deployment など)を見つける
  2. 対象の .spec.selector のラベルで Pod を選ぶ
  3. Pod の値を Metrics API(Pod ごとの CPU やメモリ)か custom metrics API(それ以外の値)から得る
  4. 値と目標から望ましい数を計算し、対象の数を書き換える

参考:Horizontal Pod Autoscaling | Kubernetes

Pod ごとの CPU やメモリの値を出す API は metrics.k8s.io というグループの API で、公式ドキュメントでは resource metrics API とも Metrics API とも呼ばれます。この記事では Metrics API(metrics.k8s.io)と書きます。HPA のページによると、metrics.k8s.io はふつう metrics-server というアドオンが提供し、metrics-server は別に起動する必要があります。Resource metrics pipeline のページも、Metrics API を使うには metrics-server か、Metrics API を提供する別のアダプターをデプロイしなければならない(must)としています。

参考:Horizontal Pod Autoscaling | Kubernetes、Resource metrics pipeline | Kubernetes

metrics-server は、各ノードの kubelet から CPU とメモリの使用量を集めて、Metrics API として kube-apiserver から読めるようにするコンポーネント です。公式ドキュメントはクラスタのアドオン(cluster addon)と呼び、Metrics API の参照実装(reference implementation)だとしています。kubelet はコンテナの使用量をノードごとにまとめて出し、metrics-server は各ノードの kubelet の API を呼んで値を集めます。metrics-server の README によると、値を集める間隔は 15 秒です。metrics-server 自身も、第4回で見たアドオンと同じく、kube-system の Deployment の Pod として動きます(後半で入れる components.yaml がそう作ります)。

参考:Resource metrics pipeline | Kubernetes、Resource metrics pipeline | Kubernetes、Kubernetes Metrics Server | GitHub、metrics-server/manifests/base at v0.9.0 · kubernetes-sigs/metrics-server

公式ドキュメントによると、Metrics API を使うには、kube-apiserver のアグリゲーション層(aggregation layer)を有効にし、metrics.k8s.io の APIService を登録しなければなりません(must)。アグリゲーション層は kube-apiserver の中で動き、登録された API のパスに届いたリクエストを、その API を提供するサーバーに取り次ぎます。つまり、HPA のコントローラーが kube-apiserver に metrics.k8s.io を問い合わせると、kube-apiserver はそれを metrics-server に取り次ぎます。その取り次ぎ先を登録するのが APIService というオブジェクトで、後半で入れる components.yaml に含まれています。

参考:Resource metrics pipeline | Kubernetes、Kubernetes API Aggregation Layer | Kubernetes、metrics-server/manifests/base at v0.9.0 · kubernetes-sigs/metrics-server

数を書き換える先は対象の scale というサブリソースです(第5回で見た /status と同じ仕組みの、オブジェクトに付いた別のパス)。公式ドキュメントによると、Deployment や StatefulSet のように数を変えられるリソースには scale があり、数を変えたり今の状態を調べたりできます。Deployment を対象にした HPA が管理するのは、Deployment の replicas です。その下の ReplicaSet の replicas を決めるのは、これまでどおり deployment-controller です(第12回)。ここまでに出てきたコンポーネントを次の表にまとめます。

コンポーネントすること
kubeletコンテナの CPU とメモリの使用量を集め、API で出す
metrics-server各ノードの kubelet から使用量を集め、Metrics API として出す
HPA のコントローラー(kube-controller-manager)HPA を読み、Metrics API から使用量を得て、対象の replicas を書き換える
deployment-controller、replicaset-controllerreplicas に合わせて ReplicaSet と Pod の数を変える

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、Resource metrics pipeline | Kubernetes

使用率は requests に対する割合

公式ドキュメントによると、CPU のような Pod ごとの資源の値では、コントローラーは HPA の対象のそれぞれの Pod について、Metrics API から値を得ます。目標が使用率(Utilization)で書かれていれば、Pod の中のコンテナの requests に対する割合として使用率を計算します。目標が使用量そのもので書かれていれば、その値を使います。そのうえで、対象のすべての Pod の平均をとります。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

使用率は limits ではなく requests に対する割合です。たとえば後半の手順で使う Pod は、CPU の requests が 200m(0.2 CPU)です。公式ドキュメントは、requests が 200m の Pod で目標の使用率を 50% にすると、Pod あたりの平均の CPU の使用量を 100m に保つことになる、と説明しています。requests を超えて使うこともできるので(第29回)、使用率は 100% を超えることがあります。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、Resource Management for Pods and Containers | Kubernetes

Pod のコンテナのどれかに CPU の requests が書かれていないと、その Pod の CPU の使用率は定義されず、HPA はそのメトリクスについて何もしません。そのため公式ドキュメントは Pod の spec に cpu や memory の requests を書いておくべき(should)だとしています。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

Pod の使用率は Pod のすべてのコンテナの使用量を合計して計算します。そのため公式ドキュメントは、1 つのコンテナだけが高く使っていても、Pod 全体の使用率が範囲内なら HPA が増やさないことがある、と注記しています。特定のコンテナの値で数を変えたいときは ContainerResource という書き方もありますが、この回では扱いません。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

Metrics API の値の測り方も決まっています。公式ドキュメントによると、CPU はカーネルが数える累積の CPU の時間から変化の割合をとった、平均のコアの使用量です。単位は第29回で見た CPU の単位です。メモリは値を集めた瞬間のワーキングセット(working set。メモリが逼迫しても解放できない、使用中のメモリの量で、計算は OS によって違う推定値)で、単位はバイトです。

参考:Resource metrics pipeline | Kubernetes

目標の数を求める式

公式ドキュメントによると、HPA のコントローラーは、いちばん基本的には、望ましい値と今の値の比をもとに動きます。式は次のとおりです(公式ドキュメントでは分数の形で書かれています)。ceil は小数を切り上げることを表します。

desiredReplicas = ceil[currentReplicas × (currentMetricValue / desiredMetricValue)]

参考:Horizontal Pod Autoscaling | Kubernetes

式の項目を次の表にまとめます。目標が平均の使用率や平均の値のときは、currentMetricValue は対象のすべての Pod の平均です。

項目意味
desiredReplicas望ましい Pod の数(計算の結果)
currentReplicas今の Pod の数
currentMetricValue今の値(たとえば平均の CPU の使用率)
desiredMetricValue目標の値(たとえば 50%)

参考:Horizontal Pod Autoscaling | Kubernetes

公式ドキュメントの例では、今の値が 200m で目標が 100m なら、比は 2.0 で、Pod の数は 2 倍になります。今の値が 50m なら比は 0.5 で、Pod の数は半分になります。

ただし、比が 1.0 に十分近いときは、HPA のコントローラーは数を変えません。この許容範囲(tolerance)は既定では 0.1 で、kube-controller-manager の --horizontal-pod-autoscaler-tolerance で変えられます。kube-controller-manager のリファレンスは、この値を「数を変えることを考えるのに要る、比の 1.0 からの最小の変化」と説明しています。比が 0.95 や 1.05 のように 1.0 からの差が 0.1 より小さければ、数は変わらないと読めます。値が少し揺れるたびに Pod の数が変わるのを防ぐためだと考えられます。

参考:Horizontal Pod Autoscaling | Kubernetes、kube-controller-manager | Kubernetes

計算した数は HPA の minReplicas と maxReplicas の範囲に収められます。API リファレンスによると、minReplicas は減らすときの下限で、書かなければ 1 です。maxReplicas は増やすときの上限で、minReplicas より小さくできません。公式ドキュメントの HPA の状態(conditions)の説明にも、望ましい数が最大か最小で抑えられたことを表す ScalingLimited があります。

参考:HorizontalPodAutoscaler | Kubernetes API Reference、HorizontalPodAutoscaler Walkthrough | Kubernetes

HPA に複数のメトリクスを書いた場合は、メトリクスごとに望ましい数を計算し、いちばん大きい数を選びます。この回の手順では CPU の使用率だけを使います。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

Ready でない Pod とメトリクスの無い Pod

公式ドキュメントによると、HPA のコントローラーは最終的な数を決める前に、メトリクスが欠けていないか、Ready の Pod がいくつあるかも考えに入れます。CPU のような Pod ごとの値では、削除中の Pod と失敗した Pod は計算に入れません。メトリクスの無い Pod と、CPU でまだ Ready でない Pod は、いったん脇に置きます。残りの Pod で比を計算したあと、脇に置いた Pod の値を見積もって計算し直します。メトリクスの無い Pod は、減らす向きなら目標の値の 100%、増やす向きなら 0% を使っていたとみなします。まだ Ready でない Pod は、増やす向きのときだけ 0% とみなします。公式ドキュメントは、これで数の変化の大きさが抑えられる(dampen)と説明しています。計算し直した比で向きが逆になるか、許容範囲に収まれば、数は変えません。HPA の status に表示される使用率は、この見積もりを入れる前の値です。

参考:Horizontal Pod Autoscaling | Kubernetes

起動してすぐに CPU を多く使うアプリケーション(ウォームアップ中の Java のアプリケーションなど)では、その紛らわしい高い値が計算に入らないよう、公式ドキュメントは次のどちらかを勧めています。startup Probe を CPU の高い時期が過ぎるまで成功しないようにするか、readiness Probe が CPU の高い時期を過ぎてから Ready を返すよう initialDelaySeconds を使うかです(Probe は第34回)。起動直後の CPU の値の扱いは kube-controller-manager の --horizontal-pod-autoscaler-cpu-initialization-period(既定では 5 分)などのフラグで、クラスタ全体に決まります。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、kube-controller-manager | Kubernetes

増やすときと減らすときの決まり(behavior)

autoscaling/v2 の HPA では behavior のフィールドで、増やすとき(scaleUp)と減らすとき(scaleDown)の振る舞いを別々に決められます。公式ドキュメントによると、behavior を書かないときの既定は次のとおりです。

yaml
behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
    - type: Pods
      value: 4
      periodSeconds: 15
    selectPolicy: Max

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

フィールドの意味と既定値を、公式ドキュメントと API リファレンスに沿って次の表にまとめます。

フィールド意味既定値
stabilizationWindowSeconds安定化の時間。過去どれだけの秒数の計算結果を考えに入れるか(0〜3600)増やすとき 0 秒、減らすとき 300 秒
policies一定の時間(periodSeconds、最大 1800 秒)のあいだに変えてよい量。数(Pods)か割合(Percent)で書く増やすとき、15 秒ごとに 4 つか今の数の 100% まで。減らすとき、15 秒ごとに今の数の 100% まで
selectPolicypolicies が複数あるときにどれを使うか。Max はいちばん大きく、Min はいちばん小さく変えるものを選び、Disabled はその向きの変更をしないMax

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、HorizontalPodAutoscaler | Kubernetes API Reference

安定化の時間は、値が揺れ続けるときに数が行ったり来たりするのを抑えるためのものです。公式ドキュメントはこの行ったり来たりを thrashing や flapping と呼んでいます。減らす向きの計算結果が出たとき、アルゴリズムは安定化の時間のあいだに計算した望ましい数を振り返り、そのうちいちばん大きい値を使います。既定の 300 秒なら、過去 5 分の望ましい数が考えに入ります(クラスタ全体の既定は kube-controller-manager の --horizontal-pod-autoscaler-downscale-stabilization で決まり、既定では 5 分)。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、kube-controller-manager | Kubernetes

既定では、増やすのはすぐ、減らすのは安定化の時間(5 分)を振り返ってから と覚えておくとよいでしょう。

参考:Horizontal Pod Autoscaling | Kubernetes

以下の図は、既定の behavior の HPA で、負荷が上がって Pod が増え、負荷が下がって安定化の時間のあとに減る流れを、コマ送りで表しています。HPA の目標は後半の手順と同じ CPU の使用率 50% です。図の使用率と Pod の数は説明のための値で、負荷の総量が一定だと仮定して式に当てはめたものです。ReplicaSet と、HPA のコントローラー、metrics-server、Service は省いています。HPA から Deployment への矢印は、HPA のコントローラーが書き換えることをまとめて描いたものです。負荷をかける load-generator も Pod です。

(初期)負荷をかける前。CPU の使用率は 0% / 50%、replicas は 1 で、Pod は 1 つ
(1/4)負荷をかけると、使用率が 200% / 50% に上がる。replicas はまだ 1
(2/4)HPA が replicas を 4 に書き換え(ceil[1 × 200 / 50] = 4)、Pod が 4 つになる。負荷が 4 つに分かれ、使用率は 50% / 50% になる
(3/4)負荷を止めると使用率は 0% / 50% に下がる。安定化の時間(既定では 300 秒)のあいだは、replicas は 4 のまま
(4/4)安定化の時間が過ぎると、HPA が replicas を minReplicas の 1 に戻し、Pod は 1 つになる

(1/4)から(2/4)は安定化の時間を待たずに増えます。(3/4)では負荷が無くなっても、過去 5 分に計算した 4 が残っているので減りません。(2/4)で使用率が目標の 50% 近くに戻ったあとは、比が 1.0 に近いので、許容範囲によって数は 4 のまま保たれると考えられます。(4/4)で一度に 1 まで減るのは、既定の scaleDown の policies が 15 秒で今の数の 100% まで減らすことを許すからです。実際には負荷の下がり方によって、何段かに分かれて減ることもあると考えられます。

参考:Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

許容範囲は v1.37 時点では、scaleUp や scaleDown の中の tolerance で HPA ごとにも決められます(機能ゲート HPAConfigurableTolerance。機能ゲートは第17回で見た、機能ごとに有効と無効を切り替える設定で、段階は注意点の表にまとめます)。

参考:Horizontal Pod Autoscaling | Kubernetes、HorizontalPodAutoscaler | Kubernetes API Reference、Feature Gates | Kubernetes

手元で試す

ここまでの内容を手元の kind のクラスタで試す手順を示します。公式ドキュメントの HorizontalPodAutoscaler Walkthrough の流れに沿って、metrics-server を入れたクラスタで、リクエストのたびに CPU を使う Web のアプリを動かし、HPA を作ります。アプリはこのメディアが Go(プログラミング言語)で書いた hpa-demo で、コンテナイメージは自分で作ります。負荷をかけて Pod が増える様子と、負荷を止めて安定化の時間のあとに減る様子を観察します。

この節で紹介する出力の例は公式ドキュメントに載っているもので、ほかのクラスタで、この記事の hpa-demo とは別のアプリを使って取ったものです。負荷の大きさも違い、数や時刻などの値は手元とは違います。各段の最後に挙げる成功の目安は公式ドキュメントとアプリのコードから予想される結果で、手元で実行して確かめたものではありません。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。

項目内容
kindv0.33.0
Kubernetesv1.37.0(kind v0.33.0 の既定のノードイメージ)
metrics-serverv0.9.0
アプリこのメディアが Go で書いた hpa-demo。イメージ zukai/hpa-demo:1.0 を手元で作る
ノード構成シングルノード(クラスタ名 kind):コントロールプレーン 1
必要なものDocker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)3 つ。イメージを取得するためのインターネットへの接続と、イメージを置くディスクの空き

参考:Release v0.33.0 · kubernetes-sigs/kind、Release v0.9.0 · kubernetes-sigs/metrics-server、Version Skew Policy | Kubernetes

kind でクラスタを作る手順は、以下の記事で解説しています。この回では第29回と同じく、シングルノードのクラスタを使います。

基本仕様 ・ 第2回 kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。

この回では端末を 3 つ使います。それぞれの役割は次のとおりです。

  • 1 つ目:第29回までと同じく、変化の監視(この回では HPA)。片付けの前まで続ける
  • 2 つ目:作業用のディレクトリで、そのほかの操作すべて
  • 3 つ目:負荷をかける Pod を動かすことだけ。ファイルを使わないので、cd は要らない

3 つ目の端末を分けるのは、負荷をかけるコマンドが、止めるまで端末を使い続けて応答を出し続けるからです。最初に、2 つ目の端末で mkdir hpa-demo を実行して作業用のディレクトリを作り、cd hpa-demo で移動しておきます。アプリのファイルとマニフェストは、すべてこのディレクトリに保存します。

シングルノードのクラスタを用意する

2 つ目の端末で、前の回のクラスタが残っていないかを確かめます。

console
$ kind get clusters

kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の multi-node が表示されても、この回では使いません。手元のマシンの資源に余裕がなければ、kind delete cluster --name multi-node で消してから進めてください。

console
$ kind create cluster
$ kind get clusters

kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。

console
$ kubectl get nodes --context kind-kind

ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。

参考:Quick Start | kind、kubectl get | Kubernetes

metrics-server を入れる

公式の Walkthrough は、metrics-server がデプロイされ、設定されたクラスタを使うよう求めています。まず、metrics-server がもう入っていないかを確かめます。-n は第4回で見た kube-system のような Namespace(第37回)を指定する、--namespace の短い書き方です。

console
$ kubectl get deployment metrics-server -n kube-system --context kind-kind

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、kubectl get | Kubernetes

このシリーズのこれまでの手順では metrics-server を入れていないので、ふつうは NotFound を含む、見つからないことを知らせるエラーの 1 行が出ると考えられます。これが想定どおりです。以前に自分で入れたものが表示されたら、次の kubectl apply は飛ばします。その場合、あとの args を確かめるコマンドで --kubelet-insecure-tls がもう入っていれば、kubectl patch も飛ばします。

metrics-server の README はリリースに付いている components.yaml を kubectl apply する手順を示しています。README の手順は最新のリリース(latest)を指しますが、この回では手順の結果がそろうように v0.9.0 に固定します。README の対応表では、metrics-server 0.9.x が対応する Kubernetes は 1.34 以降です。2 つ目の端末で次のコマンドを実行します。

console
$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml --context kind-kind

参考:Kubernetes Metrics Server | GitHub、Kubernetes Metrics Server | GitHub

kube-system の Namespace に metrics-server という Deployment や Service が作られ、クラスタ全体のリソースとして v1beta1.metrics.k8s.io という APIService が作られた、と知らせる行が並ぶはずです。v0.9.0 の components.yaml にこれらが入っているからです。APIService は Namespace に属さないリソースです。

参考:metrics-server/manifests/base at v0.9.0 · kubernetes-sigs/metrics-server、Command line tool (kubectl) | Kubernetes

このままだと、kind のクラスタでは metrics-server が kubelet から値を集められないと考えられます。README の要件は、kubelet の証明書(通信の相手が本物かを確かめるためのもの)がクラスタの認証局で署名されているか、--kubelet-insecure-tls で証明書の検証を止めるかのどちらかです。kind がノードを立ち上げるのに使う kubeadm(第2回)では、kubelet の証明書は既定で自己署名(認証局ではなく自分で署名したもの)なので、1 つ目を満たさないと考えられます。

参考:Kubernetes Metrics Server | GitHub、kind、Certificate Management with kubeadm | Kubernetes

この回では、README が示す 2 つの方法のうち、--kubelet-insecure-tls を渡す方法を使います。README はこのフラグを「kubelet が示すサーバー証明書の認証局を確かめない。テストのためだけ(For testing purposes only)」と説明しています。--kubelet-insecure-tls は手元の検証用クラスタのようなテストの場面だけで使います。もう 1 つの方法(kubelet の証明書をクラスタの認証局で署名してもらう方法)は、この回では扱いません。

参考:Kubernetes Metrics Server | GitHub

components.yaml は長く、変えたいのは 1 か所だけなので、ファイルを書き換えずに kubectl patch でその 1 か所を足します。2 つ目の端末で、metrics-server の Deployment のコンテナの引数(args)の最後に --kubelet-insecure-tls を足します。--type=json は書き換え方を JSON Patch の形で渡す指定です。

console
$ kubectl patch deployment metrics-server -n kube-system --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]' --context kind-kind

参考:kubectl patch | Kubernetes、RFC 6902 - JavaScript Object Notation (JSON) Patch

"path" の値は書き換える場所を / で区切って上から順にたどったものです。分けて読むと次のとおりです。

path の部分意味
/spec/template/specDeployment の Pod のひな形の spec
containers/01 つ目のコンテナ。第6回の [0] と同じく 0 から数える
args/-args の最後に足す。- は配列の最後を表す JSON Patch の書き方

v0.9.0 の components.yaml では、この Deployment のコンテナは 1 つで、args が書かれています。-p のあとの指定全体を単一引用符(')で囲むのは、第6回と同じく、シェルに特別な文字として扱わせないためです。

参考:metrics-server/manifests/base at v0.9.0 · kubernetes-sigs/metrics-server、RFC 6902 - JavaScript Object Notation (JSON) Patch

deployment.apps/metrics-server が書き換えられたことを知らせる 1 行が出るはずです。続けて、args に足せたかを確かめます。

console
$ kubectl get deployment metrics-server -n kube-system -o jsonpath='{.spec.template.spec.containers[0].args}' --context kind-kind

参考:JSONPath Support | Kubernetes

args の一覧が表示され、最後に --kubelet-insecure-tls があれば足せています。kubectl patch を 2 回実行すると、同じ引数が 2 つ入ると考えられるので、1 回だけ実行します。Pod のひな形が変わったので、第12回で見たとおり、新しい Pod への入れ替えが始まります。入れ替えが終わるまで待ちます。

console
$ kubectl rollout status deployment/metrics-server -n kube-system --context kind-kind

参考:kubectl rollout status | Kubernetes

展開が終わったことを知らせる行が出て、コマンドが終われば入れ替えは終わっています。最後に、kubectl top でノードの使用量を表示します。リファレンスによると、kubectl top は metrics-server から値を得るので、metrics-server が入っていて動いていないと使えません。metrics-server が要るので第29回では使わず、手順で使うのはこの回が初めてです。

console
$ kubectl top nodes --context kind-kind

参考:kubectl top | Kubernetes

ノードの名前と、CPU とメモリの使用量の行が表示されれば、metrics-server は値を集められています。まだ値が無いことを知らせるエラーが出たら、1 分ほど待ってからもう一度実行します。数分たってもエラーのままなら、さきほどの args を確かめるコマンドで、--kubelet-insecure-tls が足されているかを確かめます。なお、metrics-server の README には、metrics-server はオートスケールのためだけのもので、正確な使用量の元としては使わないよう書かれています。

参考:Kubernetes Metrics Server | GitHub

この段では、次の 3 点が合っていれば成功です。

  • args の最後に --kubelet-insecure-tls がある
  • kubectl rollout status が、展開が終わったことを知らせて終わる
  • kubectl top nodes で、ノードの CPU とメモリの使用量が表示される

HPA の監視を始める

1 つ目の端末で、default の Namespace の HPA を監視します。HPA はまだ無いので、名前を指定せずに種類全体を監視します。この監視は片付けの前まで止めずに続けます。

console
$ kubectl get hpa --watch --context kind-kind

参考:kubectl get | Kubernetes、HorizontalPodAutoscaler Walkthrough | Kubernetes、Command line tool (kubectl) | Kubernetes

hpa は horizontalpodautoscalers の短い名前(shortname)です。HPA がまだ無いので、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。

アプリのイメージを作る

負荷をかける相手のアプリを用意します。この記事のアプリ hpa-demo は HTTP の 8080 番のポートで待ち受け、どのパスへのリクエストにも平方根を足す計算を 100 万回ほど繰り返してから OK! を返すように書いてあります。リクエストのたびに CPU を使うので、リクエストが続けば CPU の使用率が上がると考えられます。Go や Dockerfile を知らなくても、次の 3 つのファイルを写して保存すれば進められます。ビルド(コードから実行ファイルやイメージを作ること)は Docker の中で行うので、Go を手元のマシンに入れる必要はありません。作業用のディレクトリに、プレーンテキストとして保存します。エディタには VS Code のような、コードを書くためのものを勧めます。文書を書くためのエディタは一般に " を別の形の引用符に自動で置き換えることがあり、そうなるとコードが使えなくなるからです。ファイル名は main.go、go.mod、Dockerfile(拡張子なし、先頭の D は大文字)です。1 つ目はアプリのコードの main.go です。

go
package main

import (
	"fmt"
	"log"
	"math"
	"net/http"
)

// result keeps the loop's value so that the compiler does not remove the work.
var result float64

func handle(w http.ResponseWriter, r *http.Request) {
	x := 0.0001
	for i := 0; i <= 1000000; i++ {
		x += math.Sqrt(x)
	}
	result = x
	fmt.Fprintln(w, "OK!")
}

func main() {
	http.HandleFunc("/", handle)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

handle はリクエストを受けるたびに、x に x の平方根(math.Sqrt)を足すことを繰り返してから OK! を返す関数です。計算の結果を変数 result に入れているのは、計算が省かれないようにするためです(一般に、コンパイラは結果を使わない計算を省くことがあります)。main は http.HandleFunc で handle をパターン / に登録し、http.ListenAndServe で 8080 番のポートで待ち受けます。Go の net/http では、パターン / はすべてのパスに当てはまります。

参考:http package - net/http | Go Packages、http package - net/http | Go Packages、http package - net/http | Go Packages

2 つ目は go.mod です。Go のモジュール(ひとまとまりのコード)を定義するファイルで、module の行がモジュールの名前を、go の行がこのモジュールを使うのに要る Go の最小のバージョンを決めます。Dockerfile で使う Go のイメージは、この 1.27 に合わせて golang:1.27 にしています。

module hpa-demo

go 1.27

参考:Go Modules Reference | The Go Programming Language、Go Modules Reference | The Go Programming Language

3 つ目はイメージの作り方を書いた Dockerfile(第19回)です。

dockerfile
FROM golang:1.27 AS build
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -o /hpa-demo .

FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /hpa-demo /hpa-demo
EXPOSE 8080
ENTRYPOINT ["/hpa-demo"]

Dockerfile の各行がすることを、Docker のリファレンスに沿って次の表にまとめます。

行すること
FROM golang:1.27 AS buildDocker Hub の Go の公式イメージを土台に 1 段目を始め、この段に build という名前を付ける
WORKDIR /srcこのあとの命令の作業場所を /src にする
COPY go.mod main.go ./ビルドコンテキスト(後で説明)にある 2 つのファイルを、作業場所にコピーする
RUN CGO_ENABLED=0 go build -o /hpa-demo .go build で実行ファイル /hpa-demo を作る。CGO_ENABLED=0 は cgo(Go から C 言語のコードを呼ぶ仕組み)を使わない指定
FROM gcr.io/distroless/static-debian13:nonrootdistroless の static のイメージを土台に、2 段目を始める
COPY --from=build /hpa-demo /hpa-demo1 段目で作った実行ファイルだけを、2 段目にコピーする
EXPOSE 8080コンテナが 8080 番で待ち受けることを書いておく。ポートを実際に公開するものではない
ENTRYPOINT ["/hpa-demo"]コンテナを実行ファイル /hpa-demo として動かす(第19回)

この Dockerfile は FROM が 2 つある 2 段のビルド(multi-stage build)です。Docker の公式ドキュメントによると、FROM ごとに新しい段が始まり、前の段で作ったものだけを選んで次の段にコピーできます。最終的なイメージに要らないもの(この回では Go のコンパイラなど)は、前の段に置いていけます。

2 段目は実行ファイルだけを入れた小さなイメージにしています。distroless の README によると、distroless のイメージにはアプリとその実行に要るものだけが入っています(パッケージマネージャーやシェルは無い)。static は libc(C 言語の標準ライブラリ)が要らない、静的にコンパイルしたアプリ(Go など)のためのイメージです。そこで CGO_ENABLED=0 で、C のライブラリに頼らない形にビルドします(cgo は手元と同じ種類のマシン向けのビルドでは、使える環境なら既定で有効。README の Go の例も同じ指定)。nonroot は、README に説明は無いものの、名前から root(何でもできる管理者のユーザー)でないユーザーで動かす版と考えられます。

参考:Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Dockerfile reference | Docker Docs、Multi-stage builds | Docker Docs、golang - Official Image | Docker Hub、GoogleContainerTools/distroless | GitHub、GoogleContainerTools/distroless | GitHub、distroless/base | GitHub、cgo command | Go Packages

2 つ目の端末で ls を実行し、Dockerfile、go.mod、main.go の 3 つの名前が表示されることを確かめてから、イメージを作ります。

console
$ docker build -t zukai/hpa-demo:1.0 .

-t はイメージに付ける名前の指定で、名前:タグ の形で書きます。zukai/hpa-demo:1.0 は手元のイメージに付ける名前です。Docker のドキュメントによると、既定のビルダー(docker ドライバー)で作ったイメージは手元のイメージの置き場所に入ります。この手順のコマンドには --push のようなレジストリ(イメージを置いておくサーバー)へ送る指定が無いので、Docker Hub には送られず、Docker Hub のアカウントも要りません。最後の . はビルドで使えるファイルの集まり(ビルドコンテキスト)を今のディレクトリにする指定で、その中の Dockerfile が使われます(docker build はリファレンスでは docker buildx build の別名)。初めてのときは FROM の行で土台のイメージを取得するので、インターネットへの接続が要り、時間がかかることがあります。

エラーで止まったら、エラーの表示を見ます。一般に、Docker に接続できないなら Docker が起動しているか、COPY の行ならファイルの名前と今いるディレクトリ、go build の行なら main.go と go.mod の写し間違い、FROM の行ならインターネットへの接続を確かめます。ビルドが終わったら、イメージができたかを確かめます。

console
$ docker image ls zukai/hpa-demo

表示の形は Docker のバージョンで違います。リファレンスの例では REPOSITORY、TAG などの列が並びますが、Docker Engine 29.0.0 のリリースノートによると、29.0.0 からは --tree に似た新しい表示が既定です。どちらの形でも、zukai/hpa-demo と 1.0 を含む行があればイメージができています。

参考:docker buildx build | Docker Docs、docker buildx build | Docker Docs、docker buildx build | Docker Docs、Builders | Docker Docs、Docker driver | Docker Docs、Build context | Docker Docs、docker image ls | Docker Docs、Docker Engine 29 release notes | Docker Docs

この段では、次の 2 点が合っていれば成功です。

  • docker build がエラーで止まらずに終わる
  • docker image ls に、zukai/hpa-demo と 1.0 を含む行がある

イメージをクラスタに読み込む

手元の Docker で作ったイメージを kind のクラスタで使うには、kind の公式ドキュメントのとおり、kind load docker-image でクラスタのノードに読み込みます。2 つ目の端末で次のコマンドを実行します。

console
$ kind load docker-image zukai/hpa-demo:1.0

クラスタの名前を指定していないので、既定の名前の kind のクラスタに読み込みます。別の名前のクラスタなら、kind の公式ドキュメントのとおり --name でクラスタの名前を指定します。クラスタを作り直したときや、イメージを作り直したときは、読み込み直します。エラーで止まったときは、この段の最後の補足を見てください。

読み込めたかを確かめます。第2回と同じく docker exec でノードのコンテナの中の crictl を実行し、ノードにあるイメージを一覧します。kind の Quick Start も同じ方法でノードのイメージを一覧しています。クラスタ kind のコントロールプレーンのノードは、kind-control-plane という名前のコンテナです。

console
$ docker exec kind-control-plane crictl images

zukai/hpa-demo と 1.0 を含む行があれば読み込めています。Kubernetes はレジストリのホスト名を書かないイメージの名前を Docker Hub のものとみなすので、名前の前に docker.io/ が付いて表示されることもあると考えられます。

参考:Quick Start | kind、Images | Kubernetes

この段では、次の 2 点が合っていれば成功です。

  • kind load docker-image がエラーで止まらずに終わる
  • crictl images の一覧に、zukai/hpa-demo と 1.0 を含む行がある

hpa-demo を動かす

以下は、hpa-demo の Pod を動かす Deployment と、それを公開する Service のマニフェストです。公式の Walkthrough のマニフェストの形(CPU の requests と limits、Service の 80 番のポート)に合わせています。hpa-demo.yaml という名前で、作業用のディレクトリに保存します。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-demo
spec:
  selector:
    matchLabels:
      app: hpa-demo
  template:
    metadata:
      labels:
        app: hpa-demo
    spec:
      containers:
      - name: hpa-demo
        image: zukai/hpa-demo:1.0
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: hpa-demo
  labels:
    app: hpa-demo
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: hpa-demo

image はここまでの段で作って読み込んだイメージです。公式ドキュメントによると、imagePullPolicy: Never では kubelet はイメージを取得しようとせず、イメージがノードにあればコンテナを起動し、無ければ起動に失敗します。Kubernetes はレジストリのホスト名を書かないイメージの名前を、Docker Hub のものとみなします。latest 以外のタグで imagePullPolicy を書かないときの既定の IfNotPresent(第5回)では、読み込みに失敗していると、kubelet は Docker Hub から同じ名前のイメージを取りに行くことになります。ほかの人が同じ名前でイメージを公開していれば、それが動いてしまうと考えられます。そこで Never を書き、読み込んだイメージだけを使うようにしています。kind の公式ドキュメントも読み込んだイメージを使う方法の 1 つとして imagePullPolicy: Never を挙げています。

containerPort はアプリのコードに合わせて 8080 にしています。Deployment に replicas を書いていないので、Pod は 1 つで始まります。コンテナには HPA が使用率を計算するのに要る CPU の requests(200m)と、limits(500m)を書いています。Service は第21回と同じ ClusterIP の Service で、app: hpa-demo のラベルを持つ Pod に通信を送ります。公式ドキュメントによると、Service は受け付ける port を、番号の違う targetPort に対応付けられます。このマニフェストでは Service の 80 番に届いた通信を Pod の 8080 番に送ります。

参考:Images | Kubernetes、Images | Kubernetes、Images | Kubernetes、Quick Start | kind、HorizontalPodAutoscaler Walkthrough | Kubernetes、Deployment | Kubernetes API Reference、Service | Kubernetes API Reference、Service | Kubernetes

2 つ目の端末で、マニフェストを渡します。

console
$ kubectl apply -f hpa-demo.yaml --context kind-kind

deployment.apps/hpa-demo と service/hpa-demo が作られたことを知らせる 2 行が出るはずです。続けて、Pod が動き出したかを確かめます。Pod は Deployment のひな形の app: hpa-demo のラベルを持つので、ラベルで絞り込みます。

console
$ kubectl get pods -l app=hpa-demo --context kind-kind

STATUS 列が Running にならず、イメージにかかわる理由が表示されたままなら、ノードにイメージが無いと考えられます。Never なので kubelet はイメージを取得せず、コンテナを起動できないままになるはずです。原因は kubectl describe pod -l app=hpa-demo --show-events=true --context kind-kind の Events: の欄で見分けます(第9回)。リファレンスによると --show-events の既定は、1 つのオブジェクトなら true、複数のオブジェクトなら false です。ラベルで選ぶときに確実にイベントを出すため、--show-events=true を付けます。

直し方は原因で分かれます。マニフェストの image のタグが 1.0 でなかったときは、ファイルを直してから kubectl apply -f hpa-demo.yaml --context kind-kind で適用し直します。Deployment のひな形が変わるので、Pod は入れ替わります(第12回)。タグは合っていて、イメージを読み込めていなかったときは、「イメージをクラスタに読み込む」の段に戻って読み込んでから、kubectl delete pod -l app=hpa-demo --context kind-kind で Pod を消します。ReplicaSet が Pod を作り直します(第11回)。

参考:Images | Kubernetes、kubectl describe | Kubernetes

この段では、次の 2 点が合っていれば成功です。

  • Pod が 1 つ表示され、STATUS 列が Running、READY 列が 1/1 になる
  • 1 つ目の端末の表示はまだ変わらない

HPA を作る

以下は、Deployment hpa-demo の Pod の数を、CPU の平均の使用率が 50% になるように 1〜10 の範囲で変える HPA のマニフェストです。公式の Walkthrough の「Creating the autoscaler declaratively」のマニフェストから、HPA と対象の Deployment の名前を hpa-demo に変えたものです。hpa-demo-hpa.yaml という名前で、作業用のディレクトリに保存します。

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-demo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-demo
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、HorizontalPodAutoscaler | Kubernetes API Reference

フィールドの意味を、API リファレンスに沿って次の表にまとめます。

フィールド値意味
scaleTargetRefDeployment hpa-demo数を変える対象。値を集める Pod もこの対象から決まる
minReplicas1減らすときの下限。書かなければ 1
maxReplicas10増やすときの上限。必ず書く
metricscpu の Utilization が 50対象の Pod の、CPU の requests に対する平均の使用率の目標。書かなければ CPU の平均の使用率 80% になる

参考:HorizontalPodAutoscaler | Kubernetes API Reference

behavior は書いていないので、「増やすときと減らすときの決まり」で見た既定の決まりになります。公式の Walkthrough は同じ内容の HPA を kubectl autoscale のコマンドで作っています。この回の Deployment なら kubectl autoscale deployment hpa-demo --cpu=50% --min=1 --max=10 にあたります。この回では中身が見えるように、マニフェストで作ります。公式ドキュメントは kubectl create -f を使っていますが、これまでの回と同じく kubectl apply を使います。2 つ目の端末で、マニフェストを渡します。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、kubectl autoscale | Kubernetes

console
$ kubectl apply -f hpa-demo-hpa.yaml --context kind-kind

horizontalpodautoscaler.autoscaling/hpa-demo が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には hpa-demo の行が現れます。公式ドキュメントの例では、kubectl get hpa の表示に NAME、REFERENCE、TARGET、MINPODS、MAXPODS、REPLICAS、AGE の列が並びます。例の値は TARGET 列が 0% / 50%、MINPODS 列が 1、MAXPODS 列が 10、REPLICAS 列が 1 です。公式ドキュメントによると、TARGET 列は今の CPU の使用率と目標で、まだリクエストを送るクライアントがいないので 0% です。REPLICAS 列は今の Pod の数です(公式の例でも、REPLICAS 列が 7 のとき Deployment の Pod の数も 7 です)。この列は API リファレンスの status.currentReplicas(HPA が最後に見た Pod の数)にあたると考えられるので、表示が実際の数より少し遅れることもあると考えられます。

手元では --watch で追っているので、表示は 1 つにまとまり、変化のたびに行が書き足されます。AGE 列などの値は手元のものになります。今の kubectl では、列の名前が少し違ったり、値にメトリクスの名前が付いたりして、表示が例と違うこともあると考えられます。その場合も、今の使用率と目標の 50% が並んでいる列が TARGET 列にあたります。値が得られるまでは、今の使用率の所に数値でない表示が出ることもあると考えられます。kubectl top pod のリファレンスは値の流れの遅れのため、Pod を作ってから数分は値が得られないことがある(may)としています。HPA の値も同じ Metrics API から得るので、数分待って数値になれば先へ進めます。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、HorizontalPodAutoscaler | Kubernetes API Reference、kubectl top pod | Kubernetes

この段では、次の 2 点が合っていれば成功です。

  • 1 つ目の端末に hpa-demo の行が現れ、目標が 50% と表示される
  • 数分のうちに今の使用率が 0% 前後の数値になり、REPLICAS 列が 1 のまま

負荷をかける

3 つ目の端末を開き、負荷をかける Pod を動かします。公式の Walkthrough のコマンドから、リクエストの宛先を hpa-demo の Service に変えたものです。

console
$ kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never --context kind-kind -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://hpa-demo; done"

コマンドの各部分の意味を次の表にまとめます。

部分意味
-i --tty-i は標準入力を開いたままにし(このときコンテナにつながる)、--tty は端末を割り当てる指定
--rmコンテナが終わったら Pod を消す指定
--image=busybox:1.28使うイメージ。busybox は一般に、小さなコマンドを 1 つにまとめたイメージ
--restart=NeverPod の restartPolicy(第9回)を Never にする
-- のあとコンテナで動かすコマンド。0.01 秒待っては wget で http://hpa-demo を取りに行くことを繰り返す

公式ドキュメントによると、このクライアントの Pod の中のコンテナは、Service に問い合わせを送り続ける無限ループを動かします。http://hpa-demo の hpa-demo は Service の名前で、第23回で見た DNS で Service の IP アドレスに変わります。URL にポートを書いていないので、wget は一般に HTTP の 80 番のポートに接続し、Service がその通信を Pod の 8080 番に送ります。wget の -O- は一般に、取ってきた内容を画面に出す指定です。この記事のアプリは応答に OK! を返すように書いてあるので、3 つ目の端末には OK! が流れ続けると考えられます。流れ始める前に、kubectl の案内の行が出ることもあると考えられます。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、kubectl run | Kubernetes

ここからの 2 つの段は待ち時間が長めです。待つ時間の目安は、公式ドキュメントの「1 分ほど」「数分」と、既定の計算の間隔(15 秒)と安定化の時間(300 秒)から見積もったもので、手元で確かめた値ではありません。負荷を止めたら、その時刻をメモしておくと待ち時間を数えやすくなります。目安を次の表にまとめます。

操作待つ時間の目安1 つ目の端末の成功の印過ぎても変わらなければ
負荷をかけるかけてから 1 分ほど今の使用率が 50% を大きく超える3 つ目の端末で、負荷をかける Pod が動いているかを確かめる
(そのまま待つ)数分REPLICAS 列が増える(何段かに分かれることもある)。HPA は既定で 15 秒ごとに計算し直すので、目安として 1 分ほど同じ値のまま変わらなければ、負荷を止めてよいと考えられる段の最後の kubectl describe hpa を見る
負荷を止める止めてから 1 分ほど今の使用率が 0% 前後になる負荷をかける Pod が消えたかを確かめる
(そのまま待つ)止めてから 5 分ほどのあいだREPLICAS 列は変わらない。行が増えないこともあると考えられる―
(そのまま待つ)止めてから 5 分ほど過ぎたあとREPLICAS 列が 1 になるしばらく待っても変わらなければ、kubectl describe hpa を見る

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、HorizontalPodAutoscaler Walkthrough | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

公式ドキュメントによると、1 分ほどのうちに CPU の負荷が上がるはずです。公式ドキュメントの例では、まず TARGET 列が 305% / 50% に上がり、そのあと REPLICAS 列が 1 から 7 に変わります(式に当てはめると ceil[1 × 305 / 50] = 7)。公式の例の Deployment の requests と limits も同じですが、値は別のアプリで取ったもので、手元の値とは違います。公式ドキュメントも、数が落ち着くまでに数分かかることがあり、負荷の量を調整していないので、最後の数が例と違うことがあると書いています。

手元では、limits が 500m(requests の 2.5 倍)なので、1 つの Pod の使用率は 250% ほどで頭打ちになると考えられます(第29回のスロットリング)。公式の例の 305% はこの見積もりを超えていて、その理由は公式ドキュメントからはわかりません。Pod が上限の 10 個まで増えると requests の合計は 2 CPU になるので、ノードの割り当て可能な量が足りなければ、一部の Pod が Pending のまま残ると考えられます(第29回)。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、HorizontalPodAutoscaler Walkthrough | Kubernetes、Resource Management for Pods and Containers | Kubernetes

1 つ目の端末の REPLICAS 列が 1 より大きくなったら、2 つ目の端末で Deployment と、Pod ごとの CPU の使用量を表示します。

console
$ kubectl get deployment hpa-demo --context kind-kind
$ kubectl top pods -l app=hpa-demo --context kind-kind

公式ドキュメントによると、Pod の数が HPA の表示と合っているはずです。公式ドキュメントの例では、READY 列が 7/7、UP-TO-DATE 列と AVAILABLE 列が 7 です。手元では、READY 列の右の数(望ましい数)が 1 つ目の端末の REPLICAS 列と合うはずです。READY 列の左の数と AVAILABLE 列は、まだ Ready でない Pod や Pending の Pod があると小さくなります。2 つ目のコマンドでは、Pod ごとの CPU とメモリの使用量が並ぶはずです。HPA が見ているのは、それぞれの Pod の CPU の使用量を requests の 200m で割った値の平均です。増えたばかりの Pod は、値がまだ表示されないこともあると考えられます。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、Deployments | Kubernetes、kubectl top pod | Kubernetes

数が思ったように変わらないときは、2 つ目の端末で kubectl describe hpa hpa-demo --context kind-kind を実行し、表示の Conditions: と Events: の欄を見ます。公式ドキュメントによると、Conditions: の AbleToScale は、数を読み書きできるか、待ち時間などで数を変えるのを止められていないかを表します。ScalingActive は HPA が有効で望ましい数を計算できるかを表し、False なら、ふつうはメトリクスの取得に問題があります。ScalingLimited は、望ましい数が maxReplicas か minReplicas で抑えられたかを表します。Events: には、HPA が数を変えた記録などが並ぶと考えられます。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes、Horizontal Pod Autoscaling | Kubernetes

この段では、次の 3 点が合っていれば成功です。

  • 1 つ目の端末で、今の使用率が目標の 50% を大きく超える
  • そのあと REPLICAS 列が 1 より大きくなる(10 を超えることはない)
  • kubectl get deployment hpa-demo の READY 列の右の数が REPLICAS 列と合う

負荷を止める

1 つ目の端末の REPLICAS 列が、目安として 1 分ほど同じ値のまま変わらなくなったら(「負荷をかける」の表)、3 つ目の端末で Ctrl+C を押して、負荷をかける Pod を止めます。--rm を付けたので、Ctrl+C のあと Pod は消され、そのことを知らせる行が出ると考えられます。公式ドキュメントは 1 分ほどしてから HPA を kubectl get hpa の --watch で監視して結果を確かめるとしています。1 つ目の端末で監視を続けているので、新しくコマンドを打たなくてかまいません。公式ドキュメントの例では、CPU の使用率が 0 に下がり、TARGET 列が 0% / 50%、REPLICAS 列が 1 に戻っています。減るまでの時間の目安は「負荷をかける」の表のとおりで、公式ドキュメントの最後にも、数が変わるのに数分かかることがあると書かれています。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes

数が減ったら、2 つ目の端末でもう一度 Deployment を表示し、負荷をかける Pod が消えたことも確かめます。

console
$ kubectl get deployment hpa-demo --context kind-kind
$ kubectl get pods --context kind-kind

公式ドキュメントの例では、READY 列が 1/1、UP-TO-DATE 列と AVAILABLE 列が 1 に減っています。2 つ目のコマンドで hpa-demo- で始まる Pod の行だけが表示され、load-generator が無ければ消えています。load-generator が残っていたら、kubectl delete pod load-generator --context kind-kind で消します。

参考:HorizontalPodAutoscaler Walkthrough | Kubernetes

この段では、次の 3 点が合っていれば成功です。

  • 負荷を止めて 1 分ほどのうちに、今の使用率が 0% 前後に下がる
  • 下がってすぐには REPLICAS 列が変わらず、数分たってから 1 になる
  • kubectl get deployment hpa-demo の数が 1/1 になる

片付け

1 つ目の端末で Ctrl+C を押して、HPA の監視を止めます。

2 つ目の端末で、作った HPA、Deployment、Service を、マニフェストを指定して削除します。

console
$ kubectl delete -f hpa-demo-hpa.yaml --context kind-kind
$ kubectl delete -f hpa-demo.yaml --context kind-kind

参考:kubectl delete | Kubernetes

Deployment を消すと、その ReplicaSet と Pod も消えます(第6回)。少し待ってから、残っていないかを確かめます。マニフェストでは Deployment 自身の metadata にラベルを付けていないので、-l app=hpa-demo では Deployment は見つかりません。そこで HPA と Deployment は種類全体で、Pod はラベルで調べます。

console
$ kubectl get hpa,deployments --context kind-kind
$ kubectl get pods -l app=hpa-demo --context kind-kind

Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。どちらのコマンドでも見つからないことを知らせる 1 行が出れば、削除できています。

metrics-server はこの回で入れたものです。クラスタを残して使い続け、metrics-server も消したい場合は、入れたときと同じ components.yaml を指定して消します。このあとのクラスタの削除まで進めるなら、metrics-server はクラスタと一緒に消えるので、このコマンドは要りません。

console
$ kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml --context kind-kind

最後に、クラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。

console
$ kind delete cluster
$ kind get clusters

kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。手元の Docker に残ったイメージも要らなければ、docker image rm zukai/hpa-demo:1.0 で消せます。ビルドの土台の Go のイメージは、docker image ls golang に 1.27 を含む行があれば、docker image rm golang:1.27 で消せます。行が無ければ、イメージはビルドキャッシュに残っていると考えられます。その場合は、ビルドキャッシュを消す docker builder prune で消せます。「イメージをクラスタに読み込む」の補足の手順で /tmp/hpa-demo.tar を書き出していれば、それも rm /tmp/hpa-demo.tar で消します。

参考:Quick Start | kind、kubectl config | Kubernetes、docker image rm | Docker Docs、docker builder prune | Docker Docs

注意点

HPA でつまずきやすい点と、バージョンで変わる点を、次の表にまとめます。

項目内容
metrics-server は別に入れるmetrics.k8s.io はふつう metrics-server が提供し、別に起動する必要がある。入っていないと、CPU の使用率では数を変えられないと考えられる
requests が無いと使用率を計算できないPod のコンテナのどれかに CPU の requests が無いと、CPU の使用率は定義されず、HPA は何もしない
使用率は requests に対する割合limits に対する割合ではない。requests を超えて使えるので、100% を超えることがある
減らすときは既定で 5 分振り返るscaleDown の安定化の時間は既定で 300 秒で、そのあいだに計算した最大の数より減らない。増やすときは既定では安定化の時間が無い
spec.replicas をマニフェストに残さない残すと、適用し直すたびに数がその値に戻る。公式ドキュメントは消すことを推奨している
--kubelet-insecure-tls はテストのためだけkubelet の証明書の認証局を確かめなくなる。README は「For testing purposes only」としている
許容範囲を HPA ごとに決める tolerancev1.37 で Stable(機能ゲート HPAConfigurableTolerance、既定で有効)。書かなければクラスタ全体の既定(10%)
0 まで減らす機能は CPU やメモリでは使えない機能ゲート HPAScaleToZero は、v1.37 時点の HPA の概念のページと機能ゲートの一覧ではベータで既定で有効(API リファレンスの minReplicas の説明はアルファとしている)。ただし CPU やメモリの使用率では使えない
Metrics API のバージョンv1.37 時点の HPA のページでは、HPA が使えるのは metrics.k8s.io/v1beta1 で、v1 にはまだ対応していない(Resource metrics pipeline のページは v1 を Stable としている)。metrics-server 0.9.x の README の対応表も v1beta1 で、この回はこちらに沿う

参考:Horizontal Pod Autoscaling | Kubernetes、Resource metrics pipeline | Kubernetes、HorizontalPodAutoscaler | Kubernetes API Reference、Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、Horizontal Pod Autoscaling | Kubernetes、Feature Gates | Kubernetes、Kubernetes Metrics Server | GitHub

関連記事

次の回ではノードの保守(drain)で Pod を退かすときに、同時に止まる Pod の数を抑える PodDisruptionBudget を扱います。

基本仕様 ・ 第36回 PodDisruptionBudget の仕組みノードの drain のような自発的な中断で、同時に止まる Pod の数を抑える PodDisruptionBudget の仕組みを図で説明します。Eviction API による退避、minAvailable と maxUnavailable、kubectl drain と cordon を押さえ、kind のクラスタで drain が PDB に断られながら Pod を 1 つずつ退避させる様子を観察します。

HPA が Pod を数えるときにかかわる Ready と、それを決める readiness Probe・startup Probe については、以下の記事で解説しています。

基本仕様 ・ 第34回 liveness・readiness・startup Probe の違いkubelet がコンテナを定期的に確かめる Probe の 3 種類、liveness・readiness・startup の違いを図で説明します。失敗したときに kubelet がすること、確かめ方と設定のフィールド、readiness Probe と Service の転送先との関係を押さえ、kind のクラスタで readiness が失敗した Pod が転送先から外れることを確かめる手順を紹介します。

HPA が書き換える replicas を持ち、ReplicaSet を通して Pod の数を変える Deployment については、以下の記事で解説しています。

基本仕様 ・ 第12回 Deployment とローリングアップデートの仕組みReplicaSet を持ち、Pod を宣言的に更新する Deployment の仕組みを図で説明します。ひな形を変えたときに新しい ReplicaSet へ Pod を少しずつ移すローリングアップデート、pod-template-hash の役割、リビジョンと切り戻しを押さえ、kind のクラスタでイメージを更新して rollout undo で戻す手順を紹介します。

Kubernetes 基本仕様の目次へ