交换内存管理
Kubernetes 可以配置为在节点上使用 swap 内存,允许内核通过将页面换出到后端存储来释放物理内存。这对于多种用例都很有用。例如,运行可以从使用 swap 中受益的工作负载的节点,例如那些内存占用大但在任何给定时间只访问其中一部分内存的工作负载。它还有助于防止 Pod 在内存压力高峰期间被终止,保护节点免受可能损害其稳定性的系统级内存高峰的影响,允许在节点上进行更灵活的内存管理,等等。
如何使用?
先决条件
- 必须在节点上启用并配置 swap。
- 节点必须运行 Linux 操作系统。
- 节点必须使用 cgroup v2。Kubernetes 不支持 cgroup v1 节点上的 swap。
为 Kubernetes 工作负载启用 swap
要允许 Kubernetes 工作负载使用 swap,您必须禁用 kubelet 默认的在检测到 swap 时失败的行为,并将 memory-swap 行为指定为 LimitedSwap
。
更新 kubelet 配置
# this fragment goes into the kubelet's configuration file
failSwapOn: false
memorySwap:
swapBehavior: LimitedSwap
swapBehavior
的可用选项是
NoSwap
(默认):Kubernetes 工作负载不能使用 swap。然而,Kubernetes 范围之外的进程,例如系统守护进程(如 kubelet 本身!)可以使用 swap。这种行为有利于保护节点免受系统级内存高峰的影响,但它不能保护工作负载本身免受此类高峰的影响。LimitedSwap
:Kubernetes 工作负载可以使用 swap 内存。可用于 Pod 的 swap 量会自动确定。有关更多详细信息,请参阅下面的章节。
如果未指定 memorySwap
的配置,kubelet 将默认应用与 NoSwap
设置相同的行为。
请注意,以下 Pod 将被排除在 swap 访问之外(更多信息请参阅下面的章节)
- 未归类为“可突发 QoS (Burstable QoS)”的 Pod。
- 高优先级 Pod。
- 内存限制等于内存请求的容器。
注意
Kubernetes 仅支持 Linux 节点上的 swap。它是如何工作的?
节点上使用 swap 的方式有多种可能。如果 kubelet 已经在节点上运行,则在配置 swap 后需要重新启动它才能识别 swap。
当 kubelet 在配置并可用 swap 的节点上启动时(使用 failSwapOn: false
配置),kubelet 将
- 能够在启用 swap 的节点上启动。
- 默认指示容器运行时接口(CRI)实现(通常称为容器运行时)为 Kubernetes 工作负载分配零 swap 内存。
节点上的 swap 配置通过 KubeletConfiguration 中的 memorySwap
暴露给集群管理员。作为集群管理员,您可以通过设置 memorySwap.swapBehavior
来指定节点在存在 swap 内存时的行为。
kubelet 使用容器运行时 API,并指示容器运行时应用特定配置(例如,在 cgroup v2 的情况下,memory.swap.max
),以启用容器所需的 swap 配置。对于使用控制组(cgroups)的运行时,容器运行时负责将这些设置写入容器级 cgroup。
swap 使用的可观测性
节点和容器级指标统计
Kubelet 现在收集节点和容器级指标统计信息,可以通过 /metrics/resource
(主要由 Prometheus 等监控工具使用)和 /stats/summary
(主要由自动扩缩器使用)kubelet HTTP 端点访问。这允许直接请求 kubelet 的客户端在使用 LimitedSwap
时监控 swap 使用情况和剩余 swap 内存。此外,cadvisor 中添加了 machine_swap_bytes
指标,以显示机器的总物理 swap 容量。有关更多信息,请参阅此页面。
例如,支持以下 /metrics/resource
node_swap_usage_bytes
:节点的当前 swap 使用量(以字节为单位)。container_swap_usage_bytes
:容器当前 swap 使用量(以字节为单位)。container_swap_limit_bytes
:容器当前 swap 限制量(以字节为单位)。
使用 kubectl top --show-swap
查询指标很有价值,但有些繁琐,因为这些指标是为软件而不是人类设计的。为了以更用户友好的方式使用这些数据,kubectl top
命令已扩展以支持 swap 指标,使用 --show-swap
标志。
为了接收有关节点上 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 swap 使用情况的信息,可以使用 kubectl top nodes --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
节点将 swap 容量作为节点状态的一部分进行报告
现在添加了一个新的节点状态字段 node.status.nodeInfo.swap.capacity
,用于报告节点的 swap 容量。
例如,以下命令可用于检索集群中节点的 swap 容量
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
字段未设置。这可能意味着该节点未配置 swap,或者不太可能,kubelet 无法确定节点的 swap 容量。使用节点特征发现(NFD)进行 swap 发现
节点特征发现 (Node Feature Discovery) 是一个 Kubernetes 插件,用于检测硬件特征和配置。它可以用于发现哪些节点配置了 swap。
例如,要找出哪些节点配置了 swap,请使用以下命令
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
上配置了 swap,但 k8s-worker3
上没有。
风险和注意事项
注意
强烈建议加密 swap 空间。有关更多信息,请参阅内存支持的卷memory-backed volumes。系统上存在 swap 会降低可预测性。虽然 swap 可以通过提供更多 RAM 来提高性能,但将数据换回内存是一项繁重的操作,有时速度会慢很多个数量级,这可能导致意外的性能下降。此外,swap 会改变系统在内存压力下的行为。启用 swap 会增加“吵闹的邻居”的风险,即频繁使用 RAM 的 Pod 可能会导致其他 Pod 发生 swap。此外,由于 swap 允许 Kubernetes 中的工作负载使用更多的内存,而这无法可靠地进行核算,并且由于意外的打包配置,调度器目前不考虑 swap 内存使用。这增加了“吵闹的邻居”的风险。
启用 swap 内存的节点性能取决于底层物理存储。当使用 swap 内存时,在 IOPS 受限的环境中(例如具有 I/O 限制的云 VM),性能会明显低于固态硬盘或 NVMe 等更快的存储介质。由于 swap 可能会导致 IO 压力,建议为系统关键守护进程设置更高的 IO 延迟优先级。请参阅下面推荐实践部分中的相关内容。
内存支持的卷
在 Linux 节点上,内存支持的卷(例如secret
卷挂载,或带有 medium: Memory
的emptyDir
)是通过 tmpfs
文件系统实现的。此类卷的内容应始终保留在内存中,因此不应交换到磁盘。为了确保此类卷的内容保留在内存中,使用了 noswap
tmpfs 选项。
Linux 内核从 6.3 版本开始正式支持 noswap
选项(更多信息可在Linux 内核版本要求中找到)。然而,不同的发行版通常也会将此挂载选项反向移植到较旧的 Linux 版本。
为了验证节点是否支持 noswap
选项,kubelet 将执行以下操作
- 如果内核版本高于 6.3,则假定支持
noswap
选项。 - 否则,kubelet 将在启动时尝试使用
noswap
选项挂载一个虚拟 tmpfs。如果 kubelet 因指示未知选项的错误而失败,则假定不支持noswap
,因此将不使用。将发出 kubelet 日志条目以警告用户内存支持的卷可能会交换到磁盘。如果 kubelet 成功,虚拟 tmpfs 将被删除,并且将使用noswap
选项。- 如果不支持
noswap
选项,kubelet 将发出警告日志条目,然后继续执行。
- 如果不支持
请参阅上面的章节,其中包含设置未加密 swap 的示例。然而,处理加密 swap 不在 kubelet 的范围之内;相反,它是一个通用的操作系统配置问题,应在该级别解决。管理员有责任提供加密 swap 以缓解此风险。
逐出
为启用 swap 的节点配置内存逐出阈值可能很棘手。
禁用 swap 后,将 kubelet 的逐出阈值配置为略低于节点内存容量是合理的。其理由是,我们希望 Kubernetes 在节点内存不足并调用内存不足(OOM)杀手之前开始逐出 Pod,因为 OOM 杀手不感知 Kubernetes,因此不考虑 QoS、Pod 优先级或其他 Kubernetes 特定因素。
启用 swap 后,情况变得更加复杂。在 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
未使用的 swap 空间
在 LimitedSwap
行为下,可用于 Pod 的 swap 量会根据其请求内存与节点总内存的比例自动确定(有关更多详细信息,请参阅下面的章节)。
这种设计意味着通常会有部分 swap 保持对 Kubernetes 工作负载的限制。例如,由于 Guaranteed QoS Pod 目前不允许使用 swap,因此与内存请求成比例的 swap 量将保持不被 Kubernetes 工作负载使用。
这种行为在许多 Pod 不符合 swap 条件的情况下带来了一些风险。另一方面,它有效地保留了一些系统保留的 swap 内存量,可以供 Kubernetes 范围之外的进程使用,例如系统守护进程甚至 kubelet 本身。
在 Kubernetes 集群中使用 swap 的最佳实践
禁用系统关键守护进程的 swap
在测试阶段和根据用户反馈,我们观察到系统关键守护进程和服务的性能可能会下降。这意味着系统守护进程,包括 kubelet,可能会比平时运行得更慢。如果遇到此问题,建议配置系统切片的 cgroup 以防止交换(即,设置 memory.swap.max=0
)。
保护系统关键守护进程的 I/O 延迟
Swap 会增加节点上的 I/O 负载。当内存压力导致内核快速交换页面时,依赖 I/O 操作的系统关键守护进程和服务可能会遇到性能下降。
为了缓解这种情况,建议 systemd 用户在 I/O 延迟方面优先考虑系统切片。对于非 systemd 用户,建议为系统守护进程和进程设置一个专用的 cgroup,并以相同的方式优先考虑 I/O 延迟。这可以通过为系统切片设置 io.latency
来实现,从而赋予它更高的 I/O 优先级。有关更多信息,请参阅cgroup 文档。
Swap 和控制平面节点
Kubernetes 项目建议运行不配置任何 swap 空间的控制平面节点。控制平面主要托管 Guaranteed QoS Pod,因此通常可以禁用 swap。主要担忧是交换控制平面上的关键服务可能会对性能产生负面影响。
使用专用磁盘进行 swap
Kubernetes 项目建议在启用 swap 的节点上始终使用加密 swap。如果 swap 位于分区或根文件系统上,工作负载可能会干扰需要写入磁盘的系统进程。当它们共享同一个磁盘时,进程可能会使 swap 不堪重负,从而扰乱 kubelet、容器运行时和 systemd 的 I/O,这将影响其他工作负载。由于 swap 空间位于磁盘上,因此确保磁盘足够快以满足预期用例至关重要。或者,可以在单个后端设备的不用映射区域之间配置 I/O 优先级。
Swap 感知调度
Kubernetes 1.34 不支持以考虑 swap 内存使用的方式将 Pod 分配给节点。调度器通常使用基础设施资源的“请求”来指导 Pod 放置,而 Pod 不请求 swap 空间;它们只请求 memory
。这意味着调度器在做出调度决策时不考虑 swap 内存。虽然这是我们正在积极努力解决的问题,但尚未实施。
为了管理员确保 Pod 不会调度到具有 swap 内存的节点上,除非它们明确打算使用它,管理员可以对具有可用 swap 的节点进行污点处理以防止此问题。污点将确保容忍 swap 的工作负载在负载下不会溢出到没有 swap 的节点上。
选择存储以实现最佳性能
指定用于 swap 空间的存储设备对于在高内存使用期间保持系统响应能力至关重要。旋转硬盘驱动器 (HDD) 不适合此任务,因为其机械特性会引入显著的延迟,从而导致严重的性能下降和系统颠簸。对于现代性能需求,固态驱动器 (SSD) 等设备可能是 swap 的适当选择,因为其低延迟电子访问最大限度地减少了速度下降。
Swap 行为详情
使用 LimitedSwap 时,swap 限制是如何确定的?
Swap 内存的配置,包括其限制,是一个重大挑战。它不仅容易出现配置错误,而且作为系统级属性,任何配置错误都可能危及整个节点,而不仅仅是特定工作负载。为了减轻此风险并确保节点的健康,我们实施了带有自动限制配置的 Swap。
使用 LimitedSwap
,不属于可突发 QoS 分类(即 BestEffort
/Guaranteed
QoS Pod)的 Pod 被禁止使用 swap 内存。BestEffort
QoS Pod 表现出不可预测的内存消耗模式,并且缺乏有关其内存使用情况的信息,因此难以确定安全的 swap 内存分配。相反,Guaranteed
QoS Pod 通常用于依赖工作负载指定的精确资源分配的应用程序,其中内存是立即可用的。为了保持上述安全性和节点健康保证,当 LimitedSwap
生效时,这些 Pod 不允许使用 swap 内存。此外,高优先级 Pod 不允许使用 swap,以确保它们消耗的内存始终驻留在磁盘上,因此随时可用。
在详细说明 swap 限制的计算之前,有必要定义以下术语
nodeTotalMemory
:节点上可用的物理内存总量。totalPodsSwapAvailable
:节点上可供 Pod 使用的 swap 内存总量(部分 swap 内存可能保留用于系统使用)。containerMemoryRequest
:容器的内存请求。
Swap 限制配置为
( containerMemoryRequest
/ nodeTotalMemory
) × totalPodsSwapAvailable
换句话说,容器能够使用的 swap 量与其内存请求、节点总物理内存以及节点上可供 Pod 使用的 swap 内存总量成比例。
需要注意的是,对于可突发 QoS Pod 中的容器,可以通过指定等于内存限制的内存请求来选择不使用 swap。以这种方式配置的容器将无法访问 swap 内存。
下一步
- 您可以查看一篇关于 Kubernetes 和 swap 的博客文章
- 有关更多信息,请参阅原始 KEP,KEP-2400 及其设计。