Kubernetesでは、アプリケーションの可用性や運用効率を高めるため、リソース変更時のダウンタイムを極力抑える取り組みが進んでいます。従来、CPU やメモリのリソースを変更する際には、Pod の再作成やコンテナ再起動が必要でしたが、In-Place Update of Pod Reourcesは、稼働中のコンテナに対してリソース更新を適用できる新たな仕組みとしてKubernetes 1.33からbeta版に昇格するなど、その実装が進んでいます。
本記事では、Kubernetes 1.33におけるIn-Place Update of Pod Resources(KEP-1287)の技術背景から具体的な有効化手順、検証例、内部動作の概要や今後の展望までを、実践的な視点で詳しく解説します。
1. 背景と意義
例えばバッチ処理で初動にのみリソース配分を高めて起動を高速にしたいなどの用途があり、Pod のリソース(CPU やメモリ)を変更する場合、従来以下のような問題がありました。
- サービス停止
Pod の再作成やコンテナ再起動によって、一時的なダウンタイムが発生する - 運用コストの増大
再起動による状態復元や、必要なシーケンス管理など、運用が複雑になる 再起動であるため、ステートフルなPodではそれまでの計算は無駄になる
In-Place Update of Pod Resources は、こうした課題を解決するため、動作中の Pod に対してリソース変更を適用できる仕組みです。これにより、次のようなメリットが期待できます。
- 継続的なサービス提供
アプリケーションの再起動を伴わずにリソース変更が可能 - 柔軟なリソース管理
リアルタイムな需要変動に応じたリソースの微調整が可能となる
2. 仕組み
機能概要
In-Place Update of Pod Resources は、既存のPodの各コンテナに割り当てたリソース(CPUやメモリのrequests/limits)を、コンテナを停止せず動的に更新可能にします。
たとえば、あるPod内のコンテナに最初CPU 500m(0.5コア)を割り当てていたのを、後から800mに増やしたり、メモリを1Giから2Giに増やすことができます。従前、Podのスペックは不変とされ、リソース量を変更するにはPod削除->新規Pod投入が必要でした。しかしこの機能により、PodのSpec上のリソース値が一部可変になり、変更をAPIサーバに送るとkubeletがその場でリソース調整を行ってくれます。
更新後は、Pod のステータスに新たなフィールド(たとえば allocatedResources)が追加され、変更の状況がリアルタイムに反映されます。
コンテナの再起動ポリシー
ただ、すべての場合にコンテナを無停止で変更できるわけではありません。例えばメモリーを縮小する際には再起動が必要となります。これは、多くのアプリケーションにおいてメモリの上限を動的に縮小することは難しく安全策として再起動が選ばれるためです。しかし、CPUの割当て縮小にはその必要が無いためCPUとメモリーのそれぞれにリソース変更時の再起動ポリシーを適用するかを制御する仕組み、resizePolicy
があります。
Podの各コンテナに設定できる resizePolicy
によって、リソース変更時の挙動が次のように制御されます。
NotRequired
変更時に再起動不要な場合に指定。実際のcgroup設定が即時に更新され、Pod は継続して稼働します。RestartContainer
リソースの変更がコンテナの動作に影響する場合は、再起動を伴うことで新たな設定を適用します。
resizePolicy
が未設定の場合はNotRequired
が使用されます。
Kubernetesにおける各コンポーネントの役割
In-Place Updateの仕組みを理解するために、Kubernetesとランタイムがどのような役割を果たすかを簡単におさらいします。
ここでは、ランタイムにcontainerdとruncを使用していると仮定します。
kubelet
kubeletは各Kubernetesノード上で動作するエージェントで、Pod(コンテナ群)のライフサイクルを管理する中核です。コントロールプレーからPodのスケジューリング指示を受け取ると、指定されたコンテナイメージをpullし、コンテナを起動します。また、実行中のコンテナのヘルスチェックや再起動ポリシーに基づく再起動処理、リソースの制御も担っています。各Podのcgroup(Linuxカーネルに存在するプロセスグループの使用リソースの制御機構)を管理し、ノードにおけるリソース割当てが適切に行われるように調整を行います。
containerd
containerdはDockerから切り離されたコンテナ実行エンジンで、よりシンプルなコンテナ管理に特化したKubernetesとの統合が進んだランタイムです。kubeletはcontainerdを通して間接的にコンテナを操作し、containerdはコンテナ実行の低レベル部分を次に説明するruncなどのOCIランタイムに委譲します。
runc
runcはOCIランタイムの一つで、コンテナのcgroup設定やLinuxネームスペースの設定を行い、実際にプロセスを起動させる役割を持ちます。runcには稼働中のコンテナのリソース制限を変更するrunc updateコマンドが用意されており、これによりCPUやメモリーのリソース割当てを実行中に変更することが可能になっています。In-Place Updateはこの仕組みを利用してリソース割当ての更新を可能にしています。
内部処理の流れ
In-Place Updateではどのコンテナのリソースをどのような優先度でどう変更するかをkubeletが判断します。ソースコードではpkg/kubelet/kuberuntime/kuberuntime_manager.go
(GitHub)というファイルにそのロジックが実装されています。リソース変更が行われた際の処理の流れをおおまかに追っていきます。
リソース更新の有無と対象コンテナの判定
まず、kubeletのReconciliation Loopの中でPod内の各コンテナのリソースについて、Podのあるべき状態であるspecと現在の状態であるstatusを比較します。差分がある場合、resizePolicyがRestartContainerの場合は通常の再起動処理に載せられ、NotRequiredの場合はIn-Place Updateが可能としてContainersToUpdate
リストに追加します。
更新のキューイング
更新時には、各コンテナごとの判定結果をもとに、後段で行うアクションをキューに積みます。この際、リソースの増減によってキューへの積まれ方が異なり、リソースを増やす処理はリストの末尾に追加し、減らす処理はリストの先頭に挿入されます。先にリソース開放を行うことはノード上に余裕を作ることにつながり、リソースが逼迫するといったことを避けることができます
kubeletによるコンテナ更新
In-Place UpdateではPod内の各コンテナのリソース割当てあるべき状態にないため、containerdに指示を出しあるべき状態に寄せるためノード上のリソースを予約を行います。
まず、Podに対して設定されているリソース上限が変更されます。Pod全体でリソース不足が生じないようここでも増減で処理が異なり、リソース増加の場合はコンテナ更新前にPodのcgroup上限を引き上げ、リソース減少の場合はコンテナ更新後にPodのcgroup上限を引き下げるといったことが行われます。
Podの調整が完了すると各コンテナへの適用が行われます。kubeletは先ほど準備したContainersToUpdate
リストをリソース種類ごとに処理します。たとえばまずCPUに関する変更リストを取り出し、順番に各コンテナのCPUリソースを更新します。その際に呼ばれるのがupdatePodContainerResources
という関数です。この中では、各コンテナの新しいリソース設定値を計算した上で、CRI経由でランタイムに対してコンテナのリソース更新を指示します。
CPUの更新が終われば、次にメモリの更新リストについて同様の処理が行われます。
そして、Podに対してのcgroup減少方向の処理が行われます。
containerdとruncによるリソース更新
kubeletから更新の要求を受け取ったcontainerdは、その内容に従って該当コンテナのリソース設定を変更します。この際、特にメモリーの制限を下げる場合runcにおいては現在のメモリー使用量を考慮せずに新しい制限を適用します。つまり、500MiB使っている状態でメモリ上限を400MiBに下げることができてしまい、その結果kernelがそのコンテナを殺すことになります。このため、Kubernetesではメモリー減少方向の操作を行う際にはresizePolicyがRestartContainerであることが必須とされています。
確保されたリソースのステータス反映
containerdから得られる情報に基づいてステータスを更新し、確保できているリソースをマークします。変更後のリソース割当はkubectl get pod -o yaml
のstatus.contaienerStatuses.allocatedResouces
から確認できます。
status:
containerStatuses:
- allocatedResources:
cpu: 500m
memory: 256Mi
なお、1.33より前ではstatus.resize
に変更の状態が表示されていましたが、1.33で廃止されています。
これはstatus.resize
が2つのステータスを持つ可能性があることや、メモリ制限と実際のメモリ使用量をキャプチャするステータスがないためにInProgressのまま状態が変更されないことが主な理由です。
https://github.com/kubernetes/kubernetes/issues/128922
* Race condition setting the status: the kubelet can overwrite the Proposed status set by the API server ([FG:InPlacePodVerticalScaling] Race condition setting pod resize status #125394)
* Can technically have 2 parallel resize statuses: a {Proposed/Deferred/Infeasible} resize when desired resources != allocated resources, and an InProgress resize when allocated resources != actual resources. Currently the former just overrides the latter.
* Separate mechanism for surfacing details related to error states (Deferred, Infeasible, stuck InProgress)
* No status to capture desired memory limit < actual memory usage, which can lead to a resize stuck in InProgress (maybe this should move to the deferred state?)
代替として、PodConditionにResizeStatusが追加されています。
https://github.com/kubernetes/kubernetes/pull/130733
3. In-Place Update of Pod Resources の有効化と検証
Feature Gate の有効化
In-Place Update of Pod Resources を利用するには、各ノードおよび制御プレーンコンポーネントで InPlacePodVerticalScaling
の Feature Gate を有効にする必要があります。 たとえば、minikube を利用する場合は、以下のコマンドで起動します。
minikube start --feature-gates=InPlacePodVerticalScaling=true
なお記事執筆時点の最新バージョンであるminikube v1.35.0では、Kubernetesのバージョンを指定しなかった際のデフォルトがv1.32.0となるため、次のようにv1.33.0を明示的に指定します。
minikube start --feature-gates=InPlacePodVerticalScaling=true --kubernetes-version=1.33.0
これにより、各コンポーネントでIn-Place Update of Pod Resourcesが有効化され、実際のリソース更新操作が可能になります。
サンプル Pod の作成
以下は、リソース要求と制限が設定された Podを作成するYAMLです。まず、1.33からデフォルトとなったResizePoliceのNotRequiredを試していきます。
apiVersion: v1
kind: Pod
metadata:
name: inplace-test
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"
この YAML を適用し、Pod が正常に稼働していることを確認します。
kubectl apply -f inplace-test.yaml
kubectl get pods -o yaml
status:
(略)
containerStatuses:
- allocatedResources:
cpu: 500m
memory: 256Mi
(略)
restartCount: 0
リソースの更新と検証
リソース制限の拡大
Pod が稼働中に、たとえば CPU リソースの変更を行います。以下のコマンドで、CPUとメモリーのRequest, Limitを拡大します。
kubectl patch pod inplace-test \
--subresource resize --patch \
'{"spec":{"containers":[{"name":"nginx", "resources":{"requests":{"cpu":"1000m","memory":"512Mi"}, "limits":{"cpu":"1000m","memory":"512Mi"}}}]}}'
更新後、Pod のstatus.containerStatuses.allocatedResourcesに割当されたリソースが表示されます。 また、コンテナの再起動回数が変わらないことを確認することで、再起動なしに CPUとメモリー割り当ての更新が実現されていることがわかります。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
inplace-test 1/1 Running 0 53s
kubectl get pod inplace-test -o yaml
status:
(略)
containerStatuses:
- allocatedResources:
cpu: 1
memory: 512Mi
(略)
restartCount: 0
実際にnginxコンテナのリソース制限を行っているcgroupの値も確認します。
minikube ssh
$ docker ps --filter "name=nginx" --format '{{.ID}}\t{{.Names}}'
324a8969b4b1 k8s_nginx_inplace-test_default_6622ff2a-ff64-4384-8
fac-28543c0b55d7_2
324a8969b4b1の部分に表示されるコンテナIDを使用します。
$ sudo docker inspect -f '{{.State.Pid}}' 324a8969b4b1
10246
10246がPIDです。
$ CGROUP=$(awk -F: '/^0::/ {print $3}' /proc/10246/cgroup)
$ cat /sys/fs/cgroup${CGROUP}/memory.max
536870912
$ cat /sys/fs/cgroup${CGROUP}/cpu.max
100000 100000
536870912byte=512MiB、100000/100000=1で設定値と一致していることが確認できます。
リソース制限の縮小(RestartPolicy: NotRequired)
縮小方向ではCPUとメモリーで取り扱いが違います。
まずCPUを縮小してみます。
kubectl patch pod inplace-test \
--subresource resize --patch \
'{"spec":{"containers":[{"name":"nginx", "resources":{"requests":{"cpu":"500m"}, "limits":{"cpu":"500m"}}}]}}'
正常に縮小できました。
kubectl get pod -o yaml
status:
(略)
containerStatuses:
- allocatedResources:
cpu: 500m
memory: 512Mi
(略)
restartCount: 0
次に、メモリーを縮小してみます。
kubectl patch pod inplace-test \
--subresource resize --patch \
'{"spec":{"containers":[{"name":"nginx", "resources":{"requests":{"memory":"256Mi"}, "limits":{"memory":"256Mi"}}}]}}'
するとエラーとなり次のように表示されます。
The Pod "inplace-test" is invalid: spec.containers[0].resources.limits[memory]: Forbidden: memory limits cannot be decreased unless resizePolicy is RestartContainer
表示されるように、メモリーの縮小にはresizePolicyをRestartContainerに設定する必要があります。
リソース制限の縮小(RestartPolicy: RestartContainer)
次に、CPU は指定無しのデフォルト動作、再起動なしで変更可能な「NotRequired」として、メモリは「RestartContainer」としてメモリの縮小ができるよう再起動を伴う設定を試してみます。
apiVersion: v1
kind: Pod
metadata:
name: inplace-test
spec:
containers:
- name: nginx
image: nginx
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: RestartContainer
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
先ほどのinplace-testを削除し、CPUのResizePolicyを変更したPodを作成します。
kubectl delete pod inplace-test
kubectl apply -f inplace-test-memory.yaml
kubectl get pods -o yaml
status:
(略)
containerStatuses:
- allocatedResources:
cpu: 500m
memory: 512Mi
(略)
restartCount: 0
先ほどと同じように縮小してみます
kubectl patch pod inplace-test \
--subresource resize --patch \
'{"spec":{"containers":[{"name":"nginx", "resources":{"requests":{"memory":"256Mi"}, "limits":{"memory":"256Mi"}}}]}}'
すると今度は縮小の要求が成功します。設定したResizePolicy通り、Containerが再起動されRestartCountが増加していることがわかります。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
inplace-test 1/1 Running 1 90s
status:
(略)
containerStatuses:
- allocatedResources:
cpu: 500m
memory: 256Mi
(略)
restartCount: 1
なお、メモリー割当の縮小における制限は将来的に緩和される予定とKEPに記載があります。
Memory Limit Decreases
Setting the memory limit below current memory usage can cause problems. If the kernel cannot reclaim sufficient memory, the outcome depends on the cgroups version. With cgroups v1 the change will simply be rejected by the kernel, whereas with cgroups v2 it will trigger an oom-kill.
In the initial beta release of in-place resize, we will disallow
PreferNoRestart
memory limit decreases, enforced through API validation. The intent is for this restriction to be relaxed in the future, but the design of how limit decreases will be approached is still undecided.Memory limit decreases with
RestartRequired
are still allowed.
- https://docs.google.com/document/d/1cEFLXKwNOSNLAkzyhoJUgkBW0OiX-9bXB_aJV7OAypw/edit?tab=t.0
- https://github.com/kubernetes/kubernetes/issues/129152
4. まとめ
In-Place Update of Pod Resources は、従来の再起動を伴うリソース更新の概念を一新し、稼働中のコンテナに対してダイナミックなリソース調整を可能にする画期的な機能です。
1.33より前のバージョンではAlpha扱いでしたが、1.33以降ではBeta昇格やfeature-gatesの設定のみでデフォルト動作も変更されるなど、いよいよGAへと近づいています。
https://github.com/kubernetes/kubernetes/releases/tag/v1.33.0