DUICUO

ポッドの垂直スケーリングの実施 - 4年間のKEPと2年間のPR


この提案は、Podやそのコンテナを再起動することなく、Podのリソース要求と制限をその場で更新できるようにすることを目的としています。このアプローチの核となるアイデアは、必要なリソースを表すPodSpecのResourcesを可変にすることです。さらに、PodStatusはPodに割り当てられたリソースを反映し、Podとそのコンテナに適用された実際のリソースに関する情報を提供するように拡張されます。

さらに、この提案では、コンテナのCPUおよびメモリリソース構成を実行時に管理するためのContainer Runtime Interface(CRI)APIの改善が提案されており、UpdateContainerResources CRI APIを拡張してWindowsやLinux以外の将来のランタイムとの互換性を確保することも試みられています。また、ContainerStatus CRI APIを拡張して、Kubeletがコンテナに現在設定されているリソースを検出できるようにすることも求められています。

理由

さまざまな理由により、ポッドに割り当てられたコンテナ リソースを変更する必要がある場合があります。このようなシナリオは多数あります。

  • Pod によって処理される負荷が大幅に増加し、現在のリソースが不足している場合。
  • 言い換えれば、負荷は大幅に軽減されましたが、割り当てられたリソースは使用されていません。
  • 非効率的な資源配分

現在、PodSpec内のコンテナリソースは不変であるため、リソース割り当てを変更するにはPodを再作成する必要があります。多くのステートレスワークロードはこのような中断に耐えられるように設計されていますが、特にPodレプリカの数が少ない場合など、一部のワークロードはより影響を受けやすい傾向があります。

さらに、ステートフルまたはバッチ ワークロードの場合、ポッドの再起動は重大な中断動作となり、ワークロードの可用性の低下や運用コストの増加につながる可能性があります。

ポッドの再作成やコンテナの再起動を行わずにリソース変更を許可することで、この問題に直接対処できます。さらに、インプレースポッドの垂直スケーリングでは、コンテナランタイムインターフェース(CRI)を利用してポッドコンテナのCPUおよびメモリのリクエスト/制限を更新します。

現在の CRI API には、対処する必要があるいくつかの欠点があります。

  • UpdateContainerResources CRI API には、Linux コンテナー用に更新するコンテナー リソースを記述するパラメーターが必要ですが、これは Windows コンテナーや将来的には Linux 以外のランタイムには適用されない可能性があります。
  • Kubelet がコンテナ ランタイムからコンテナに設定されている CPU およびメモリの制限を照会および検出できるようにする CRI メカニズムは存在しません。
  • UpdateContainerResources CRI API を処理する際の予想される実行時動作は、適切に定義または文書化されていません。

ターゲット

  • 主に: コンテナを再起動せずにコンテナのリソース要求と制限を変更できます。
  • セカンダリ: インプレース リソース調整が不可能な場合に、参加者 (ユーザー、VPA、StatefulSet、JobController) が続行方法を決定できるようにします。
  • セカンダリ: 再起動せずにリソースのサイズを変更できるコンテナーをユーザーが指定できるようにします。

さらに、この提案には CRI API の実装目標が 2 つあります。

  • UpdateContainerResources を変更して、Windows コンテナーだけでなく、Linux 以外のランタイム管理コンテナーでも機能するようにします。
  • 実行時にコンテナに現在適用されている CPU およびメモリ リソース構成を照会するための CRI API メカニズムを提供します。

提案のもう 1 つの目標は、リソースの更新を処理する際のコンテナ ランタイムの予想される動作をより適切に定義し、文書化することです。

提案

APIの変更

PodSpec はコンテナ リソースの要求と制限に関して可変であり、PodStatus は Pod とそのコンテナに割り当てられたリソースを表示するように拡張されています。

  • Pod.Spec.Containers[i].Resourcesは、Podリソースの予想される状態を表す純粋な宣言になります。
  • Pod.Status.ContainerStatuses[i].ResourcesAllocated(新しいフィールド、タイプ v1.ResourceList) は、Pod とそのコンテナに割り当てられたノード リソースを表します。
  • Pod.Status.ContainerStatuses[i].Resources (v1.ResourceRequirements タイプの新しいフィールド) には、Pod とそのコンテナが保持する実際のリソースが表示されます。
  • Pod.Status.Resize(new field, type map[string]string) は、指定されたコンテナ上の指定されたリソースに何が起こっているかを説明します。

新しいResourcesAllocatedフィールドは、進行中のサイズ変更操作を示し、ノードチェックポイントに保存された状態によって制御されます。ノード上の利用可能なスペースを考慮する際、スケジューラはSpec.Containers[i].ResourcesとStatus.ContainerStatuses[i].ResourcesAllocatedのいずれか大きい方の値を使用する必要があります。

サブリソース

アルファ版では、リソースの変更はポッド仕様の更新によって実装されます。ベータ版(またはアルファ版の次期バージョン)では、新しいサブリソース「/resize」が定義されます。このサブリソースは、デプロイメント、レプリカセット、ジョブ、ステートフルセットなど、PodTemplatesを使用する他のリソースにも最終的に適用できます。これにより、ユーザーはVPAなどのコントローラーにRBACアクセスを許可できますが、ポッド仕様への完全な書き込みアクセスは許可されません。このサブリソースの具体的なAPIはまだ決定されていません。

コンテナチューニング戦略

きめ細かなユーザー制御を提供するために、PodSpec.Containers は ResizePolicy を拡張します。これは、CPU とメモリを名前としてサポートする名前付きサブオブジェクト(新しいオブジェクト)のリストです。以下のポリシー値をサポートします。

  • RestartNotRequired - デフォルト値。可能な場合は、コンテナーを再起動せずにサイズを変更しようとします。
  • 再起動 - 新しいリソース値を適用するには、コンテナを再起動する必要があります(例えば、JavaプロセスでXmxフラグを変更する必要がある場合など)。ResizePolicyを使用すると、ユーザーはコンテナをインプレースリソース更新に対して安全(または安全でない)とマークできます。Kubeletはこれに基づいて必要なアクションを決定します。

注意: RestartNotRequired はコンテナが再起動しないことを保証するものではありません。再起動しない場合、新しいリソースを適用できず、ランタイムがコンテナを停止する可能性があります。

CPU とメモリを個別に制御するためのフラグを設定するのは、CPU は通常大きな問題なく追加/削除できるが、使用可能なメモリの変更には再起動が必要になる可能性が高いという観察に基づいています。

異なるポリシーを持つ複数のリソースタイプが同時に更新された場合、再起動ポリシーは「RestartNotRequired」ポリシーよりも優先されます。ポッドの「RestartPolicy」が「Never」の場合、検証に合格するには「ResizePolicy」フィールドを「RestartNotRequired」に設定する必要があります。つまり、システムがインプレースでサイズ変更を実行できない場合、インプレースでサイズ変更を行うとコンテナが再起動せずに停止する可能性があります。

ステータスの調整

上記に加えて、新しいフィールド「Pod.Status.Resize[]」が追加されます。このフィールドは、kubeletが指定されたリソースに対するサイズ変更操作の提案を受け入れるか拒否するかを示します。この新しいフィールドは、「Pod.Spec.Containers[i].Resources.Requests」フィールドと「Pod.Status.ContainerStatuses[i].Resources」フィールドの違いを説明します。

このフィールドは、次のいずれかの値に設定できます。

  • 提案済み - 提案されたサイズ変更 (Spec...Resources 内) はまだ承認または拒否されていません。
  • 進行中 - 提案されたサイズ変更が承認され、実装されています。
  • 延期 - 提案されたサイズ変更は理論的には実行可能 (このノードに適合) ですが、現在は実行可能ではありません。再評価されます。
  • 実行不可能 - 提案されたサイズ変更は実行不可能であるため拒否され、再評価されません。
  • 値なし - サイズ変更の提案はありません。

APIサーバーがサイズ変更の提案(Spec...Resourcesフィールドへの変更)を監視するたびに、このフィールドは自動的にProposedに設定されます。このフィールドが将来も安全であることを保証するため、コンシューマーは不明な値はDeferredと同じであると想定する必要があります。

CRIの変更

KubeletはUpdateContainerResources CRI APIを呼び出します。このAPIは現在、`runtimeapi.LinuxContainerResources`パラメータを使用しており、DockerとKataでは動作しますが、Windowsでは動作しません。このパラメータを、ランタイムに依存せず、プラットフォーム固有の情報を含む`runtimeapi.ContainerResources`に変更すると、UpdateContainerResources APIはWindowsでも動作するようになり、将来的にはLinux以外のランタイムでも動作するようになります。これは、APIに渡されるリソースパラメータが対象ランタイムに固有になるためです。

さらに、ContainerStatus CRI API が拡張され、runtimeapi.ContainerResources データを保持できるようになりました。これにより、Kubelet はランタイムからコンテナの CPU およびメモリ制限設定を照会できるようになりました。これにより、ランタイムはコンテナに現在適用されている CPU およびメモリリソース値を応答する必要があります。

CRI へのこれらの変更は別の作業であり、この KEP で提示された設計には影響しません。

上記の CRI の変更を完了するには:

  • 以下では、LinuxContainerResources と WindowsContainerResources をカプセル化する ContainerResources と呼ばれる新しい protobuf メッセージ オブジェクトを紹介します。
  • このメッセージは、メッセージ ContainerResources に新しいランタイム固有のリソース構造を追加するだけで、将来のランタイム向けに簡単に拡張できます。
 // ContainerResource はコンテナのリソース構成を格納します。
メッセージContainerResources {
// Linuxコンテナ固有のリソース構成
Linuxコンテナリソースlinux = 1 ;
// Windows コンテナ固有のリソース構成
Windowsコンテナリソースwindows = 2 ;
}
  • 以下に示すように、メッセージ UpdateContainerResourcesRequest が ContainerResources フィールドを含むように拡張されました。
  • これにより、現在の LinuxContainerResources に依存するランタイムが引き続き動作できるようになり、同時に新しいランタイム バージョンが UpdateContainerResourcesRequest.Resources.Linux を使用できるようになるため、下位互換性が維持されます。
  • これにより、UpdateContainerResourcesRequest.Linux フィールドの廃止が有効になります。
  • Linux ランタイムの場合、Kubelet は UpdateContainerResourcesRequest.Resources.Linux フィールドに加えて UpdateContainerResourcesRequest.Linux フィールドにもデータを入力します。
メッセージUpdateContainerResourcesRequest {
// 更新するコンテナの ID。
文字列container_id = 1 ;
// Linux コンテナ固有のリソース構成。
Linuxコンテナリソースlinux = 2 ;
// コンテナのリソース構成。
コンテナリソースリソース= 3 ;
}
  • 以下に示すように、メッセージ ContainerStatus が ContainerResources を返すように拡張されました。
  • これにより、Kubelet は ContainerStatus CRI API を使用してランタイムをクエリし、現在コンテナに適用されているリソースを検出できるようになります。
 @ @ - 914 , 6 + 912 , 8 @ @メッセージContainerStatus {
繰り返しマウントマウント= 14 ;
// コンテナのログ パス。
文字列log_path = 15 ;
+ // コンテナのリソース構成。
+ ContainerResourcesリソース= 16 ;
}
  • ContainerManager CRI API サービス インターフェイスが次のように変更されました。
  • UpdateContainerResources は、LinuxContainerResources の代わりに ContainerResources パラメータを使用します。
 --- a /ステージング/ src / k8s.io / cri - api / pkg / apis / services.go
+++ b /ステージング/ src / k8s.io / cri - api / pkg / apis / services.go
@ @ - 43 , 8 + 43 , 10 @ @ContainerManagerインターフェース{
ListContainers (フィルター*ランタイムAPI .コンテナフィルター) ([] *ランタイムAPI .コンテナ,エラー)
// ContainerStatus はコンテナのステータスを返します。
ContainerStatus (コンテナID文字) ( * runtimeapi.ContainerStatus ,エラー)
- // UpdateContainerResources はコンテナの cgroup リソースを更新します。
- UpdateContainerResources (コンテナID文字列リソース*ランタイムAPI . LinuxContainerResources )エラー
+ // UpdateContainerResources はコンテナのリソース構成を更新します。
+ UpdateContainerResources ( containerID string , resources * runtimeapi . ContainerResources )エラー
// ExecSync はコンテナ内でコマンドを実行し、stdout 出力を返します。
// コマンドがゼロ以外の終了コードで終了した場合、エラーが返されます。
ExecSync ( containerID文字列, cmd []文字列,タイムアウト時間. Duration ) ( stdout []バイト, stderr []バイト, errエラー)
  • 上記の CRI の変更に適応するように Kubelet コードを変更します。

リスク

  1. 後方互換性:Pod.Spec.Containers[i].Resourcesが期待される状態を表す場合、Podの実際のリソース割り当てはPod.Status.ContainerStatuses[i].ResourcesAllocatedで追跡されます。PodSpecをクエリし、PodSpec内のResourcesに基づいてリソース割り当てを決定するアプリケーションでは、実際の割り当てを反映しない値が表示される可能性があります。この変更は、リリースノートおよびKubernetesのトップレベルのドキュメントに記載し、強調する必要があります。
  2. メモリサイズの削減:cgroup のメモリ制限を下げても、ページが使用中の可能性があるため、効果がない可能性があります。現在の使用量に近い制限を設定するなどの対策が必要になる場合があります。この問題については、さらなる調査が必要です。
  3. 以前のクライアントバージョン: 以前のバージョンのクライアントは、新しい ResourcesAllocated フィールドと ResizePolicy フィールドを認識できず、これらのフィールドを nil に設定していました。互換性を維持するため、PodResourceAllocation アドミッションコントローラは、この更新プロセスを変更し、ゼロ以外の値を持つ古い Pod を現在の Pod にコピーするようになりました。

デザインの詳細

Kubelet と APIServer の相互作用

新しいポッドが作成されると、スケジューラはポッドを格納する適切なノードを選択する責任を負います。

新しく作成されたPodの場合、APIサーバーは各コンテナのResources.Requestsと一致するようにResourcesAllocatedフィールドを設定します。KubeletがPodを受け入れる際、ResourcesAllocatedの値を使用して、Podを受け入れるのに十分な空き容量があるかどうかを判断します。KubeletはPodを受け入れる際にResourcesAllocatedを設定しません。

Podがサイズ変更を要求すると、KubeletはPodとそのコンテナに割り当てられたリソースを更新しようとします。Kubeletはまず、サイズ変更対象のPodを除くノード上のすべてのPodに割り当てられたリソースの合計(Pod.Spec.Containers[i].ResourcesAllocated)を計算し、新たに必要なリソースがノードの割り当て可能なリソースに適しているかどうかを確認します。サイズ変更対象のPodについては、新たに必要なリソース(Spec.Containers[i].Resources.Requests)を合計に追加します。

  • 必要な新しいリソースが適切である場合、KubeletはStatus...ResourcesAllocatedフィールドを更新し、Status.ResizeをInProgressに設定することでサイズ変更を受け入れます。次に、UpdateContainerResources CRI APIを呼び出してコンテナのリソース制限を更新します。すべてのコンテナの更新に成功した後、Status...Resourcesを更新して新しいリソース値を反映させ、Status.Resizeの設定を解除します。
  • 新しく必要なリソースが適切でない場合、Kubelet は Status.Resize フィールドを Infeasible に更新し、サイズ変更操作を実行しません。
  • 新しく必要なリソースが適切であるが現在使用中の場合、Kubelet は Status.Resize フィールドを Deferred に更新します。

上記に加えて、サイズ変更が承認または拒否されるたびに、kubelet は Pod でイベントを生成し、可能であればサイズ変更プロセスの主要なステップをログに記録して、進行中であることをユーザーに知らせます。

複数の Pod のサイズを変更する必要がある場合、それらは Kubelet で定義された順序 (たとえば、表示される順序) で順番に処理されます。

スケジューラは、キャッシュされたPodを使用してノードの割り当て可能値を計算するため、新しいPodをノードに並列に割り当てることがあります。このような競合が発生し、Podのサイズ変更後にノードに空き容量がなくなった場合、Kubeletは新しいPodを拒否することでこの問題を解決します。

注: ポッドが拒否された後、スケジューラは、拒否した同じノード上で代替ポッドの再スケジュールを試みることができます。

Kubelet 再起動フォールトトレランス

Podのサイズ変更中にKubeletが再起動した場合、すべてのPodは再起動時に現在のStatus...ResourcesAllocated値で承認され、既存のPodがすべて追加された後にサイズ変更が処理されます。これにより、サイズ変更が既に承認済みの既存のPodに影響を与えることはありません。

スケジューラとAPIサーバー間の相互作用

スケジューラは、ポッドのSpec.Containers[i].Resources.Requestsを使用して新しいポッドのスケジュールを設定し、ポッドの更新を監視してキャッシュを更新し続けます。ポッドに割り当てられるノードリソースを計算するには、Status.Resizeに記述されている保留中の調整を考慮する必要があります。

Status.Resize = "InProgress" または "Infeasible" のコンテナーの場合は、Status.ContainerStatus[i].ResourcesAllocated と併用するだけです。

Status.Resize = "Proposed" のコンテナの場合、悲観的な設定となり、サイズ変更が即座に承認されると想定されます。そのため、Pod の Spec...Resources.Requests と Status...ResourcesAllocated の値のうち、大きい方の値を使用する必要があります。

プロセス制御

次の手順は、Pod のすべてのコンテナの ResizePolicy が RestartNotRequired に設定され、さまざまなエッジ ケースのデモンストレーションが可能になっている、Pod の一連のインプレース サイズ変更操作を示しています。

 T = 0 :新しいポッドが作成されます。
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1
-状態は設定されていません。

T = 1 : apiserver のデフォルト値を適用します
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1
- `status . resize [ cpu ] ` =設定解除

T = 2 : kubelet はポッドを実行し API を更新します。
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1
- `status . resize [ cpu ] ` =設定解除
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1

T = 3 :サイズ変更#1 : cpu = 1.5 ( PUT PATCH 、または/ resize調整)
- apiserver はリクエストを検証し (例:制限がリクエスト数より少なくなく ResourceQuota を超えていない)、操作を受け入れます。
- apiserver は`resize [ cpu ] `"Proposed"設定します
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1.5
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1
- `status . resize [ cpu ] ` = "提案"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1

T = 4 : Kubelet監視ポッドがサイズ変更#1を検出し、それを受け入れました。
- kubeletはパッチ操作を送信します{
`resourceVersion` = ` <以前の> ` #競合検出を有効にする
`status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.5
`status . resize [ cpu ] ` = "進行中"
}
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1.5
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.5
- `status . resize [ cpu ] ` = "進行中"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1

T = 5 :サイズ変更#2 : cpu = 2 #この時点で、別のサイズ変更操作が実行されます。
- API サーバーはリクエストを検証し、操作を受け入れます。
- apiserver は`resize [ cpu ] `"Proposed"設定します
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 2
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.5
- `status . resize [ cpu ] ` = "提案"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1

T = 6 :コンテナランタイムCPU使用率= 1.5
- kubeletはパッチ操作を送信します{
`resourceVersion` = ` <以前の> ` #競合検出を有効にする
`status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.5
`status . resize [ cpu ] ` =設定解除
}
- apiserver 操作が「競合」エラーで失敗しました。

T = 7 : kubelet が更新され、サイズ調整#2が表示されます ( cpu = 2 )
- kubelet はこれが実現可能だと考えていますが、現時点では不可能です。
- kubeletはパッチを送信します{
`resourceVersion ` = ` <更新された> ` #競合検出を有効にする
`status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.5
`status.resize [ cpu ] = "Deferred" #延期としてマーク
}
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 2
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.5
- `status . resize [ cpu ] ` = "延期"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1.5

T = 8 :サイズ変更#3 : CPU = 1.6
- apiserver はリクエストを検証して受け入れます。
- apiserver は`resize [ cpu ] `"Proposed"設定します
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1.6
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.5
- `status . resize [ cpu ] ` = "提案"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1.5

T = 9 : Kubelet はポッド3 番目サイズ変更操作#3観察し、それを受け入れます。
- kubeletはパッチを送信します{
`resourceVersion ` = ` <以前の> ` #競合検出を有効にする
`status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.6
`status.resize [ cpu ] = "InProgress" #プロセスを進行中としてマークします。
}
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1.6
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.6
- `status . resize [ cpu ] ` = "進行中"
- `status.containerStatuses [ 0 ] .resources.requests [ cpu ] ` = 1.5

T = 10 :コンテナランタイムアプリケーションCPU = 1.6
- kubeletはパッチを送信します{
`resourceVersion ` = ` <以前の> ` #競合検出を有効にする
`status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.6
`status . resize [ cpu ] ` =設定解除
}
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 1.6
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.6
- `status . resize [ cpu ] ` =設定解除
- `status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.6

T = 11 :サイズ変更#4 : CPU = 100
- apiserverはリクエストを検証し、操作を受け入れます
- apiserver は`resize [ cpu ] `"Proposed"設定します
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 100
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.6
- `status . resize [ cpu ] ` = "提案"
- `status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.6

T = 12 : Kubelet は、ポッドが 4 回目のサイズ変更調整#4 を実行していることを観察しました。
このノードには100 個のCPUがないためkubelet はこれを受け入れません。
- kubeletはパッチを送信します{
`resourceVersion ` = ` <以前の> ` #競合検出を有効にする
`status.resize [ cpu ] = "Infeasible" #実行不可能としてマーク
}
- `spec.containers [ 0 ] .resources.requests [ cpu ] ` = 100
- `status.containerStatuses [ 0 ] .resourcesAllocated [ cpu ] ` = 1.6
- `status . resize [ cpu ] ` = "実行不可能"
- `status.containerStatuses [ 0 ] .resources.requests [cpu ] ` = 1.6

コンテナリソース制限の更新順序

Pod 内の複数のコンテナがインプレース サイズ変更を要求すると、Kubelet は次のように Pod とそのコンテナのリソース制限を更新します。

  1. リソース調整によってリソース タイプ (CPU またはメモリ) が純増した場合、Kubelet はまずそのリソース タイプの Pod レベルの cgroup 制限を更新し、次にコンテナ リソース制限を更新します。
  2. リソース調整によってリソースの種類が全体的に減少した場合、Kubelet はまずコンテナのリソース制限を更新し、次にポッドレベルの cgroup 制限を更新します。
  3. リソースの更新によってリソース タイプに実質的な変更がない場合は、コンテナー リソースの制限のみが更新されます。

上記のすべてのケースにおいて、Kubelet はアプリケーションの制限が増加する前に、アプリケーション コンテナのリソース制限を削減します。

コンテナのリソース制限更新の失敗の処理

ポッド内の複数のコンテナが更新され、これらのコンテナのいずれかのUpdateContainerResources CRI APIが失敗した場合、Kubeletはロールバックし、後で再試行します。Kubeletは、失敗したコンテナの後に更新キューに入っているコンテナの制限を更新しようとしません。これにより、コンテナ制限の合計がポッドレベルのcgroup制限を超えることはありません。すべてのコンテナ制限の更新に成功した後、KubeletはポッドのStatus.ContainerStatuses[i].Resourcesを更新し、目的の制限値と一致させます。

CRI変更プロセス

以下の図は、ユーザーが Pod Spec で必要なリソースを変更したときに、Kubelet が UpdateContainerResources および ContainerStatus CRI API を使用して新しいコンテナ リソース制限を設定し、Pod ステータスを更新する方法の概要を示しています。

 +-----------+ +-----------+ +-----------+
| | | | | |
|アピサーバー | |キュベレット | |ランタイム |
| | | | | |
+-----+-----+ +-----+-----+ +-----+-----+
| | |
| ウォッチ (ポッド更新) | |
|------------------------------>| |
| [コンテナー.リソース] | |
| | |
| (認める) |
| | |
| | コンテナリソースを更新します() |
| |---------------------------->|
| | (制限を設定する)
| |<- - - - - - - - - - - - - - - - -|
| | |
| | コンテナステータス() |
| |---------------------------->|
| | |
| | [コンテナリソース] |
| |<- - - - - - - - - - - - - - - - -|
| | |
| 更新 (ポッドステータス) | |
|<------------------------------| |
| [コンテナーステータス.リソース] | |
| | |
  • Kubeletは、ContainerManagerインターフェース内のUpdateContainerResources() CRI APIを呼び出し、APIのContainerResourcesパラメータに値を指定することにより、コンテナの新しいCPUおよびメモリ制限を設定します。このCRI APIを呼び出す際、Kubeletはターゲットランタイムプラットフォームに基づいてContainerResourcesパラメータを設定します。
  • Kubeletは、ContainerManagerインターフェースのContainerStatus() CRI APIを呼び出し、コンテナに適用されているCPUおよびメモリ制限を取得します。ContainerStatus.Resourcesで返された値を使用して、Podステータス内のそのコンテナのContainerStatuses[i].Resources.Limitsを更新します。

予防

  • ノードのCPUマネージャーポリシーが「static」に設定されている場合、CPUサイズ変更に使用できるのは整数値のみです。CPUマネージャーポリシーが「static」に設定されているノードで整数以外のCPUサイズ変更が要求された場合、サイズ変更は拒否され、イベントにエラーメッセージが記録されます。
  • 競合を避けるために、Pod が使用するリソースを計算するときに、すべてのコンポーネントは Pod の Status.ContainerStatuses[i].ResourcesAllocated を使用します。
  • Podのサイズ変更中に追加のサイズ変更リクエストが到着した場合、これらのリクエストは進行中のサイズ変更が完了した後に処理されます。サイズ変更は最新の望ましい状態に基づいて実行されます。
  • アプリケーションが常にページを占有している場合、メモリ制限を下げてもすぐに効果が現れない場合があります。Kubeletは制御ループを使用してメモリ制限を使用済みに近い値に設定し、強制的にメモリ回収を行います。そして、制限が想定値に達した場合にのみ、PodのStatus.ContainerStatuses[i].Resourcesを更新します。
  • Pod オーバーヘッドの影響: Kubelet は、サイズ変更リクエストに Pod オーバーヘッドを追加して、サイズ変更をその場で実行できるかどうかを判断します。

成し遂げる

実は、KEP ドラフトが最初に作成されたのは 2018 年で、ほぼ 5 年前です。

このKEPに対応するPRである#In-place Pod Vertical Scaling feature #102884 (https://github.com/kubernetes/kubernetes/pull/102884) は、約2年前の2021年に初めてリリースされました。このPRにおける大幅な変更点を見れば、Kubernetesコミュニティの真摯な姿勢が伺えます。幸いなことに、このPRは昨日マージされました。これは大きな成果であり、160件を超えるファイルの変更が含まれていますが、その大部分はテスト目的です。

この新しい機能の使い方を見てみましょう。

次のようなテスト Pod ができました。

 # 2ポッド.yaml
apiバージョン: v1
種類:ポッド
メタデータ
名前2ポッド
仕様:
コンテナ
-名前ストレス
画像: skiibum / ubuntu -ストレス: 18.10
リソース
制限
CPU「500m」
メモリ「500Mi」
リクエスト:
CPU「500m」
メモリ「500Mi」

Pod を作成するだけです:

 $ kubectl apply -f 2ポッド.yaml

作成後、このリソース リストの詳細データを表示してみましょう。

 $ kubectl get po 2 pod -oyaml
apiバージョン: v1
種類:ポッド
メタデータ
名前2ポッド
名前空間:デフォルト
仕様:
コンテナ
-画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
サイズ変更ポリシー:
-ポリシー: RestartNotRequired #デフォルト: 可能であれば、コンテナーを再起動せずにサイズを変更しようとします。
リソース名: CPU
-ポリシー:再起動不要
リソース名:メモリ
リソース
制限
CPU : 500 m
メモリ: 500 Mi
リクエスト:
CPU : 500 m
メモリ: 500 Mi
...
...
状態
条件
...
コンテナステータス:
-コンテナID : docker : //015b2d8605c732329129a8d61894ef5438b5a8ed09da0b5e56dad82d3b57a789
画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
準備完了: true
リソース
制限
CPU : 500 m
メモリ: 500 Mi
リクエスト:
CPU : 500 m
メモリ: 500 Mi
割り当てられたリソース:
CPU : 500 m
メモリ: 500 Mi
再起動回数: 0
開始しました: true
...
qosClass :保証
startTime : "2021-06-27T02:06:56Z"

最大の変更点は、Podリソースマニフェストに「resizePolicy」属性が追加されたことです。自動設定されるCPUおよびメモリリソースのサイズ変更ポリシーは「RestartNotRequired」に設定されており、これはデフォルト値でもあります。つまり、可能な場合はコンテナのサイズを再起動せずに変更します。

さらに、ステータスの下に新しい「Automatic ResourcesAllocated」オプションが表示され、ポッドとそのコンテナに割り当てられたノード リソースが示されます。

次に、このポッドのコンテナ リソース サイズを変更します。

 $ kubectl patch pod 2 pod -- patch '{"spec":{"containers":[{"name":"stress", "resources":{"requests":{"cpu":"650m"}, "limits":{"cpu":"650m"}}}]}}'
ポッド/ 2ポッドパッチ

変更を加えた後、詳細な Pod リソース リストを再度確認します。

 $ kubectl get po 2 pod -oyaml
apiバージョン: v1
種類:ポッド
メタデータ
名前2ポッド
名前空間:デフォルト
仕様:
コンテナ
-画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
サイズ変更ポリシー:
-ポリシー:再起動不要
リソース名: CPU
-ポリシー:再起動不要
リソース名:メモリ
リソース
制限
CPU : 650 m
メモリ: 500 Mi
リクエスト:
CPU : 650 m
メモリ: 500 Mi
...
...
状態
条件
...
コンテナステータス:
-コンテナID : docker : //015b2d8605c732329129a8d61894ef5438b5a8ed09da0b5e56dad82d3b57a789
画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
準備完了: true
リソース
制限
CPU : 500 m
メモリ: 500 Mi
リクエスト:
CPU : 500 m
メモリ: 500 Mi
割り当てられたリソース:
CPU : 650 m
メモリ: 500 Mi
再起動回数: 0
開始しました: true
...
qosClass :保証
サイズ変更:進行中
startTime : "2021-06-27T02:06:56Z"

PodSpec のリソースサイズが変更されていることがわかります。重要なのは Status で、 ​resize: InProgress​という新しいフィールドが追加されていることです。これは、調整が承認され、実行中であることを示しています。

ここでリソース サイズを再度調整すると、次のようになります。

 $ kubectl patch pod 2 pod -- patch '{"spec":{"containers":[{"name":"stress", "resources":{"requests":{"cpu":"3950m"}, "limits":{"cpu":"3950m"}}}]}}'
ポッド/ 2ポッドパッチ

調整後、ポッドの詳細データを表示します。

 $ kubectl get po 2 pod -oyaml
apiバージョン: v1
種類:ポッド
メタデータ
名前2ポッド
名前空間:デフォルト
仕様:
コンテナ
-画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
サイズ変更ポリシー:
-ポリシー:再起動不要
リソース名: CPU
-ポリシー:再起動不要
リソース名:メモリ
リソース
制限
CPU : 3950 m
メモリ: 500 Mi
リクエスト:
CPU : 3950 m
メモリ: 500 Mi
...
...
状態
条件
...
コンテナステータス:
-コンテナID : docker : //015b2d8605c732329129a8d61894ef5438b5a8ed09da0b5e56dad82d3b57a789
画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
準備完了: true
リソース
制限
CPU : 500 m
メモリ: 500 Mi
リクエスト:
CPU : 500 m
メモリ: 500 Mi
割り当てられたリソース:
CPU : 650 m
メモリ: 500 Mi
再起動回数: 0
開始しました: true
...
qosClass :保証
サイズ変更:延期
startTime : "2021-06-27T02:06:56Z"

この時点で、Statusのサイズ変更がDeferredに変わります。これは、提案されたサイズ変更が理論的には実行可能(このノードに収まる)であるものの、まだ実行されていないことを示し、再評価されます。resourcesAllocatedは、最後に成功した変更後の​​サイズのままであることに注意してください。

ノードへのリソースの再調整が不十分な場合:

 $ kubectl patch pod 2 pod -- patch '{"spec":{"containers":[{"name":"stress", "resources":{"requests":{"cpu":"4650m"}, "limits":{"cpu":"4650m"}}}]}}'
ポッド/ 2ポッドパッチ
$ kubectl get po 2 pod -oyaml
apiバージョン: v1
種類:ポッド
メタデータ
名前2ポッド
名前空間:デフォルト
仕様:
コンテナ
-画像: skiibum / ubuntu -ストレス: 18.10
名前ストレス
サイズ変更ポリシー:
-ポリシー:再起動不要
リソース名: CPU
-ポリシー:再起動不要
リソース名:メモリ
リソース
制限
CPU : 4650 m
メモリ: 500 Mi
リクエスト:
CPU : 4650 m
メモリ: 500 Mi
...
...
状態
条件
...
コンテナステータス:
-コンテナID : docker : //015b2d8605c732329129a8d61894ef5438b5a8ed09da0b5e56dad82d3b57a789
画像: skiibum / ubuntu -ストレス: 18.10
...
名前ストレス
準備完了: true
リソース
制限
CPU : 500 m
メモリ: 500 Mi
リクエスト:
CPU : 500 m
メモリ: 500 Mi
割り当てられたリソース:
CPU : 650 m
memory : 500 Mi
restartCount : 0
...
qosClass : Guaranteed
resize : Infeasible
startTime : "2021-06-27T02:06:56Z"

现在resize 状态变成了 Infeasible,表示不能满足资源条件,拒绝调整。

以后当我们的Pod 资源不足的时候,可以放心大胆去调整资源了,不用担心容器被重启了。

参考リンク

  • https://github.com/kubernetes/kubernetes/pull/102884。
  • https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources。