Kubernetes 和 cgroup v2
首先
Kubernetes は v1.25 で cgroup v2 サポートを GA しており、その後に cgroup v2 に関連する機能が追加されています。しかしまだ多くのディストリビューションで Kubernetes がデフォルトで cgroup v2 を使用しない設定のため、実際に利用している方は多くないと思います。PFN では2022年12月に Kubernetes バージョンを v1.25 にアップグレードするのと同じタイミングで cgroup v2 に切り替えています。
在这篇文章中,我们将介绍 Kubernetes 的 cgroup v2 功能,包括 MemoryQoS 功能和 memory.oom.group 功能。我们会分享这两个功能的概述和问题。请注意,这些信息是基于 Kubernetes v1.28 的。
关于cgroup v2本质上说
最初に、cgroup v2について詳しく説明しているKubernetesのブログエントリがありますが、簡単に概要を説明します。
- Kubernetes 1.25: cgroup v2 graduates to GA | Kubernetes
cgroup 是 Linux 内核的资源管理功能,可以限制正在运行的进程的 CPU 和内存使用等。Docker 从最初发布起就使用 cgroup 来限制容器的资源,并且在 Kubernetes 中使用容器的自然也同样如此。Kubernetes 使用 cgroup 来实现资源管理功能,例如 Pod 的资源需求和限制。
cgroup v2是cgroup API的最新版本,具有更强大的资源管理功能(尽管从2016年开始提供)。如果Kubernetes在使用的节点上只启用了cgroup v2,那么它将提供基于该增强资源管理功能的功能。
手元のノードで Kubernetes が cgroup v2 を使用してくれるかを知るには
下記コマンドを実行して確認できます。
$ stat -fc %T /sys/fs/cgroup/
cgroup2fs
tmpfs と出力された場合は cgroup v1 が有効です。v2 への切り替え方法は上記の Kubernetes blog エントリを参照ください。
MemoryQoS フィーチャゲート
1つ目が MemoryQoS フィーチャゲートです。この機能は名前のとおりメモリに対して QoS (Quolity-of-Service) を導入してより柔軟な制御を可能にします。このフィーチャゲートは v1.28 時点でアルファです。
これまで Pod のメモリリソース制御は limits.memory で使用できるメモリ量の上限を設定するのみで、上限を超えると OOM が発生するだけでした。この上限の制御には cgroup memory.max が使われています。Pod requests.memory はというと、主に Pod をスケジューリングするノードを決定するための情報として使用されていてリソース制御には使われていません。
在cgroup v2中,除了使用memory.max来限制内存外,还添加了memory.min和memory.high来进行限制。
memory.min: Pod のメモリ要求 (requests.memory) と同じ値が設定され、設定された値のメモリが保護される。システムがメモリ (page cache) を回収しようとした場合にも対象にならず、保護されておらず回収可能なメモリがなくなった場合に OOM Killer が呼び出される。
memory.high: Pod のメモリ制限 (limits.memory) の90%の値が設定され(デフォルト)、これに達するとシステムがメモリをできる限り回収しようとする。
- Quality-of-Service for Memory Resources | Kubernetes
以下的图表展示的是在这个功能有效的集群中,Pod 增加了 page cache 的使用量,直到达到 memory.high 的情况。可以看出它被限制在上限(Limits)的90%。

题目:如果请求的内存等于限制的内存
启用MemoryQoS功能后,可以期望请求的内存受到保护,从而使工作负载更稳定。然而,如果Pod的内存请求(requests.memory)和限制(limits.memory)被设置为相同的值,那么工作负载有可能经常遇到OOM错误。
このフィーチャゲートが有効な場合、先に説明したとおりメモリ要求と同じ値が cgroup memory.min に設定されることで要求したメモリが保護されるためシステムから回収されません。そのまま回収されずに制限 (cgroup memory.max) に達してしまうと OOM になってしまいそうですが、使用量が制限の90%に達すると cgroup v2 memory.high によりメモリが回収されるため OOM にならずに済みます。しかし、Kubernetes v1.28 時点でメモリ要求と制限に同じ値が設定されている場合に memory.high が設定されません。そのためメモリが回収されずにそのまま OOM になってしまいます。KEP に下記の記述があり仕様のようです。
If container sets limits.memory, we set memory.high=pod.spec.containers[i].resources.limits[memory] * memory throttling factor for container level cgroup if memory.high>memory.min
// Guaranteed pods by their QoS definition requires that memory request equals memory limit and cpu request must equal cpu limit.
// Here, we only check from memory perspective. Hence MemoryQoS feature is disabled on those QoS pods by not setting memory.high.
if memoryRequest != memoryLimit {
// The formula for memory.high for container cgroup is modified in Alpha stage of the feature in K8s v1.27.
// It will be set based on formula:
// `memory.high=floor[(requests.memory + memory throttling factor * (limits.memory or node allocatable memory - requests.memory))/pageSize] * pageSize`
// where default value of memory throttling factor is set to 0.9
// More info: https://git.k8s.io/enhancements/keps/sig-node/2570-memory-qos
memoryHigh := int64(0)
if memoryLimit != 0 {
memoryHigh = int64(math.Floor(
float64(memoryRequest)+
(float64(memoryLimit)-float64(memoryRequest))*float64(m.memoryThrottlingFactor))/float64(defaultPageSize)) * defaultPageSize
} else {
allocatable := m.getNodeAllocatable()
allocatableMemory, ok := allocatable[v1.ResourceMemory]
if ok && allocatableMemory.Value() > 0 {
memoryHigh = int64(math.Floor(
float64(memoryRequest)+
(float64(allocatableMemory.Value())-float64(memoryRequest))*float64(m.memoryThrottlingFactor))/float64(defaultPageSize)) * defaultPageSize
}
}
if memoryHigh != 0 && memoryHigh > memoryRequest {
unified[cm.Cgroup2MemoryHigh] = strconv.FormatInt(memoryHigh, 10)
}
}
- https://github.com/kubernetes/kubernetes/blob/bae2c62678db2b5053817bc97181fcc2e8388103/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go#L122-L147
この問題はメモリ要求と制限の値を少しズラずだけで回避できますが、すぐに対処することが難しい場合にはこのフィーチャゲートを有効にすることは避けるべきでしょう(まだアルファですし)。ただ、この仕様のまま GA してしまうと将来無効にできなくなってしまうため、この仕様が期待したものなのかをアップストリームに確認する予定です。
稳定Pod内存的 memory.oom.group
自 Kubernetes v1.28 起引入了一个名为 cgroup v2 memory.oom.group 的特性。
これまで Pod が OOM になった場合に子プロセスなど1つのプロセスだけが殺されてコンテナとしては健全なまま実行され続けていました。成熟したマルチプロセスなシステムではこのような子プロセスの OOM を正しく処理できますが、そうではないその他多くのシステムでは OOM になった場合に一部のプロセスが殺されて不安定になるよりはコンテナ全体を OOM として強制終了したほうが安定しそうです。そこで、Kubernetes v1.28 からコンテナ内のプロセスが OOM になった場合にコンテナ内のすべての PID を OOM として強制終了するようになりました。
- use the cgroup aware OOM killer if available by tzneal · Pull Request #117793 · kubernetes/kubernetes
问题:假设存在子进程在OOM之前耗尽资源的工作负载。
虽然在OOM时终止整个容器可能会使许多工作负载稳定,但我们的工作负载的一部分出现了问题。我们升级了集群到v1.28后的数天内,有人咨询说之前成功的作业现在却因为OOM而失败了。经询问得知,这个作业实际上是一个无法预知每个条目到底会消耗多少内存的工作负载,所以暂时先运行,如果子进程发生OOM,则计算另一个条目。由于启用了memory.oom.group,当子进程发生OOM时整个进程会被终止,导致无法继续计算。
同样,另一个查询中使用了make命令的-k选项,以便在交互式工作负载中,即使构建过程的某些部分失败,也能够继续进行;同时,在遇到OOM失败时,也能够成功构建(例如,make -j 20 -k || make -j 10)。然而,由于这个功能,当发生OOM之前,终端本身将被终止。
这些问题不仅仅需要修改清单文件,还需要改变应用程序和使用方式。因此,我们原本希望先将功能禁用,但在 v1.28 版本中,memory.oom.group 选项尚不可用,若要禁用该功能,则需要修改 Kubernetes 节点以不使用 cgroup v2。由于在我们的环境中暂时无法立即禁用 cgroup v2,我们选择构建并使用已删除该功能的 kubelet 进行处理。
diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go
index f14b6d1a6fc..0aef6395ce7 100644
--- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go
+++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go
@@ -233,15 +233,6 @@ func (m *kubeGenericRuntimeManager) calculateLinuxResources(cpuRequest, cpuLimit
resources.CpuPeriod = cpuPeriod
}
- // runc requires cgroupv2 for unified mode
- if isCgroup2UnifiedMode() {
- resources.Unified = map[string]string{
- // Ask the kernel to kill all processes in the container cgroup in case of OOM.
- // See memory.oom.group in https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html for
- // more info.
- "memory.oom.group": "1",
- }
- }
return &resources
}
我希望在上游添加一个禁用此功能的选项,并请求将其作为SIG-Node会议的议题来处理。
2023/12/8 追記: 在 SIG-Node 会议上,我们决定追加一个节点层面的 kubelet 配置字段来启用或禁用此功能,以允许管理员选择加入。请关注 1.30 版本中的更新信息。
链接:https://github.com/kubernetes/kubernetes/pull/117793#issuecomment-1845678609
结论
尽管Kubernetes已经支持cgroup v2,但在现实世界中使用cgroup v2的集群仍然很少。因此,与cgroup v2相关的问题可能尚未显露。考虑到将来使用cgroup v2的集群数量可能会增加,问题可能会显现出来,因此建议在一段时间内观察cgroup v2的使用情况。过去还存在以下问题。
- Kubernetes: cgroup v2 使用時に “failed to create fsnotify watcher: too many open files” エラーが発生する問題の対策 #kubernetes – Qiita
对于对cgroup产生兴趣的读者来说,我推荐阅读在gihyo.jp连载的《通过LXC学习容器入门-学习实现轻量级虚拟化环境的技术》,你可以在这篇文章中了解关于cgroup的内容。
PFN では大規模な機械学習プラットフォームを Kubernetes でいいかんじにしていくエンジニアを募集しています。お気軽にカジュアル面談にご応募ください。
-
- 株式会社 Preferred Networks “Infrastructure” 領域カジュアル面談応募フォーム
- Machine Learning Platform Engineer/機械学習プラットフォームエンジニア (Infrastructure) – Preferred Networks, Inc.