Kubernetesは、ノード上でスワップメモリを使用するように構成でき、カーネルがページをバッキングストレージにスワップアウトすることで物理メモリを解放できるようにします。 これは複数のユースケースで有用です。 たとえば、大きなメモリフットプリントを持つが、特定の時点ではそのメモリの一部しかアクセスしないようなワークロードのように、スワップを使用することで恩恵を受けられるワークロードを実行するノードがあります。 また、メモリ圧迫時にPodが終了されるのを防いだり、システムの安定性を損なう可能性のあるシステムレベルのメモリ急増からノードを保護したり、ノード上でより柔軟なメモリ管理を可能にしたりするなど、さまざまな点で役立ちます。
クラスターでスワップを構成する方法については、Kubernetesノードでのスワップメモリの構成を参照してください。
ノード上でのスワップの使用方法については、いくつかの方法が考えられます。 kubeletがすでにノード上で実行されている場合、スワップがプロビジョニングされた後にkubeletを再起動して認識させる必要があります。
スワップがプロビジョニングされて利用可能なノードでkubeletが起動する場合(failSwapOn: falseの設定を使用)、kubeletは以下を行います:
ノード上のスワップ構成は、KubeletConfigurationのmemorySwapを介してクラスター管理者に公開されます。
クラスター管理者として、memorySwap.swapBehaviorを設定することで、スワップメモリが存在する場合のノードの動作を指定できます。
使用するスワップの動作を選択する必要があります。 クラスター内のノードごとに異なるスワップの動作を設定することができます。
Linuxノードで選択できるスワップの動作は以下の通りです:
NoSwap(デフォルト)LimitedSwapNoSwapの動作を選択し、kubeletがスワップスペースを許容するように構成した場合(failSwapOn: false)、ワークロードはスワップを使用しません。
ただし、Kubernetesが管理するコンテナ外のプロセス(systemdサービスやkubelet自体までも!)はスワップを利用できます。
クラスターでスワップを有効にする方法については、Kubernetesノードでのスワップメモリの構成を参照してください。
kubeletはコンテナランタイムAPIを使用し、コンテナランタイムに対して特定の構成(たとえばcgroup v2の場合はmemory.swap.max)を適用するように指示します。
これにより、コンテナに対して目的のスワップ構成が有効になります。
コントロールグループ(cgroups)を使用するランタイムの場合、コンテナランタイムがこれらの設定をコンテナレベルのcgroupに書き込む責任を負います。
kubeletはノードおよびコンテナレベルのメトリック統計を収集するようになりました。
これらは、kubeletのHTTPエンドポイントである/metrics/resource(主にPrometheusなどの監視ツールによって使用される)および/stats/summary(主にAutoscalerによって使用される)からアクセスできます。
これにより、kubeletに直接リクエストできるクライアントが、LimitedSwapを使用する際のスワップ使用量と残りのスワップメモリを監視できます。
さらに、マシンの合計物理スワップ容量を示すmachine_swap_bytesメトリックがcadvisorに追加されました。
詳細についてはこちらのページを参照してください。
たとえば、以下の/metrics/resourceがサポートされています:
node_swap_usage_bytes: ノードの現在のスワップ使用量(バイト単位)。container_swap_usage_bytes: コンテナの現在のスワップ使用量(バイト単位)。container_swap_limit_bytes: コンテナの現在のスワップ制限(バイト単位)。kubectl top --show-swapの使用メトリックのクエリは有用ですが、これらのメトリックは人間ではなくソフトウェアが使用するように設計されているため、少し面倒です。
このデータをよりユーザーフレンドリーな方法で利用するために、kubectl topコマンドが--show-swapフラグを使用してスワップメトリックをサポートするように拡張されました。
ノードのスワップ使用量に関する情報を取得するには、kubectl top nodes --show-swapを使用できます:
kubectl top nodes --show-swap
出力は次のようになります:
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) SWAP(bytes) SWAP(%)
node1 1m 10% 2Mi 10% 1Mi 0%
node2 5m 10% 6Mi 10% 2Mi 0%
node3 3m 10% 4Mi 10% <unknown> <unknown>
Podのスワップ使用量に関する情報を取得するには、kubectl top pods --show-swapを使用できます:
kubectl top pod -n kube-system --show-swap
出力は次のようになります:
NAME CPU(cores) MEMORY(bytes) SWAP(bytes)
coredns-58d5bc5cdb-5nbk4 2m 19Mi 0Mi
coredns-58d5bc5cdb-jsh26 3m 37Mi 0Mi
etcd-node01 51m 143Mi 5Mi
kube-apiserver-node01 98m 824Mi 16Mi
kube-controller-manager-node01 20m 135Mi 9Mi
kube-proxy-ffgs2 1m 24Mi 0Mi
kube-proxy-fhvwx 1m 39Mi 0Mi
kube-scheduler-node01 13m 69Mi 0Mi
metrics-server-8598789fdb-d2kcj 5m 26Mi 0Mi
新しいノードステータスフィールドnode.status.nodeInfo.swap.capacityが追加され、ノードのスワップ容量を報告するようになりました。
たとえば、クラスター内のノードのスワップ容量を取得するには、以下のコマンドを使用できます:
kubectl get nodes -o go-template='{{range .items}}{{.metadata.name}}: {{if .status.nodeInfo.swap.capacity}}{{.status.nodeInfo.swap.capacity}}{{else}}<unknown>{{end}}{{"\n"}}{{end}}'
出力は次のようになります:
node1: 21474836480
node2: 42949664768
node3: <unknown>
<unknown>の値は、そのノードの.status.nodeInfo.swap.capacityフィールドが設定されていないことを示します。
これはおそらく、ノードにスワップがプロビジョニングされていないか、または、より可能性は低いですが、kubeletがノードのスワップ容量を判定できないことを意味します。Node Feature Discoveryは、ハードウェアの機能と構成を検出するためのKubernetesアドオンです。 これを利用して、どのノードにスワップがプロビジョニングされているかを検出できます。
たとえば、どのノードにスワップがプロビジョニングされているかを確認するには、以下のコマンドを使用します:
kubectl get nodes -o jsonpath='{range .items[?(@.metadata.labels.feature\.node\.kubernetes\.io/memory-swap)]}{.metadata.name}{"\t"}{.metadata.labels.feature\.node\.kubernetes\.io/memory-swap}{"\n"}{end}'
出力は次のようになります:
k8s-worker1: true
k8s-worker2: true
k8s-worker3: false
この例では、ノードk8s-worker1とk8s-worker2にはスワップがプロビジョニングされていますが、k8s-worker3にはプロビジョニングされていません。
システム上でスワップが利用可能な場合、予測可能性が低下します。 スワップはより多くのRAMを利用可能にすることでパフォーマンスを向上させることができますが、データをメモリにスワップインする操作は重い処理であり、時には桁違いに遅くなることがあり、予期しないパフォーマンスの低下を引き起こす可能性があります。 さらに、スワップはメモリ圧迫時のシステムの動作を変化させます。 スワップを有効にすると、RAMを頻繁に使用するPodが他のPodのスワップを引き起こす可能性があるため、ノイジーネイバーのリスクが高まります。 さらに、スワップによりKubernetesのワークロードのメモリ使用量が予測不能に増大し、予期しないパッキング構成のために、スケジューラーは現在スワップメモリの使用量を考慮していません。 これにより、ノイジーネイバーのリスクが高まります。
スワップメモリが有効なノードのパフォーマンスは、基盤となる物理ストレージに依存します。 スワップメモリが使用されている場合、I/O制限のあるクラウドVMなどのIOPS制約のある環境では、SSDやNVMeなどの高速なストレージメディアと比較して、パフォーマンスが大幅に低下します。 スワップはIO圧迫を引き起こす可能性があるため、システムクリティカルなデーモンに対してIOレイテンシーの優先度を高く設定することが推奨されます。 以下の推奨プラクティスセクションの該当箇所を参照してください。
Linuxノードでは、メモリバックボリューム(secretボリュームマウントやmedium: Memoryを使用したemptyDirなど)はtmpfsファイルシステムで実装されています。
このようなボリュームの内容は常にメモリに保持されるべきであり、ディスクにスワップされるべきではありません。
このようなボリュームの内容がメモリに保持されることを保証するために、noswap tmpfsオプションが使用されています。
Linuxカーネルはバージョン6.3からnoswapオプションを公式にサポートしています(詳細はLinuxカーネルバージョンの要件を参照してください)。
ただし、ディストリビューションによっては、このマウントオプションを古いLinuxバージョンにバックポートすることがよくあります。
ノードがnoswapオプションをサポートしているかどうかを確認するために、kubeletは以下を行います:
noswapオプションがサポートされていると見なされます。noswapオプションを使用してダミーのtmpfsをマウントしようとします。
kubeletが不明なオプションを示すエラーで失敗した場合、noswapはサポートされていないと見なされ、使用されません。
メモリバックボリュームがディスクにスワップされる可能性があることをユーザーに警告するkubeletのログエントリが出力されます。
kubeletが成功した場合、ダミーのtmpfsは削除され、noswapオプションが使用されます。noswapオプションがサポートされていない場合、kubeletは警告ログエントリを出力し、実行を続行します。暗号化されていないスワップの設定例については、上記のセクションを参照してください。 ただし、暗号化されたスワップの処理はkubeletの範囲外であり、一般的なOSの構成の問題として対処されるべきです。 このリスクを軽減するために暗号化されたスワップをプロビジョニングするのは管理者の責任です。
スワップが有効なノードに対するメモリエビクションのしきい値の構成は複雑です。
スワップが無効な場合、kubeletのエビクションしきい値をノードのメモリ容量より少し低く設定することは合理的です。 その理由は、ノードがメモリ不足になりOut Of Memory(OOM)キラーが呼び出される前にKubernetesがPodのエビクションを開始したいからです。 OOMキラーはKubernetesを認識しないため、QoS、Podの優先度、またはその他のKubernetes固有の要素を考慮しません。
スワップが有効な場合、状況はより複雑です。
Linuxでは、vm.min_free_kbytesパラメーターがカーネルがメモリの積極的な回収(ページのスワップアウトを含む)を開始するメモリしきい値を定義します。
kubeletのエビクションしきい値が、カーネルがメモリ回収を開始する前にエビクションが行われるように設定されている場合、ノードのメモリ圧迫時にワークロードがスワップアウトできなくなる可能性があります。
一方、エビクションしきい値を高く設定しすぎると、ノードがメモリ不足になりOOMキラーが呼び出される可能性があり、これも理想的ではありません。
これに対処するため、kubeletのエビクションしきい値をvm.min_free_kbytesの値よりわずかに低く設定することが推奨されます。
この方法により、kubeletがPodのエビクションを開始する前にノードがスワップを開始でき、ワークロードが未使用データをスワップアウトしてエビクションを防止できます。
一方、わずかに低いだけなので、ノードがメモリ不足になる前にkubeletがPodのエビクションを開始し、OOMキラーを回避できます。
vm.min_free_kbytesの値は、ノード上で以下のコマンドを実行することで確認できます:
cat /proc/sys/vm/min_free_kbytes
LimitedSwapの動作では、Podが利用できるスワップの量は、ノードの合計メモリに対するメモリリクエストの割合に基づいて自動的に決定されます(詳細については、以下のセクションを参照してください)。
この設計により、通常、Kubernetesワークロードに対して制限されたままとなるスワップの一部が存在します。 たとえば、Kubernetes 1.36はGuaranteed QoSクラスのPodに対してスワップの使用を許可していないため、Guaranteed Podのメモリリクエストに比例するスワップの量は、Kubernetesワークロードによって使用されずに残ります。
この動作は、多くのPodがスワップの対象外である場合にリスクを伴います。 一方で、これはKubernetesの管理範囲外のプロセス(システムデーモンやkubelet自体など)が使用できる、システム予約量のスワップメモリを効果的に維持します。
テストフェーズやユーザーフィードバックに基づいて、システムクリティカルなデーモンやサービスのパフォーマンスが低下する可能性があることが観察されました。
これは、kubeletを含むシステムデーモンが通常よりも遅く動作する可能性があることを意味します。
この問題が発生した場合、スワップを防止するためにシステムスライスのcgroupを構成すること(つまりmemory.swap.max=0を設定すること)が推奨されます。
スワップはノードのI/O負荷を増加させる可能性があります。 メモリ圧迫によりカーネルが急速にページをスワップインおよびスワップアウトする場合、I/O操作に依存するシステムクリティカルなデーモンやサービスがパフォーマンスの低下を経験する可能性があります。
これを軽減するために、systemdユーザーにはシステムスライスのI/Oレイテンシーを優先することが推奨されます。
非systemdユーザーの場合、システムデーモンとプロセス用の専用cgroupをセットアップし、同様にI/Oレイテンシーを優先することが推奨されます。
これは、システムスライスにio.latencyを設定することで実現でき、より高いI/O優先度を付与します。
詳細についてはcgroupのドキュメントを参照してください。
Kubernetesプロジェクトは、スワップスペースを構成せずにコントロールプレーンノードを実行することを推奨しています。 コントロールプレーンは主にGuaranteed QoSのPodをホストするため、一般的にスワップを無効にできます。 主な懸念点は、コントロールプレーン上のクリティカルなサービスのスワップがパフォーマンスに悪影響を与える可能性があることです。
Kubernetesプロジェクトは、スワップが有効なノードを実行する場合は常に暗号化されたスワップを使用することを推奨しています。 スワップがパーティションまたはルートファイルシステム上にある場合、ワークロードがディスクへの書き込みを必要とするシステムプロセスに干渉する可能性があります。 同じディスクを共有している場合、プロセスがスワップを圧倒し、kubelet、コンテナランタイム、およびsystemdのI/Oを中断させ、他のワークロードに影響を与える可能性があります。 スワップスペースはディスク上に配置されるため、意図されたユースケースに対してディスクが十分に高速であることを確認することが重要です。 あるいは、単一のバッキングデバイスの異なるマップ領域間でI/O優先度を構成することもできます。
Kubernetes 1.36は、スワップメモリの使用量を考慮してノードにPodを割り当てることをサポートしていません。
スケジューラーは通常、Pod配置のガイドとしてインフラストラクチャリソースの リクエスト を使用しますが、Podはスワップスペースをリクエストせず、memoryのみをリクエストします。
つまり、スケジューラーはスケジューリングの決定においてスワップメモリを考慮しません。
これは現在積極的に取り組んでいるものですが、まだ実装されていません。
スワップメモリの使用を意図したPod以外がスワップメモリのあるノードにスケジュールされないようにするために、管理者はスワップが利用可能なノードにtaintを設定することで、この問題から保護できます。 taintにより、スワップを許容するワークロードが負荷時にスワップのないノードに溢れ出ることが防止されます。
スワップスペースに指定されるストレージデバイスは、高いメモリ使用量時のシステム応答性を維持するために重要です。 回転式ハードディスクドライブ(HDD)は、その機械的な性質により大きなレイテンシーが発生し、深刻なパフォーマンスの低下とシステムのスラッシングを引き起こすため、このタスクには適していません。 現代のパフォーマンス要件には、ソリッドステートドライブ(SSD)などのデバイスがスワップに適した選択肢です。 低レイテンシーの電子的アクセスにより、速度低下を最小限に抑えます。
スワップメモリの設定(制限を含む)は重大な課題を提示します。 誤設定が起こりやすいだけでなく、システムレベルのプロパティであるため、誤設定は特定のワークロードではなくノード全体を危険にさらす可能性があります。 このリスクを軽減し、ノードの健全性を確保するために、制限の自動設定を備えたスワップを実装しました。
LimitedSwapでは、Burstable QoS分類に属さないPod(つまりBestEffort/Guaranteed QoSのPod)はスワップメモリの利用が禁止されています。
BestEffort QoSのPodは予測不能なメモリ消費パターンを示し、メモリ使用量に関する情報が不足しているため、安全なスワップメモリの割り当てを決定することが困難です。
逆に、Guaranteed QoSのPodは通常、ワークロードによって指定されたリソースの正確な割り当てに依存するアプリケーションに使用され、メモリがすぐに利用可能であることが前提となります。
上記のセキュリティとノードの健全性の保証を維持するため、LimitedSwapが有効な場合、これらのPodはスワップメモリの使用が許可されません。
さらに、高優先度のPodは、消費するメモリが常にRAM上に常駐し、すぐに使用可能であることを保証するために、スワップの使用が許可されていません。
スワップ制限の計算を詳しく説明する前に、以下の用語を定義する必要があります:
nodeTotalMemory: ノードで利用可能な物理メモリの合計量。totalPodsSwapAvailable: Podが使用できるノード上のスワップメモリの合計量(一部のスワップメモリはシステム使用のために予約されている場合があります)。containerMemoryRequest: コンテナのメモリリクエスト。スワップ制限は次のように構成されます:
( containerMemoryRequest / nodeTotalMemory ) × totalPodsSwapAvailable
つまり、コンテナが使用できるスワップの量は、そのメモリリクエスト、ノードの合計物理メモリ、およびPodが使用できるノード上のスワップメモリの合計量に比例します。
Burstable QoSのPod内のコンテナの場合、メモリリクエストをメモリ制限と等しく指定することでスワップの使用をオプトアウトできることに注意する必要があります。 この方法で構成されたコンテナはスワップメモリにアクセスできません。