Kubernetes 可以被配置为在 节点 上使用交换内存,允许内核通过将页面换出到后台存储来释放物理内存。这在多种使用场景中很有用。例如,运行受益于使用交换空间的工作负载的节点,例如那些具有大内存占用但每次只访问该内存一部分的场景。它还有助于防止 Pod 在内存压力峰值期间被终止,保护节点免受可能损害其稳定性的系统级内存峰值影响,允许在节点上进行更灵活的内存管理,等等。
要了解如何在集群中配置交换空间,请阅读 在 Kubernetes 节点上配置交换内存。
对于节点上的交换空间使用,可以构想出多种可能的方式。如果 kubelet 已经在节点上运行,则在置备交换空间后需要重新启动它才能识别该空间。
当 kubelet 在已置备并可用交换空间的节点上(配置为 failSwapOn: false)启动时,kubelet 将
节点上的交换空间配置通过 KubeletConfiguration 中的 memorySwap 向集群管理员公开。作为集群管理员,您可以通过设置 memorySwap.swapBehavior 来指定节点在存在交换内存时的行为。
您需要选择要使用的 交换行为。集群中的不同节点可以使用不同的交换行为。
您可以为 Linux 节点选择的交换行为有
NoSwap(默认)LimitedSwap如果您选择 NoSwap 行为,并配置 kubelet 以容忍交换空间 (failSwapOn: false),则您的工作负载不会使用任何交换空间。
但是,在 Kubernetes 管理的容器之外的进程,例如 systemd 服务(甚至 kubelet 本身!)可以使用交换空间。
您可以阅读 在 Kubernetes 节点上配置交换内存 以了解如何为集群启用交换空间。
kubelet 使用容器运行时 API,并指示容器运行时应用特定配置(例如,在 cgroup v2 的情况下,memory.swap.max),以便为容器启用所需的交换空间配置。对于使用控制组(cgroups)的运行时,容器运行时负责将这些设置写入容器级别的 cgroup 中。
Kubelet 现在收集节点和容器级别的指标统计信息,可以通过 /metrics/resource(主要由 Prometheus 等监控工具使用)和 /stats/summary(主要由自动扩缩器使用)kubelet HTTP 端点访问这些统计信息。这允许可以直接请求 kubelet 的客户端在使用 LimitedSwap 时监控交换空间使用情况和剩余交换内存。此外,cadvisor 中还添加了一个 machine_swap_bytes 指标,以显示机器的总物理交换容量。更多信息请参阅 此页面。
例如,支持以下 /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 每秒操作次数 (IOPS) 受限的环境(例如具有 I/O 限制的云虚拟机)中,性能将远低于固态硬盘 (SSD) 或 NVMe 等更快的存储介质。由于交换空间可能会导致 I/O 压力,建议为系统关键守护进程提供更高的 I/O 延迟优先级。请参阅下面 建议的做法 部分中的相关章节。
在 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 的驱逐阈值配置得略低于节点的内存容量是合理的。其基本原理是,我们希望 Kubernetes 在节点内存耗尽并调用内存不足 (OOM) 杀手之前开始驱逐 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 不会被调度到带有交换内存的节点上(除非它们专门打算使用它),管理员可以给带有可用交换空间的节点贴上污点,以防止此问题。污点将确保容忍交换空间的工作负载不会在负载下溢出到没有交换空间的节点上。
指定用于交换空间的存储设备对于在高内存使用期间保持系统响应能力至关重要。旋转式硬盘驱动器 (HDD) 不适合此任务,因为其机械特性会引入显著的延迟,从而导致严重的性能下降和系统颠簸。对于现代性能需求,固态硬盘 (SSD) 等设备可能是交换空间的合适选择,因为其低延迟的电子访问最大限度地减少了减速。
交换内存的配置(包括其限制)提出了重大挑战。它不仅容易配置错误,而且作为系统级属性,任何错误配置都有可能危及整个节点,而不仅仅是特定的工作负载。为了降低这种风险并确保节点的健康,我们实施了具有自动配置限制的交换空间。
使用 LimitedSwap 时,不属于 Burstable QoS 分类的 Pod(即 BestEffort/Guaranteed QoS Pod)被禁止使用交换内存。BestEffort QoS Pod 表现出不可预测的内存消耗模式,并且缺乏有关其内存使用情况的信息,因此难以确定安全的交换内存分配。相反,Guaranteed QoS Pod 通常用于依赖工作负载指定的精确资源分配的应用程序,内存是立即可用的。为了保持上述的安全性和节点健康保证,当 LimitedSwap 生效时,这些 Pod 不允许使用交换内存。此外,高优先级 Pod 不允许使用交换空间,以确保它们消耗的内存始终驻留在磁盘上,因此随时可以使用。
在详细说明交换限制的计算之前,有必要定义以下术语:
nodeTotalMemory:节点上可用的物理内存总量。totalPodsSwapAvailable:节点上可供 Pod 使用的交换内存总量(一些交换内存可能为系统使用而保留)。containerMemoryRequest:容器的内存请求。交换空间限制配置为
( containerMemoryRequest / nodeTotalMemory ) × totalPodsSwapAvailable
换句话说,一个容器能够使用的交换量与其内存请求、节点的总物理内存以及节点上可供 Pod 使用的总交换内存量成正比。
需要注意的是,对于 Burstable QoS Pods 中的容器,可以通过将内存请求指定为等于内存限制来选择不使用交换。以这种方式配置的容器将无法访问交换内存。