Kubernetes の Pod にはpriorityという属性があり、その名の通り Pod の優先度を示している。
重要な Pod のpriorityを高く設定することで、 Node へのスケジューリングが行われやすくすることができる。
この記事では、priorityの使い方やそれが解決する課題を具体的に見ていく。
この記事の内容は Kubernetes の1.34で動作確認した。
要求しているだけのリソースがない Node には Pod はスケジューリングされない
事前準備としてまずクラスタを用意する。この記事では Amazon EKS を使う。
t3.smallの Node を 1 つだけ用意した。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-1-16.ap-northeast-1.compute.internal Ready <none> 62m v1.34.2-eks-ecaa3a6
$ kubectl topコマンドを使えるようにしたいので、$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlを実行しておく。
現在の状況を確認してみる。
この Node の使用可能なメモリは約 1.4 Gi 。
$ kubectl describe node ip-10-0-1-16.ap-northeast-1.compute.internal | grep -A5 Allocatable Allocatable: cpu: 1930m ephemeral-storage: 18181869946 hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 1466568Ki
そのうち 471 Mi が現在使われている。
$ kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% ip-10-0-1-16.ap-northeast-1.compute.internal 28m 1% 471Mi 32%
この Node に配置されている Pod のresources.requests.memoryの合計値は 340 Mi 。
まだ何のリソースも作成していないのに0ではないのは、Kubernetes によって自動的に作られる Pod があるため。それらの合計が 340 Mi 。
$ kubectl describe node ip-10-0-1-16.ap-northeast-1.compute.internal | grep -A10 "Allocated resources" Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 450m (23%) 0 (0%) memory 340Mi (23%) 340Mi (23%) ephemeral-storage 0 (0%) 0 (0%) hugepages-1Gi 0 (0%) 0 (0%) hugepages-2Mi 0 (0%) 0 (0%) Events: <none>
このクラスタにリソースを作成して、挙動を確認していく。
まず、以下の内容のmanifest.yamlを作成し、$ kubectl apply -f manifest.yamlする。
apiVersion: apps/v1 kind: Deployment metadata: name: resource-filler spec: replicas: 2 selector: matchLabels: app: filler template: metadata: labels: app: filler spec: containers: - name: nginx image: docker.io/library/nginx:1.27 resources: requests: memory: "500Mi" --- apiVersion: batch/v1 kind: CronJob metadata: name: important-cronjob spec: schedule: "*/5 * * * *" jobTemplate: spec: template: spec: restartPolicy: Never containers: - name: work image: docker.io/library/busybox:1.36 command: ["sh", "-c", "for i in $(seq 1 60); do date; sleep 1; done"] resources: requests: memory: "300Mi"
Nginx を image とした Pod が 2 つ作られ、それとは別にimportant-cronjobという名前の CronJob も作られた。
この CronJob は 5 分毎に Job を作成する。作成された Job は 1 分間、現在時刻の出力を毎秒行う。
$ kubectl get pods NAME READY STATUS RESTARTS AGE resource-filler-6767d9ddc6-gdhsz 1/1 Running 0 5s resource-filler-6767d9ddc6-mdm8c 1/1 Running 0 5s $ kubectl get cronjobs NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE important-cronjob */5 * * * * <none> False 0 <none> 11s
ここで重要なのは、 Nginx のresources.requests.memoryが500Miであること。
Pod が 2 つあるため、resources.requests.memoryの合計値は1000Mi増えて1340Miになった。
$ kubectl describe node ip-10-0-1-16.ap-northeast-1.compute.internal | grep -A10 "Allocated resources" Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 450m (23%) 0 (0%) memory 1340Mi (93%) 340Mi (23%) ephemeral-storage 0 (0%) 0 (0%) hugepages-1Gi 0 (0%) 0 (0%) hugepages-2Mi 0 (0%) 0 (0%) Events: <none>
そして、 Job を実行するための Pod のresources.requests.memoryは300Miである。
しかし Node の使用可能メモリは約 1.4 Gi なので、もう300Miも残っていない。
このようなときにどうなるのかというと、 Job を実行するための Pod を Node にスケジューリングすることができない。
数分待つとimportant-cronjob-29438710-sh5q4という名前の Pod が作られるが、Pendingとなっている。
$ kubectl get jobs NAME STATUS COMPLETIONS DURATION AGE important-cronjob-29438710 Running 0/1 3m2s 3m2s $ kubectl get pods NAME READY STATUS RESTARTS AGE important-cronjob-29438710-sh5q4 0/1 Pending 0 3m5s resource-filler-6767d9ddc6-gdhsz 1/1 Running 0 7m16s resource-filler-6767d9ddc6-mdm8c 1/1 Running 0 7m16s
調べてみると、メモリ不足(Insufficient memory)のため Node に配置できない、というイベントが発生している。
$ kubectl get events --field-selector involvedObject.name=important-cronjob-29438710-sh5q4 LAST SEEN TYPE REASON OBJECT MESSAGE 4m31s Warning FailedScheduling pod/important-cronjob-29438710-sh5q4 0/1 nodes are available: 1 Insufficient memory. no new claims to deallocate, preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
スケジューリングできるかどうかはresources.requestsで決まることに注意する。
実際のリソース使用量ではない。
実際のメモリ使用量を確認してみると484Miであり、まだまだ余裕がある。
$ kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% ip-10-0-1-16.ap-northeast-1.compute.internal 29m 1% 484Mi 33%
しかしresources.requests.memoryの合計は1340Miであるため、resources.requests.memoryが300Miの Pod はスケジューリングできない。
priority に基づいた Pod の退避とスケジューリング
上記のケースにおいて Job の Pod のpriorityを Nginx の Pod のそれより高くした場合、 Nginx の Pod が Node から退避させられることでresources.requests.memoryの空きが生まれ、 Job の Pod をスケジューリングさせることができる。
先程見た 3 つの Pod のpriorityは、デフォルト値の0。
$ kubectl get pod important-cronjob-29438710-sh5q4 -o jsonpath='{.spec.priority}'; echo 0 $ kubectl get pod resource-filler-6767d9ddc6-gdhsz -o jsonpath='{.spec.priority}'; echo 0 $ kubectl get pod resource-filler-6767d9ddc6-mdm8c -o jsonpath='{.spec.priority}'; echo 0
priorityは数値が大きいほど優先度が高い。
なので Job の Pod のpriorityを1以上にすれば、スケジューリングされるようになるはずである。
$ kubectl delete -f manifest.yamlを実行し、一旦現状のリソースを全て消す。
manifest.yamlに以下の変更を加える。
@@ -1,3 +1,10 @@ +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: middle-priority +value: 10 +globalDefault: false +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -29,6 +36,7 @@ spec: spec: template: spec: + priorityClassName: middle-priority restartPolicy: Never containers: - name: work
priorityを設定したい場合、マニフェストファイルに直接priorityを書くのではなく、 PriorityClass というリソースを通して設定する。
上記の例ではmiddle-priorityという名前の PriorityClass を作っている。valueが優先度。globalDefaultについては後述する。
そして Pod のpriorityClassNameフィールドに、その Pod に適用する PriorityClass の名前を書く。
この状態で$ kubectl apply -f manifest.yamlする。
PriorityClass 一覧を見てみると、middle-priorityという PriorityClass が作られている。それ以外の PriorityClass は Kubernetes が自動的に作成したもの。
$ kubectl get pc NAME VALUE GLOBAL-DEFAULT AGE PREEMPTIONPOLICY middle-priority 10 false 2m30s PreemptLowerPriority system-cluster-critical 2000000000 false 137m PreemptLowerPriority system-node-critical 2000001000 false 137m PreemptLowerPriority
Nginx の Pod が 2 つあるが、どちらもpriorityClassNameフィールドは存在せず、priorityは0。
$ kubectl get pods NAME READY STATUS RESTARTS AGE resource-filler-6767d9ddc6-255gf 1/1 Running 0 16s resource-filler-6767d9ddc6-qqsks 1/1 Running 0 16s $ kubectl get pod resource-filler-6767d9ddc6-255gf -o jsonpath='{.spec.priorityClassName}'; echo $ kubectl get pod resource-filler-6767d9ddc6-255gf -o jsonpath='{.spec.priority}'; echo 0 $ kubectl get pod resource-filler-6767d9ddc6-qqsks -o jsonpath='{.spec.priorityClassName}'; echo $ kubectl get pod resource-filler-6767d9ddc6-qqsks -o jsonpath='{.spec.priority}'; echo 0
数分経つとimportant-cronjob-29438765-bk9b7が作られるが、priorityClassNameが設定されており、priorityは10になっている。
$ kubectl get pods NAME READY STATUS RESTARTS AGE important-cronjob-29438765-bk9b7 1/1 Running 0 12s resource-filler-6767d9ddc6-255gf 1/1 Running 0 3m57s resource-filler-6767d9ddc6-txmm2 0/1 Pending 0 12s $ kubectl get pod important-cronjob-29438765-bk9b7 -o jsonpath='{.spec.priorityClassName}'; echo middle-priority $ kubectl get pod important-cronjob-29438765-bk9b7 -o jsonpath='{.spec.priority}'; echo 10
そして、important-cronjob-29438765-bk9b7はRunningである。つまり Node にスケジューリングされている。
また、よく見ると Nginx の Pod が先程とは変わっている。
| before | after |
|---|---|
resource-filler-6767d9ddc6-255gf✅️ Running |
resource-filler-6767d9ddc6-255gf✅️ Running |
resource-filler-6767d9ddc6-qqsks✅️ Running |
resource-filler-6767d9ddc6-txmm2🟡 Pending |
resource-filler-6767d9ddc6-qqsksが消えてその代わりにresource-filler-6767d9ddc6-txmm2が作られたが、resource-filler-6767d9ddc6-txmm2はPendingになっている。
これは、相対的にpriorityの高いimportant-cronjob-29438765-bk9b7をスケジューリングするために、resource-filler-6767d9ddc6-qqsksが退避させられたため。
$ kubectl get events --field-selector reason=Preempted LAST SEEN TYPE REASON OBJECT MESSAGE 102s Normal Preempted pod/resource-filler-6767d9ddc6-qqsks Preempted by pod 52bbafdb-730a-4244-ac2c-178c7d2b71c0 on node ip-10-0-1-16.ap-northeast-1.compute.internal
Deployment のreplicasが2なのですぐにresource-filler-6767d9ddc6-txmm2が作られたが、スケジューリングできる Node がなく、自身よりpriorityが低い Pod もないため、Pendingになっている。
important-cronjob-29438765-bk9b7が終了するとresources.requests.memoryに空きが生まれるため、resource-filler-6767d9ddc6-txmm2がスケジューリングされてRunningになる。
$ kubectl get pods NAME READY STATUS RESTARTS AGE important-cronjob-29438765-bk9b7 0/1 Completed 0 73s resource-filler-6767d9ddc6-255gf 1/1 Running 0 4m58s resource-filler-6767d9ddc6-txmm2 1/1 Running 0 73s
globalDefault の挙動
PriorityClass のglobalDefaultをtrueにすると、priorityClassNameを指定していない Pod のpriorityClassNameがこの PriorityClass になる。
これを確認するため、まず$ kubectl delete -f manifest.yamlを実行して現状のリソースを全て消す。
その後manifest.yamlに以下を追記して$ kubectl apply -f manifest.yamlする。
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 100 globalDefault: true
そうすると、 Nginx の Pod のpriorityClassNameがhigh-priorityになる。
$ kubectl get pods NAME READY STATUS RESTARTS AGE resource-filler-6767d9ddc6-4frjm 1/1 Running 0 5s resource-filler-6767d9ddc6-5f28m 1/1 Running 0 5s $ kubectl get pod resource-filler-6767d9ddc6-4frjm -o jsonpath='{.spec.priorityClassName}'; echo high-priority $ kubectl get pod resource-filler-6767d9ddc6-4frjm -o jsonpath='{.spec.priority}'; echo 100 $ kubectl get pod resource-filler-6767d9ddc6-5f28m -o jsonpath='{.spec.priorityClassName}'; echo high-priority $ kubectl get pod resource-filler-6767d9ddc6-5f28m -o jsonpath='{.spec.priority}'; echo 100
Nginx の Pod のpriority(100)が Job の Pod のそれ(10)よりも大きくなったので、リソースが足りなくなっても Nginx の Pod が退避させられることはなくなり、 Job の Pod がPendingのままになる。
$ kubectl get pods NAME READY STATUS RESTARTS AGE important-cronjob-29438780-q8kkb 0/1 Pending 0 29s resource-filler-6767d9ddc6-4frjm 1/1 Running 0 89s resource-filler-6767d9ddc6-5f28m 1/1 Running 0 89s $ kubectl get events --field-selector involvedObject.name=important-cronjob-29438780-q8kkb LAST SEEN TYPE REASON OBJECT MESSAGE 78s Warning FailedScheduling pod/important-cronjob-29438780-q8kkb 0/1 nodes are available: 1 Insufficient memory. no new claims to deallocate, preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.