Ritolabo
  1. Home
  2. Kubernetes
  3. HPA: Kubernetes で負荷に応じて Pod を自動でスケールする

HPA: Kubernetes で負荷に応じて Pod を自動でスケールする

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

アクセスが増えたら 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

  1. 環境
  2. 手動スケールと HPA の違い
    1. HPA はどこにあるか
  3. Deployment と Service を作る
  4. kubectl scale で増やす
  5. 減らすときに、どの Pod が消えるか
  6. YAML を書き換えて apply する
  7. CPU の使用率
  8. HPA を作る
  9. 負荷をかけて、増えるのを見る
    1. なぜ 2 個か
  10. 負荷を止めて、減るのを見る
    1. なぜ減るのに時間がかかるのか
  11. HPA を消す
  12. 減るまでの待ち時間を短くする
  13. requests がないと HPA は動かない
  14. kubectl autoscale で HPA を作る
  15. Pod を手で消しても、数は戻る
  16. 用語メモ
  17. 参考 URL

環境

kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)

手動スケールと 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 の間

出てくる名前は次のとおり。

名前内容
replicasDeployment の 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: 1Pod を 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 段それぞれに表示される。

行列意味
DeploymentREADY 1/1準備できている Pod の数 / 欲しい数(spec.replicas)
DeploymentUP-TO-DATE 1今の YAML の内容で動いている Pod の数
DeploymentAVAILABLE 1使える状態の Pod の数
ReplicaSetDESIRED 1この ReplicaSet が持つべき Pod の数。Deployment の replicas がここに写される
ReplicaSetCURRENT 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該当なし(付けていない)
3Pod が多く置かれている Node の Podworker2 に 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=NYAML の 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/v2HPA の apiVersion。Deployment は apps/v1、Job は batch/v1
scaleTargetRefどの Deployment の replicas を変えるか。HPA は Pod を直接は触らず、Deployment の replicas を書き換えるだけ
minReplicas / maxReplicasreplicas の下限と上限。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
列意味
REFERENCEscaleTargetRef。Deployment/demo-app の数を変える
TARGETS今の値 / 目標。cpu: 0%/50% は「CPU 使用率の平均が今 0%、目標は 50%」
MINPODS / MAXPODSminReplicas / 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 targetHPA 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


[Prev] Job / CronJob: Kubernetes でバッチ処理を実行する

Author

rito

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級