Horizontal Pod Autoscaler を動かして理解する
v1.37 時点(2026年10月28日 確認)
目次
この記事では負荷に合わせて Pod の数を自動で増やしたり減らしたりする Horizontal Pod Autoscaler(以下、HPA)の仕組みを、図で説明します。HPA のコントローラーがどこで動き、何を読んで何を書き換えるのか、目標の Pod の数をどう計算するのかがわかります。既定では、数を増やすときはすぐに増え、減らすときは時間を置いてから減る理由もわかります。後半では kind のクラスタに metrics-server を入れ、負荷をかけて Pod が増え、負荷を止めると減る様子を観察する手順を示します。
この回から第7部(スケーリングと可用性)に入ります。コンテナの requests と limits を扱った第29回の内容を前提にしています。HPA が見る CPU の使用率は、requests に対する割合で計算するからです。まだの方は先にこちらをどうぞ。
requests と limits の仕組みコンテナが使う CPU とメモリの量を書く requests と limits の仕組みを図で説明します。requests で Pod を置くノードが選ばれること、limits を超えようとした CPU とメモリに起きること、QoS クラスの決まり方を押さえ、kind のクラスタで OOMKilled を観察する手順を紹介します。以下の図は、HPA のコントローラーがどこで動き、何を読んで、何を書き換えるかを表しています。後半の手順で作る Deployment hpa-demo と HPA hpa-demo を例にしています。
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 などに数を減らすよう指示します。
HPA は Kubernetes の API のリソースと、コントローラーの組み合わせで実装されています。リソースがコントローラーの振る舞いを決めます。コントローラーはコントロールプレーンの中で動き、平均の CPU の使用率や平均のメモリの使用率などの値に合わせて、対象(たとえば Deployment)の望ましい数を定期的に調整します。数を変えられないもの(たとえば第15回の DaemonSet)には使えません。
v1.37 時点で安定版の API のバージョンは autoscaling/v2 で、メモリやカスタムメトリクス(CPU やメモリ以外の値)によるスケーリングにも対応しています。この回では autoscaling/v2 を使い、CPU の使用率だけを扱います。
なぜ 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 回の間隔のあいだに、コントローラーは次の順に動きます。
- HPA の
scaleTargetRefに書かれた対象(Deployment など)を見つける - 対象の
.spec.selectorのラベルで Pod を選ぶ - Pod の値を Metrics API(Pod ごとの CPU やメモリ)か custom metrics API(それ以外の値)から得る
- 値と目標から望ましい数を計算し、対象の数を書き換える
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-controller | replicas に合わせて 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 によって違う推定値)で、単位はバイトです。
目標の数を求める式
公式ドキュメントによると、HPA のコントローラーは、いちばん基本的には、望ましい値と今の値の比をもとに動きます。式は次のとおりです(公式ドキュメントでは分数の形で書かれています)。ceil は小数を切り上げることを表します。
式の項目を次の表にまとめます。目標が平均の使用率や平均の値のときは、currentMetricValue は対象のすべての Pod の平均です。
| 項目 | 意味 |
|---|---|
desiredReplicas | 望ましい Pod の数(計算の結果) |
currentReplicas | 今の Pod の数 |
currentMetricValue | 今の値(たとえば平均の CPU の使用率) |
desiredMetricValue | 目標の値(たとえば 50%) |
公式ドキュメントの例では、今の値が 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 に表示される使用率は、この見積もりを入れる前の値です。
起動してすぐに 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 を書かないときの既定は次のとおりです。
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% まで |
selectPolicy | policies が複数あるときにどれを使うか。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 分)を振り返ってから と覚えておくとよいでしょう。
以下の図は、既定の behavior の HPA で、負荷が上がって Pod が増え、負荷が下がって安定化の時間のあとに減る流れを、コマ送りで表しています。HPA の目標は後半の手順と同じ CPU の使用率 50% です。図の使用率と Pod の数は説明のための値で、負荷の総量が一定だと仮定して式に当てはめたものです。ReplicaSet と、HPA のコントローラー、metrics-server、Service は省いています。HPA から Deployment への矢印は、HPA のコントローラーが書き換えることをまとめて描いたものです。負荷をかける load-generator も Pod です。
(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 を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| metrics-server | v0.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回と同じく、シングルノードのクラスタを使います。
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 つ目の端末で、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の multi-node が表示されても、この回では使いません。手元のマシンの資源に余裕がなければ、kind delete cluster --name multi-node で消してから進めてください。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
metrics-server を入れる
公式の Walkthrough は、metrics-server がデプロイされ、設定されたクラスタを使うよう求めています。まず、metrics-server がもう入っていないかを確かめます。-n は第4回で見た kube-system のような Namespace(第37回)を指定する、--namespace の短い書き方です。
$ 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 つ目の端末で次のコマンドを実行します。
$ 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 の証明書をクラスタの認証局で署名してもらう方法)は、この回では扱いません。
components.yaml は長く、変えたいのは 1 か所だけなので、ファイルを書き換えずに kubectl patch でその 1 か所を足します。2 つ目の端末で、metrics-server の Deployment のコンテナの引数(args)の最後に --kubelet-insecure-tls を足します。--type=json は書き換え方を JSON Patch の形で渡す指定です。
$ 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/spec | Deployment の Pod のひな形の spec |
containers/0 | 1 つ目のコンテナ。第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 に足せたかを確かめます。
$ kubectl get deployment metrics-server -n kube-system -o jsonpath='{.spec.template.spec.containers[0].args}' --context kind-kind
args の一覧が表示され、最後に --kubelet-insecure-tls があれば足せています。kubectl patch を 2 回実行すると、同じ引数が 2 つ入ると考えられるので、1 回だけ実行します。Pod のひな形が変わったので、第12回で見たとおり、新しい Pod への入れ替えが始まります。入れ替えが終わるまで待ちます。
$ kubectl rollout status deployment/metrics-server -n kube-system --context kind-kind
展開が終わったことを知らせる行が出て、コマンドが終われば入れ替えは終わっています。最後に、kubectl top でノードの使用量を表示します。リファレンスによると、kubectl top は metrics-server から値を得るので、metrics-server が入っていて動いていないと使えません。metrics-server が要るので第29回では使わず、手順で使うのはこの回が初めてです。
$ kubectl top nodes --context kind-kind
ノードの名前と、CPU とメモリの使用量の行が表示されれば、metrics-server は値を集められています。まだ値が無いことを知らせるエラーが出たら、1 分ほど待ってからもう一度実行します。数分たってもエラーのままなら、さきほどの args を確かめるコマンドで、--kubelet-insecure-tls が足されているかを確かめます。なお、metrics-server の README には、metrics-server はオートスケールのためだけのもので、正確な使用量の元としては使わないよう書かれています。
この段では、次の 3 点が合っていれば成功です。
argsの最後に--kubelet-insecure-tlsがあるkubectl rollout statusが、展開が終わったことを知らせて終わるkubectl top nodesで、ノードの CPU とメモリの使用量が表示される
HPA の監視を始める
1 つ目の端末で、default の Namespace の HPA を監視します。HPA はまだ無いので、名前を指定せずに種類全体を監視します。この監視は片付けの前まで止めずに続けます。
$ 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 です。
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 にしています。
参考:Go Modules Reference | The Go Programming Language、Go Modules Reference | The Go Programming Language
3 つ目はイメージの作り方を書いた Dockerfile(第19回)です。
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 build | Docker 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:nonroot | distroless の static のイメージを土台に、2 段目を始める |
COPY --from=build /hpa-demo /hpa-demo | 1 段目で作った実行ファイルだけを、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 つの名前が表示されることを確かめてから、イメージを作ります。
$ 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 の行ならインターネットへの接続を確かめます。ビルドが終わったら、イメージができたかを確かめます。
$ 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 つ目の端末で次のコマンドを実行します。
$ kind load docker-image zukai/hpa-demo:1.0
クラスタの名前を指定していないので、既定の名前の kind のクラスタに読み込みます。別の名前のクラスタなら、kind の公式ドキュメントのとおり --name でクラスタの名前を指定します。クラスタを作り直したときや、イメージを作り直したときは、読み込み直します。エラーで止まったときは、この段の最後の補足を見てください。
読み込めたかを確かめます。第2回と同じく docker exec でノードのコンテナの中の crictl を実行し、ノードにあるイメージを一覧します。kind の Quick Start も同じ方法でノードのイメージを一覧しています。クラスタ kind のコントロールプレーンのノードは、kind-control-plane という名前のコンテナです。
$ docker exec kind-control-plane crictl images
zukai/hpa-demo と 1.0 を含む行があれば読み込めています。Kubernetes はレジストリのホスト名を書かないイメージの名前を Docker Hub のものとみなすので、名前の前に docker.io/ が付いて表示されることもあると考えられます。
この段では、次の 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 という名前で、作業用のディレクトリに保存します。
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-demoimage はここまでの段で作って読み込んだイメージです。公式ドキュメントによると、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 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f hpa-demo.yaml --context kind-kind
deployment.apps/hpa-demo と service/hpa-demo が作られたことを知らせる 2 行が出るはずです。続けて、Pod が動き出したかを確かめます。Pod は Deployment のひな形の app: hpa-demo のラベルを持つので、ラベルで絞り込みます。
$ 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回)。
この段では、次の 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 という名前で、作業用のディレクトリに保存します。
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 リファレンスに沿って次の表にまとめます。
| フィールド | 値 | 意味 |
|---|---|---|
scaleTargetRef | Deployment hpa-demo | 数を変える対象。値を集める Pod もこの対象から決まる |
minReplicas | 1 | 減らすときの下限。書かなければ 1 |
maxReplicas | 10 | 増やすときの上限。必ず書く |
metrics | cpu の Utilization が 50 | 対象の Pod の、CPU の requests に対する平均の使用率の目標。書かなければ CPU の平均の使用率 80% になる |
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
$ 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 に変えたものです。
$ 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=Never | Pod の 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 の使用量を表示します。
$ 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 に戻っています。減るまでの時間の目安は「負荷をかける」の表のとおりで、公式ドキュメントの最後にも、数が変わるのに数分かかることがあると書かれています。
数が減ったら、2 つ目の端末でもう一度 Deployment を表示し、負荷をかける Pod が消えたことも確かめます。
$ 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 で消します。
この段では、次の 3 点が合っていれば成功です。
- 負荷を止めて 1 分ほどのうちに、今の使用率が 0% 前後に下がる
- 下がってすぐには
REPLICAS列が変わらず、数分たってから1になる kubectl get deployment hpa-demoの数が1/1になる
片付け
1 つ目の端末で Ctrl+C を押して、HPA の監視を止めます。
2 つ目の端末で、作った HPA、Deployment、Service を、マニフェストを指定して削除します。
$ kubectl delete -f hpa-demo-hpa.yaml --context kind-kind
$ kubectl delete -f hpa-demo.yaml --context kind-kind
Deployment を消すと、その ReplicaSet と Pod も消えます(第6回)。少し待ってから、残っていないかを確かめます。マニフェストでは Deployment 自身の metadata にラベルを付けていないので、-l app=hpa-demo では Deployment は見つかりません。そこで HPA と Deployment は種類全体で、Pod はラベルで調べます。
$ 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 はクラスタと一緒に消えるので、このコマンドは要りません。
$ kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml --context kind-kind
最後に、クラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。
$ 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 ごとに決める tolerance | v1.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 を扱います。
PodDisruptionBudget の仕組みノードの drain のような自発的な中断で、同時に止まる Pod の数を抑える PodDisruptionBudget の仕組みを図で説明します。Eviction API による退避、minAvailable と maxUnavailable、kubectl drain と cordon を押さえ、kind のクラスタで drain が PDB に断られながら Pod を 1 つずつ退避させる様子を観察します。HPA が Pod を数えるときにかかわる Ready と、それを決める readiness Probe・startup Probe については、以下の記事で解説しています。
HPA が書き換える replicas を持ち、ReplicaSet を通して Pod の数を変える Deployment については、以下の記事で解説しています。