HPA: Kubernetes で負荷に応じて Pod を自動でスケールする
- 公開日
- カテゴリ:Kubernetes
- タグ:Kubernetes,学習メモ

アクセスが増えたら Pod を増やし、減ったら Pod を減らす。これをスケーリングと呼ぶ。Kubernetes では、Pod の数は Deployment の replicas で決まる。
この記事では、replicas を変える 2 つの方法を確認する。
- 自分の手で変える(
kubectl scale、YAML の書き換え) - CPU 使用率を見て Kubernetes に変えさせる(HPA = HorizontalPodAutoscaler)
この記事でやること:
kubectl scaleで Pod を 1 → 3 → 1 にし、Deployment・ReplicaSet・Pod の 3 段に数が伝わることの確認- 減らすときにどの Pod が消えるかの確認
- YAML の
replicasを書き換えてapplyしても同じことが起きること、applyがkubectl scaleの結果を上書きすることの確認 - HPA を作り、負荷をかけると Pod が増え、止めると数分後に減ることの確認
- 減るまでの待ち時間を変える
behavior、requestsがないと HPA が動かないこと、kubectl autoscaleの確認
contents
- 環境
- 手動スケールと HPA の違い
- Deployment と Service を作る
- kubectl scale で増やす
- 減らすときに、どの Pod が消えるか
- YAML を書き換えて apply する
- CPU の使用率
- HPA を作る
- 負荷をかけて、増えるのを見る
- 負荷を止めて、減るのを見る
- HPA を消す
- 減るまでの待ち時間を短くする
- requests がないと HPA は動かない
- kubectl autoscale で HPA を作る
- Pod を手で消しても、数は戻る
- 用語メモ
- 参考 URL
環境
kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)
- 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1
- metrics-server が入っていること(入れ方は「requests / limits: Kubernetes でコンテナが使う CPU とメモリを決める」の「metrics-server と kubectl top」を参照)
手動スケールと HPA の違い
同じ nginx の Deployment を使って、手動で replicas を変える場合と、HPA が replicas を変える場合を比べる。
| 手動スケール | HPA | |
|---|---|---|
誰が replicas を変えるか | 自分(kubectl scale か、YAML を書き換えて apply) | HPA controller(kube-controller-manager の中のプログラム) |
| 何を見て変えるか | 自分の判断 | Pod の CPU 使用率(requests に対する割合) |
| いつ変わるか | コマンドを打った直後 | 15 秒ごとに判断。増えるのは 1 分以内、減るのは負荷が下がってから 5 分後(既定) |
| 数の範囲 | 何でも | minReplicas 〜 maxReplicas の間 |
出てくる名前は次のとおり。
| 名前 | 内容 |
|---|---|
replicas | Deployment の YAML の spec.replicas。「この Pod を何個動かし続けるか」の数 |
| HPA(HorizontalPodAutoscaler) | 「この Deployment の Pod の数を、CPU 使用率が○% になるように、△個〜□個の間で自動で変えろ」というデータ。kind: HorizontalPodAutoscaler |
| utilization(使用率) | Pod のコンテナが実際に使っている CPU ÷ YAML に書いた requests.cpu。requests.cpu: 100m のコンテナが 50m 使っていれば 50% |
| metrics-server | 各 Node の kubelet から「この Pod は今 CPU をいくら使っている」という値を 15 秒ごとに集めて、kubectl top や HPA に渡すプログラム。kube-system の Pod として動いている |
HPA はどこにあるか
HPA は、Deployment と同じく、動いているプログラムではなく、Control Plane で動いている etcd に保存されるデータ。(「ConfigMap: Kubernetes で Pod に渡す設定値」の「ConfigMap はどこにあるか」を参照)
HPA を読んで Deployment の replicas を書き換えるのは、Control Plane の kube-controller-manager の中の HPA controller。CPU の使用量を集めるのは metrics-server(kube-system の Pod。このクラスタでは k8s-study-worker の上)と、各 Node の kubelet。
- 角が二重の箱(kube-controller-manager、metrics-server、kubelet)は動いているプログラム。それ以外は etcd に保存されたデータと、Node の上の Pod
- 自分で YAML を書くのは HPA と Deployment。HPA controller は Deployment の
replicasを書き換えるだけで、Pod を直接は触らない。replicasが変わったあとは、deployment-controller が ReplicaSet のDESIREDを変え、replicaset-controller が Pod を作る・消す。自分でkubectl scaleしたときと同じ流れ kubectl top podsも、同じ metrics-server に聞いている。metrics-server が止まっていると、kubectl topも HPA も使用率を取れない
Deployment と Service を作る
replicas: 1 の Deployment を作り、Deployment・ReplicaSet・Pod の 3 つに「数」がどう表示されるかを見る。
# demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
| フィールド | 意味 |
|---|---|
spec.replicas: 1 | Pod を 1 個動かし続ける。この数を変えていく |
resources.requests.cpu: 100m | このコンテナは CPU を 0.1 個分使う前提、という申告。HPA は、この値を分母にして使用率を計算する |
resources.limits.cpu: 200m | 上限。負荷をかけたとき、使用率が 200% を超えないということでもある |
demo-service.yaml は、app: demo-app の Pod に振り分ける Service。あとで負荷をかける宛先として使う。(Service は「Service: Kubernetes で Pod への宛先を固定する」を参照)
# demo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
$ kubectl apply -f demo-deployment.yaml -f demo-service.yaml
deployment.apps/demo-app created
service/demo-service created
$ kubectl get deploy,rs,pods -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/demo-app 1/1 1 1 5s nginx nginx:1.16.1 app=demo-app
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/demo-app-86547cd9f8 1 1 1 5s nginx nginx:1.16.1 app=demo-app,pod-template-hash=86547cd9f8
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/demo-app-86547cd9f8-6xk5z 1/1 Running 0 5s 10.244.2.12 k8s-study-worker2 <none> <none>
Deployment を作ると ReplicaSet が作られ、ReplicaSet が Pod を作る(Deployment → ReplicaSet → Pod の 3 段。「Deployment: Kubernetes で Pod の数と更新を管理する」を参照)。「数」は 3 段それぞれに表示される。
| 行 | 列 | 意味 |
|---|---|---|
| Deployment | READY 1/1 | 準備できている Pod の数 / 欲しい数(spec.replicas) |
| Deployment | UP-TO-DATE 1 | 今の YAML の内容で動いている Pod の数 |
| Deployment | AVAILABLE 1 | 使える状態の Pod の数 |
| ReplicaSet | DESIRED 1 | この ReplicaSet が持つべき Pod の数。Deployment の replicas がここに写される |
| ReplicaSet | CURRENT 1 / READY 1 | 今ある Pod の数 / 準備できている Pod の数 |
kubectl scale で増やす
kubectl scale deployment <Deployment 名> --replicas=<数> で、Deployment の spec.replicas を書き換える。
$ kubectl scale deployment demo-app --replicas=3
# => deployment.apps/demo-app scaled
$ kubectl get deploy,rs,pods -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/demo-app 3/3 3 3 112s nginx nginx:1.16.1 app=demo-app
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/demo-app-86547cd9f8 3 3 3 112s nginx nginx:1.16.1 app=demo-app,pod-template-hash=86547cd9f8
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/demo-app-86547cd9f8-6xk5z 1/1 Running 0 112s 10.244.2.12 k8s-study-worker2 <none> <none>
pod/demo-app-86547cd9f8-gqgsw 1/1 Running 0 6s 10.244.1.5 k8s-study-worker <none> <none>
pod/demo-app-86547cd9f8-l5qqg 1/1 Running 0 6s 10.244.2.13 k8s-study-worker2 <none> <none>
- Deployment の
READYが3/3、ReplicaSet のDESIREDが3、Pod が 3 行。数は 3 段すべてに伝わっている - 元の Pod(
demo-app-86547cd9f8-6xk5z、AGE 112s)はそのまま。新しい Pod が 2 つ足された(AGE 6s)。増やしても、動いている Pod は作り直されない - 新しい 2 つは
k8s-study-workerとk8s-study-worker2に 1 つずつ。kube-scheduler が空いている Node に散らす
kubectl scale が書き換えたのは、Deployment の spec.replicas だけ。
$ kubectl get deploy demo-app -o jsonpath='{.spec.replicas}'
# => 3
$ kubectl describe deploy demo-app | grep -A 5 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 2m28s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 0 to 1
Normal ScalingReplicaSet 42s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 1 to 3
Scaled up replica set ... from 1 to 3 は、Deployment の replicas が 3 になったのを見て、deployment-controller が ReplicaSet の DESIRED を 1 → 3 にした記録。Pod を実際に 2 つ作ったのは、その ReplicaSet を見ている replicaset-controller。
kubectl scale は、YAML を書き換えずに replicas だけを変える近道のコマンド。YAML ファイルの replicas: 1 はそのまま残っている。
減らすときに、どの Pod が消えるか
replicas を 3 → 1 に戻す。kubectl scale のすぐあとに kubectl get pods を打つと、消されかけの Pod が Terminating で見える。
$ kubectl scale deployment demo-app --replicas=1; kubectl get pods -l app=demo-app -o wide
deployment.apps/demo-app scaled
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-app-86547cd9f8-6xk5z 1/1 Terminating 0 3m34s 10.244.2.12 k8s-study-worker2 <none> <none>
demo-app-86547cd9f8-gqgsw 1/1 Running 0 108s 10.244.1.5 k8s-study-worker <none> <none>
demo-app-86547cd9f8-l5qqg 1/1 Terminating 0 108s 10.244.2.13 k8s-study-worker2 <none> <none>
$ kubectl get deploy,rs,pods -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/demo-app 1/1 1 1 3m48s nginx nginx:1.16.1 app=demo-app
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/demo-app-86547cd9f8 1 1 1 3m48s nginx nginx:1.16.1 app=demo-app,pod-template-hash=86547cd9f8
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/demo-app-86547cd9f8-gqgsw 1/1 Running 0 2m2s 10.244.1.5 k8s-study-worker <none> <none>
残ったのは k8s-study-worker にいた 1 つ(demo-app-86547cd9f8-gqgsw)。k8s-study-worker2 にいた 2 つは、最初からいた古い Pod(6xk5z)も含めて消えた。「古い Pod が残る」のではない。
減らすときにどの Pod を消すかは、replicaset-controller が次の順で決める。
| 順 | 先に消されるもの | 今回の 3 → 1 では |
|---|---|---|
| 1 | まだ Node に置かれていない(Pending)Pod | 該当なし(3 つとも Running) |
| 2 | アノテーション controller.kubernetes.io/pod-deletion-cost の値が小さい Pod | 該当なし(付けていない) |
| 3 | Pod が多く置かれている Node の Pod | worker2 に 2 つ、worker に 1 つだったので、worker2 の 2 つが先 |
| 4 | 新しく作られた Pod | (3 で決まったので、ここまで来ていない) |
3 番目の決め方のため、Node ごとの Pod の数が偏っていると、古い Pod でも消される。Pod を Node に散らして置いておくための決め方。どの Pod が残っても、Service の宛先は自動で残った Pod だけになる。
YAML を書き換えて apply する
kubectl scale は近道で、本来のやり方は「YAML を書いて apply」。replicas も同じで、YAML の spec.replicas を 3 に書き換えて apply すれば変わる。
$ kubectl apply -f demo-deployment.yaml
# => deployment.apps/demo-app configured
$ kubectl get deploy demo-app
NAME READY UP-TO-DATE AVAILABLE AGE
demo-app 3/3 3 3 5m46s
created ではなく configured。すでにある Deployment の中身を変えた。結果は kubectl scale --replicas=3 と同じで、変えているフィールドが同じなので、どちらでやっても同じ。
YAML を 1 に戻して apply すると、Pod は 1 個に戻る。
$ kubectl apply -f demo-deployment.yaml
# => deployment.apps/demo-app configured
$ kubectl get deploy demo-app
NAME READY UP-TO-DATE AVAILABLE AGE
demo-app 1/1 1 1 6m20s
ここで気を付けること。kubectl scale で 3 にしたあとに、replicas: 1 のままの YAML を apply すると、1 に戻ってしまう。YAML に書いてある値が「あるべき状態」なので、apply のたびにその値に合わせられる。公式ドキュメントも、手動スケールのあとに YAML を apply すると手動スケールは上書きされる、としている。HPA で変わった数も同じように上書きされるので、公式ドキュメントは HPA を使う Deployment には replicas を書かないことを勧めている。
kubectl scale deployment <名前> --replicas=N | YAML の replicas を書き換えて apply | |
|---|---|---|
| 変えるフィールド | Deployment の spec.replicas | 同じ |
| YAML ファイル | 変わらない(ずれる) | ファイルの値が正 |
| 向いている場面 | 今すぐ一時的に変えたい | 変えた数を残したい(Git に入れるなど) |
CPU の使用率
ここから、自分の手ではなく、CPU 使用率を見て自動で replicas を変えさせる。その前に、「使用率」が何の値なのかを見る。
$ kubectl top pods
NAME CPU(cores) MEMORY(bytes)
demo-app-86547cd9f8-gqgsw 0m 1Mi
kubectl top pods は、各 Pod が今使っている CPU とメモリ。値を集めているのは metrics-server で、15 秒ごとに更新される。nginx はアクセスがないとほぼ CPU を使わないので 0m。
HPA が見る使用率は、この CPU(cores) を、YAML の requests.cpu で割ったもの。
使用率 = 使っている CPU ÷ requests.cpu
今: 0m ÷ 100m = 0%
もし 50m 使っていたら: 50m ÷ 100m = 50%
もし 150m 使っていたら: 150m ÷ 100m = 150%(limits の 200m までは使える)
- 分母は
requests.cpu。limitsではない。requests.cpuを書いていない Deployment では使用率が計算できず、HPA は動かない(後の節で確認する) - 「使用率 50% を目標にする」は「Pod 1 個あたり 50m 使う状態に保つ」こと。Pod が足りなくて 1 個あたりの使用量が増えると Pod を増やし、余って減ると Pod を減らす
HPA を作る
# demo-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: demo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-app
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
| フィールド | 意味 |
|---|---|
apiVersion: autoscaling/v2 | HPA の apiVersion。Deployment は apps/v1、Job は batch/v1 |
scaleTargetRef | どの Deployment の replicas を変えるか。HPA は Pod を直接は触らず、Deployment の replicas を書き換えるだけ |
minReplicas / maxReplicas | replicas の下限と上限。HPA はこの範囲の外には変えない |
metrics | 何を見て判断するか。今回は CPU(name: cpu)の使用率(type: Utilization)1 つ |
averageUtilization: 50 | 目標。全 Pod の使用率の平均を 50% にする。平均が 50% を超えていれば増やし、下回っていれば減らす |
HPA の YAML には Pod の定義(template)はない。Pod の定義は Deployment が持っていて、HPA はその数だけを変える。
$ kubectl apply -f demo-hpa.yaml
# => horizontalpodautoscaler.autoscaling/demo-app created
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 0%/50% 1 5 1 5s
| 列 | 意味 |
|---|---|
REFERENCE | scaleTargetRef。Deployment/demo-app の数を変える |
TARGETS | 今の値 / 目標。cpu: 0%/50% は「CPU 使用率の平均が今 0%、目標は 50%」 |
MINPODS / MAXPODS | minReplicas / maxReplicas |
REPLICAS | 今の Deployment の replicas。HPA controller が最後に見た数 |
作った直後は TARGETS が cpu: <unknown>/50% と出ることがある(HPA controller がまだ使用率を 1 回も取っていない)。少し待ってもう一度打つと値が入る。
$ kubectl describe hpa demo-app
Name: demo-app
Namespace: default
Labels: <none>
Annotations: <none>
CreationTimestamp: Wed, 07 Oct 2026 20:52:53 +0900
Reference: Deployment/demo-app
Metrics: ( current / target )
resource cpu on pods (as a percentage of request): 0% (0) / 50%
Min replicas: 1
Max replicas: 5
Deployment pods: 1 current / 1 desired
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ScaleDownStabilized recent recommendations were higher than current one, applying the highest recent recommendation
ScalingActive True ValidMetricFound the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
ScalingLimited False DesiredWithinRange the desired count is within the acceptable range
Events: <none>
| 行 | 意味 |
|---|---|
resource cpu on pods (as a percentage of request): 0% (0) / 50% | TARGETS と同じ。括弧の (0) は平均の使用量そのもの(0m)。as a percentage of request は requests に対する割合 |
Deployment pods: 1 current / 1 desired | 今の Pod の数 / HPA が計算した「あるべき数」。同じなので何もしない |
ScalingActive True ValidMetricFound | 使用率が取れていて、判断できる状態。metrics-server が止まっていたり requests.cpu がなかったりすると、ここが False になる |
Events: は <none>。まだ数を変えていない。
負荷をかけて、増えるのを見る
別の Pod から demo-service に休みなくアクセスする。アクセスする側のプログラムとして、busybox の wget をループで回す。
nginx(demo-app。アクセスされる側) | while true; do wget ...; done(load-generator。アクセスする側) | |
|---|---|---|
| 何か | Web サーバー | demo-service に HTTP でアクセスし、返事を捨てる、を無限に繰り返すコマンド |
| CPU | アクセスがあるときだけ使う | 休みなく使う(Pod 1 個で 900m 前後) |
| HPA が見るか | 見る(app: demo-app の Pod) | 見ない(別の Pod。HPA の対象は Deployment demo-app の Pod だけ) |
# load-generator.yaml
apiVersion: v1
kind: Pod
metadata:
name: load-generator
spec:
restartPolicy: Never
containers:
- name: load
image: busybox:1.36
command: ["sh", "-c", "while true; do wget -q -O- http://demo-service > /dev/null; done"]
wget -q -O- http://demo-service > /dev/null は、Service demo-service にアクセスして、返ってきた HTML を捨てる。while true; do ...; done で無限に繰り返す。止めるには Pod を消す。
kubectl get hpa -w を付けると、値が変わるたびに行が追加される。
$ kubectl apply -f load-generator.yaml
# => pod/load-generator created
$ kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 0%/50% 1 5 1 3m46s
demo-app Deployment/demo-app cpu: 62%/50% 1 5 1 4m14s
demo-app Deployment/demo-app cpu: 62%/50% 1 5 2 4m29s
demo-app Deployment/demo-app cpu: 42%/50% 1 5 2 4m44s
demo-app Deployment/demo-app cpu: 22%/50% 1 5 2 4m59s
demo-app Deployment/demo-app cpu: 29%/50% 1 5 2 5m14s
demo-app Deployment/demo-app cpu: 42%/50% 1 5 2 5m29s
TARGETSが0%→62%。目標の50%を超えた- 次の行で
REPLICASが1→2。負荷をかけてから 45 秒ほど - その後、
TARGETSは22%〜42%に下がる。アクセスが 2 つの Pod に分かれて、1 個あたりの使用量が半分近くになったため。平均が 50% を下回っても、すぐには減らさない(次の節)
$ kubectl top pods
NAME CPU(cores) MEMORY(bytes)
demo-app-86547cd9f8-f8fft 21m 2Mi
demo-app-86547cd9f8-gqgsw 24m 3Mi
load-generator 914m 12Mi
$ kubectl get pods -l app=demo-app -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demo-app-86547cd9f8-f8fft 1/1 Running 0 104s 10.244.2.17 k8s-study-worker2 <none> <none>
demo-app-86547cd9f8-gqgsw 1/1 Running 0 15m 10.244.1.5 k8s-study-worker <none> <none>
$ kubectl get deploy demo-app -o jsonpath='{.spec.replicas}'
# => 2
- nginx の Pod が 2 つ、それぞれ 20m あまり。
load-generatorは 914m で、ほとんどの CPU を使っているのはアクセスする側。HPA が見るのは nginx の 2 つだけ。平均 (21 + 24) ÷ 2 ≒ 22m ÷ 100m = 22% がTARGETSの値 - Deployment の
spec.replicasが2になっている。kubectl scaleで自分が書き換えたのと同じフィールドを、HPA controller が書き換えた。そこから先(ReplicaSet のDESIRED→ Pod が増える)は手動スケールと同じ流れ
$ kubectl describe hpa demo-app | grep -A 5 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulRescale 2m39s horizontal-pod-autoscaler New size: 2; reason: cpu resource utilization (percentage of request) above target
$ kubectl describe deploy demo-app | grep -A 8 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 18m deployment-controller Scaled up replica set demo-app-86547cd9f8 from 0 to 1
Normal ScalingReplicaSet 12m (x2 over 16m) deployment-controller Scaled up replica set demo-app-86547cd9f8 from 1 to 3
Normal ScalingReplicaSet 11m (x2 over 14m) deployment-controller Scaled down replica set demo-app-86547cd9f8 from 3 to 1
Normal ScalingReplicaSet 2m49s deployment-controller Scaled up replica set demo-app-86547cd9f8 from 1 to 2
| 行 | 意味 |
|---|---|
HPA の SuccessfulRescale / New size: 2; reason: cpu resource utilization (percentage of request) above target | HPA controller(From: horizontal-pod-autoscaler)が「CPU 使用率が目標より上なので、数を 2 にした」 |
Deployment の Scaled up replica set ... from 1 to 2 | その結果、deployment-controller が ReplicaSet を 1 → 2 にした。kubectl scale したときの記録(from 1 to 3)と同じ形。Deployment から見ると、誰が replicas を変えたかは区別がない |
なぜ 2 個か
HPA controller は、15 秒ごとに次の計算をする。
あるべき数 = ceil( 今の数 × 今の使用率の平均 ÷ 目標 )
今回: ceil( 1 × 62% ÷ 50% ) = ceil( 1.24 ) = 2
ceilは切り上げ。1.24 → 2- 使用率が 62% ではなく 150% なら、ceil(3.0) = 3 個になる。目標との差が大きいほど、一度に多く増やす
- 今の数と計算した数の差が 10% 以内なら、変えない
- 計算した数が
maxReplicas(5)を超えたら 5 で止まる
2 個になったあとは、平均が 22%〜42% で目標の 50% を下回る。計算は ceil(2 × 0.42) = 1 になるが、減らすほうには待ち時間があるので、しばらく 2 のまま。
負荷を止めて、減るのを見る
$ kubectl delete -f load-generator.yaml
# => pod "load-generator" deleted from default namespace
$ kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 39%/50% 1 5 2 8m45s
demo-app Deployment/demo-app cpu: 40%/50% 1 5 2 8m59s
demo-app Deployment/demo-app cpu: 12%/50% 1 5 2 9m14s
demo-app Deployment/demo-app cpu: 0%/50% 1 5 2 9m29s
負荷を止めると、15 秒ごとの更新で TARGETS が 12% → 0% に戻る。でも REPLICAS は 2 のまま。使用率が下がっても、すぐには減らさない。-w は値が変わらないと行を出さないので、0% のあとは数分間なにも表示されない。
数分待って、もう一度見る。
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 0%/50% 1 5 1 14m
$ kubectl describe hpa demo-app | grep -A 6 'Events:'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulRescale 10m horizontal-pod-autoscaler New size: 2; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 56s horizontal-pod-autoscaler New size: 1; reason: All metrics below target
$ kubectl get pods -l app=demo-app
NAME READY STATUS RESTARTS AGE
demo-app-86547cd9f8-gqgsw 1/1 Running 0 24m
New size: 1; reason: All metrics below target。「見ている値がすべて目標より下なので、1 にした」。負荷を止めてから 4 分あまりたっていた- Pod は元の 1 つ(
gqgsw)が残り、増えたほうの Pod(f8fft)が消えた。2 つの Pod が別々の Node に 1 つずつだったので、前の節の決め方の 4 番目(新しい Pod が先)が当てはまる
なぜ減るのに時間がかかるのか
増やすときは待たなかったのに、減らすときだけ待つ。これは HPA の既定の動きで、減らす判断は「直近 5 分間の計算結果のうち、いちばん大きい数」を使う(公式ドキュメントでは stabilization window、安定化ウィンドウ)。
負荷を止めた直後の 15 秒ごとの計算結果: 2, 1, 1, 1, 1, ... ← 直近 5 分に「2」が残っているうちは 2 を使う
5 分たつと: 1, 1, 1, 1, 1, ... ← 全部 1 になったので 1 に減らす
- 目的は、アクセスが一瞬だけ減ったときに Pod を減らし、すぐまた増やす、という揺れ(flapping)を防ぐこと。増やすほうは、遅れると処理が間に合わなくなるので待たない
- 5 分という長さは kube-controller-manager の起動オプション
--horizontal-pod-autoscaler-downscale-stabilizationの既定値。HPA ごとに変えるなら、HPA の YAML のspecにbehaviorフィールドを足す(後の節で確認する) kubectl describe hpaのConditions:に出ていたAbleToScale True ScaleDownStabilized(「直近の計算結果にもっと大きい数があるので、それを使っている」)は、この待ちのこと
| 増やす | 減らす | |
|---|---|---|
| 判断の間隔 | 15 秒ごと | 15 秒ごと |
| 待ち時間 | なし(計算結果が今より大きければすぐ) | 直近 5 分の計算結果のいちばん大きい数を使う(既定) |
| 今回 | 負荷をかけてから 45 秒ほどで 2 に | 負荷を止めてから 4 分あまりで 1 に |
HPA を消す
$ kubectl delete -f demo-hpa.yaml
# => horizontalpodautoscaler.autoscaling "demo-app" deleted from default namespace
$ kubectl get hpa,deploy,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/demo-app 1/1 1 1 26m
NAME READY STATUS RESTARTS AGE
pod/demo-app-86547cd9f8-gqgsw 1/1 Running 0 25m
HPA は消えたが、Deployment と Pod は残っている。Job を消すと Pod が消える(「Job / CronJob: Kubernetes でバッチ処理を実行する」を参照)のとは違い、HPA は Deployment の持ち主ではなく、横から replicas を書き換えていただけ。HPA を消したあとの replicas は、HPA が最後に書いた数のまま残る。
$ kubectl delete -f demo-deployment.yaml -f demo-service.yaml
deployment.apps "demo-app" deleted from default namespace
service "demo-service" deleted from default namespace
減るまでの待ち時間を短くする
HPA の YAML の spec に、metrics と並べて behavior フィールドを足す。
# demo-hpa-fast.yaml(spec の部分。scaleTargetRef / minReplicas / maxReplicas / metrics は demo-hpa.yaml と同じ)
spec:
...
behavior:
scaleDown:
stabilizationWindowSeconds: 60
| フィールド | 意味 |
|---|---|
behavior | 増やすとき(scaleUp)と減らすとき(scaleDown)の動きを HPA ごとに調整する |
scaleDown.stabilizationWindowSeconds: 60 | 減らす判断に使う「直近の計算結果」の範囲を 60 秒にする。既定は 300(5 分) |
Deployment と Service を作り直し、この HPA と負荷の Pod を apply して、同じ手順で見る。
$ kubectl apply -f demo-deployment.yaml -f demo-service.yaml
$ kubectl apply -f demo-hpa-fast.yaml
$ kubectl apply -f load-generator.yaml
$ kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: <unknown>/50% 1 5 1 4m58s
demo-app Deployment/demo-app cpu: 14%/50% 1 5 1 5m15s
demo-app Deployment/demo-app cpu: 0%/50% 1 5 1 5m30s
demo-app Deployment/demo-app cpu: 26%/50% 1 5 1 5m45s
demo-app Deployment/demo-app cpu: 58%/50% 1 5 1 6m
demo-app Deployment/demo-app cpu: 75%/50% 1 5 2 6m15s
demo-app Deployment/demo-app cpu: 61%/50% 1 5 2 6m30s
$ kubectl delete -f load-generator.yaml
# => pod "load-generator" deleted from default namespace
$ kubectl get hpa -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 15%/50% 1 5 3 7m30s
demo-app Deployment/demo-app cpu: 4%/50% 1 5 3 7m45s
demo-app Deployment/demo-app cpu: 0%/50% 1 5 2 8m
demo-app Deployment/demo-app cpu: 0%/50% 1 5 2 8m15s
demo-app Deployment/demo-app cpu: 0%/50% 1 5 1 8m30s
- 最初の
<unknown>は、Deployment を作り直した直後で Pod の使用率がまだ取れていなかったため。15 秒後に値が入った - このときは使用率が
75%まで上がり、Pod は 3 つまで増えていた(REPLICAS 3) - 負荷を止めてから
0%になるまで 30 秒、そこから 1 分ほどで 3 → 2 → 1 と戻った。前の節の 4 分あまりより短い
requests がないと HPA は動かない
resources を書いていない Deployment に HPA を付ける。
# demo-deployment-noreq.yaml(containers の部分。resources がない)
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
$ kubectl apply -f demo-deployment-noreq.yaml
$ kubectl apply -f demo-hpa.yaml
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: <unknown>/50% 1 5 1 40s
$ kubectl describe hpa demo-app | grep -A 4 'Conditions:'
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True SucceededGetScale the HPA controller was able to get the target's current scale
ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count: failed to get cpu utilization: missing request for cpu in container nginx of Pod demo-app-6d69c7cc8c-4kl7t
TARGETSが<unknown>のまま。使用量は取れていても、分母のrequests.cpuがないので使用率が計算できないConditions:のScalingActiveがFalse、ReasonがFailedGetResourceMetric、Messageにmissing request for cpu in container nginx of Pod demo-app-...(「Pod のコンテナ nginx に cpu の requests がない」)- この状態では、負荷をかけても数は変わらない。HPA を使う Deployment には
requests.cpuを書く - HPA を作った直後に出る
<unknown>は 15〜30 秒で消えるが、こちらはrequestsを書くまで消えない。見分けはConditions:のMessage
kubectl autoscale で HPA を作る
kubectl scale に replicas を変える近道があったように、HPA を作る近道もある。kubectl autoscale deployment <名前> --cpu=<目標%> --min=<下限> --max=<上限> で、demo-hpa.yaml と同じ HPA ができる。
$ kubectl autoscale deployment demo-app --cpu=50% --min=1 --max=5
# => horizontalpodautoscaler.autoscaling/demo-app autoscaled
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
demo-app Deployment/demo-app cpu: 2%/50% 1 5 1 5s
- 名前は Deployment と同じ
demo-appになる(--nameで変えられる) kubectl get hpa demo-app -o yamlで見ると、demo-hpa.yamlと同じscaleTargetRef/minReplicas/maxReplicas/metricsが入っている- YAML が手元に残らない。本番で使う HPA は YAML を書く形にして、
kubectl autoscaleは試すときに使う
Pod を手で消しても、数は戻る
HPA がなくても、Deployment は replicas の数を保ち続ける。replicas: 3 の Pod を 1 つ手で消す。
$ kubectl scale deployment demo-app --replicas=3
# => deployment.apps/demo-app scaled
$ kubectl get pods -l app=demo-app
NAME READY STATUS RESTARTS AGE
demo-app-86547cd9f8-44jpd 1/1 Running 0 80s
demo-app-86547cd9f8-5mxhm 1/1 Running 0 5s
demo-app-86547cd9f8-qs6c4 1/1 Running 0 5s
$ kubectl delete pod demo-app-86547cd9f8-qs6c4; kubectl get pods -l app=demo-app
pod "demo-app-86547cd9f8-qs6c4" deleted from default namespace
NAME READY STATUS RESTARTS AGE
demo-app-86547cd9f8-44jpd 1/1 Running 0 2m33s
demo-app-86547cd9f8-5mxhm 1/1 Running 0 78s
demo-app-86547cd9f8-76q5g 1/1 Running 0 0s
消した直後に、新しい名前の Pod(76q5g、AGE 0s)が作られている。ReplicaSet の DESIRED 3 に対して CURRENT が 2 になったのを replicaset-controller が見て作った。HPA がやっているのは、この replicas の数そのものを変えること。「数を保つ」のは HPA ではなく ReplicaSet の仕事。
用語メモ
| 用語 | 読み方 | 意味 |
|---|---|---|
| replicas | レプリカズ | Deployment のフィールド spec.replicas。Pod をいくつ動かし続けるか |
| scale | スケール | Pod の数を変えること。増やす = scale out、減らす = scale in。まとめて horizontal scaling(水平スケール) |
| kubectl scale | キューブ コントロール スケール | Deployment の replicas を YAML なしで書き換えるコマンド |
| HPA / HorizontalPodAutoscaler | エイチピーエー / ホリゾンタル ポッド オートスケーラー | 「この Deployment の replicas を、使用率が目標になるように min〜max の間で自動で変えろ」というデータ。apiVersion: autoscaling/v2。etcd に保存される |
| scaleTargetRef | スケール ターゲット レフ | HPA のフィールド。数を変える相手(Deployment の kind と name) |
| minReplicas / maxReplicas | ミン レプリカズ / マックス レプリカズ | HPA が変える数の下限と上限 |
| averageUtilization | アベレージ ユーティライゼーション | HPA のフィールド。全 Pod の使用率の平均の目標(%) |
| utilization | ユーティライゼーション | 使用率。使っている CPU ÷ requests.cpu。requests.cpu: 100m で 50m 使っていれば 50% |
| metrics-server | メトリクス サーバー | 各 Node の kubelet から Pod の CPU / メモリの使用量を 15 秒ごとに集めるプログラム。kube-system の Pod。kubectl top と HPA が使う |
| TARGETS | ターゲッツ | kubectl get hpa の列。今の値/目標(例: cpu: 62%/50%)。取れていないときは <unknown> |
| SuccessfulRescale | サクセスフル リスケール | HPA の Events: の Reason。数を変えたときに New size: N; reason: ... と記録される |
| stabilization window | スタビライゼーション ウィンドウ | 減らすときに「直近この時間の計算結果のいちばん大きい数を使う」という待ち時間。既定 5 分。HPA の behavior.scaleDown.stabilizationWindowSeconds で変えられる |
| behavior | ビヘイビア | HPA のフィールド。増やし方・減らし方の調整(待ち時間、速さの上限) |
| HPA controller(horizontal-pod-autoscaler-controller) | エイチピーエー コントローラー | kube-controller-manager の中の係。HPA を 15 秒ごとに読み、使用率から数を計算して Deployment の replicas を書き換える |
| replicaset-controller | レプリカセット コントローラー | kube-controller-manager の中の係。ReplicaSet の DESIRED と Pod の数を合わせる(作る・消す)。減らすときにどの Pod を消すかも決める |
まとめ
- Pod の数は Deployment の
spec.replicasで決まる。Deployment → ReplicaSet(DESIRED)→ Pod の 3 段に数が伝わる kubectl scale deployment <名前> --replicas=Nも、YAML のreplicasを書き換えてapplyも、変えているのは同じフィールド。applyは YAML の値で上書きするので、kubectl scaleや HPA で変えた数は YAML をapplyすると戻る- 増やすときは動いている Pod はそのままで、足りない分だけ新しく作られる。減らすときにどの Pod を消すかは replicaset-controller が決める(
Pending→pod-deletion-cost→ Pod が多い Node の Pod → 新しい Pod) - HPA は「Deployment の
replicasを、CPU 使用率の平均が N% になるように、min〜max の間で変えろ」というデータ。etcd に保存され、読んで動くのは kube-controller-manager の中の HPA controller - 使用率 = 使っている CPU ÷
requests.cpu。Deployment にrequests.cpuがないとTARGETSが<unknown>のままで動かない。値を集めているのは metrics-server(kubectl top podsと同じ) - あるべき数 = ceil(今の数 × 使用率の平均 ÷ 目標)。15 秒ごとに計算する
- 増やすのは速い(1 分以内)。減らすのは、直近 5 分の計算結果のいちばん大きい数を使うので、負荷が下がってから数分かかる。
behavior.scaleDown.stabilizationWindowSecondsで変えられる - HPA controller は Pod を直接作らず、Deployment の
replicasを書き換えるだけ。kubectl get hpaのTARGETSとREPLICAS、kubectl describe hpaのEvents:で動きが追える - HPA を消しても Pod は残る。HPA は Deployment の持ち主ではない
参考 URL
- [official] 水平Pod自動スケーリング | Kubernetes
- [official] HorizontalPodAutoscaler Walkthrough | Kubernetes
- [official] Deployment | Kubernetes
- [official] ReplicaSet | Kubernetes
- [official] リソースメトリクスパイプライン | Kubernetes
- [official] kube-controller-manager | Kubernetes
- [GitHub] kubernetes-sigs/metrics-server

