Kubernetes 1.32 中面向 Linux 用户的全新 Swap 特性
Swap 是一个基础且价值极高的 Linux 特性。它提供了诸多优势,例如通过换出未使用数据有效增加节点的内存、保护节点免受系统级内存峰值影响、防止 Pod 在达到内存限制时崩溃等,还有更多。因此,Kubernetes 项目中的节点特别兴趣小组 (SIG Node) 投入了大量精力支持 Linux 节点上的 Swap。
1.22 版本引入了针对在 Linux 上运行的 Kubernetes 工作负载按节点配置 Swap 内存使用的 Alpha 支持。随后,在 1.28 版本中,Linux 节点上的 Swap 支持升级到了 Beta,同时带来了许多新改进。在随后的 Kubernetes 版本中,又进行了更多改进,为近期达到 GA 铺平了道路。
在 1.22 版本之前,Kubernetes 不支持 Linux 系统上的 Swap 内存。这是由于涉及 Swap 内存时,难以保证和统计 Pod 的内存使用情况。因此,在 Kubernetes 的初始设计中,Swap 支持被认为是超出范围的,并且 Kubelet 的默认行为是在检测到节点上存在 Swap 内存时启动失败。
在 1.22 版本中,Linux 的 Swap 特性首次以 Alpha 阶段引入。这为 Linux 用户提供了第一次尝试 Swap 特性的机会。然而,作为 Alpha 版本,它并未完全开发成熟,并且仅在有限的环境下部分工作。
在 1.28 版本中,Linux 节点上的 Swap 支持升级到了 Beta。Beta 版本是一个巨大飞跃。它不仅修复了大量 bug 并使 Swap 能够稳定工作,还带来了 cgroup v2 支持,引入了各种复杂的测试场景,例如节点级压力测试等。此外,它还带来了许多令人兴奋的新能力,例如 LimitedSwap
行为(为容器设置自动计算的 Swap 限制)、OpenMetrics 检测支持(通过 /metrics/resource
端点)以及 VerticalPodAutoscalers 的 Summary API 支持(通过 /stats/summary
端点)等等。
目前我们正在进行更多改进,为达到 GA 铺平道路。当前重点特别放在确保节点稳定性、增强调试能力、处理用户反馈、完善特性并使其稳定。例如,为了提高稳定性,高优先级 Pod 中的容器无法访问 Swap,这确保了它们所需的内存随时可用。此外,UnlimitedSwap
行为已被移除,因为它可能危及节点健康。还引入了 Secret 内容防 Swap 保护(更多信息请参阅相关的安全风险章节)。
总而言之,与之前的版本相比,Kubelet 对启用 Swap 运行的支持更加稳定可靠、对用户更友好,并且解决了许多已知缺点。话虽如此,NodeSwap 特性只提供了基本的 Swap 支持,这仅仅是个开始。在不久的将来,计划增加更多特性,以多种方式增强 Swap 功能,例如改进驱逐机制、扩展 API、提高可定制性等等!
如何使用?
为了让 Kubelet 在启用 Swap 的节点上初始化,必须在 Kubelet 的配置设置中将 failSwapOn
字段设置为 false
,或者停用已弃用的 --fail-swap-on
命令行标志。
可以配置 memorySwap.swapBehavior
选项来定义节点使用 Swap 内存的方式。例如:
# this fragment goes into the kubelet's configuration file
memorySwap:
swapBehavior: LimitedSwap
当前可用的 swapBehavior
配置选项有:
NoSwap
(默认):Kubernetes 工作负载不能使用 Swap。然而,Kubernetes 范围之外的进程,如系统守护进程(包括 Kubelet 本身!),可以使用 Swap。这种行为有助于保护节点免受系统级内存峰值影响,但它不保护工作负载本身免受此类峰值影响。LimitedSwap
:Kubernetes 工作负载可以使用 Swap 内存,但存在某些限制。Pod 可用的 Swap 量是自动确定的,基于请求的内存相对于节点总内存的比例。仅允许 Burstable 服务质量 (QoS) 等级下的非高优先级 Pod 使用 Swap。更多详情,请参阅以下章节。
如果未指定 memorySwap
的配置,默认情况下 Kubelet 将应用与 NoSwap
设置相同的行为。
在 Linux 节点上,Kubernetes 仅支持在使用 cgroup v2 的主机上启用 Swap 运行。在 cgroup v1 系统上,所有 Kubernetes 工作负载都不允许使用 Swap 内存。
使用 kubeadm 安装启用 Swap 的集群
开始之前
本演示所需的 kubeadm 工具必须已安装,请遵循kubeadm 安装指南中概述的步骤。如果节点上已启用 Swap,则集群创建可以继续。如果未启用 Swap,请参考提供的说明以启用 Swap。
创建 Swap 文件并开启 Swap
我将演示创建 4GiB 的 Swap,包括加密和未加密的情况。
设置未加密 Swap
可以如下设置未加密 Swap 文件。
# Allocate storage and restrict access
fallocate --length 4GiB /swapfile
chmod 600 /swapfile
# Format the swap space
mkswap /swapfile
# Activate the swap space for paging
swapon /swapfile
设置加密 Swap
可以如下设置加密 Swap 文件。请记住本例使用 cryptsetup
二进制文件(在大多数 Linux 发行版上可用)。
# Allocate storage and restrict access
fallocate --length 4GiB /swapfile
chmod 600 /swapfile
# Create an encrypted device backed by the allocated storage
cryptsetup --type plain --cipher aes-xts-plain64 --key-size 256 -d /dev/urandom open /swapfile cryptswap
# Format the swap space
mkswap /dev/mapper/cryptswap
# Activate the swap space for paging
swapon /dev/mapper/cryptswap
验证 Swap 是否启用
可以使用 swapon -s
命令或 free
命令验证 Swap 是否已启用。
> swapon -s
Filename Type Size Used Priority
/dev/dm-0 partition 4194300 0 -2
> free -h
total used free shared buff/cache available
Mem: 3.8Gi 1.3Gi 249Mi 25Mi 2.5Gi 2.5Gi
Swap: 4.0Gi 0B 4.0Gi
开机启用 Swap
设置好 Swap 后,要在开机时启动 Swap 文件,您可以设置 systemd unit 激活(加密)Swap,或者在 /etc/fstab
中添加类似 /swapfile swap swap defaults 0 0
的一行。
设置使用启用 Swap 节点的 Kubernetes 集群
为了使事情更清晰,这里是一个用于启用 Swap 集群的 kubeadm 配置文件示例 kubeadm-config.yaml
。
---
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: InitConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: false
memorySwap:
swapBehavior: LimitedSwap
然后使用 kubeadm init --config kubeadm-config.yaml
创建一个单节点集群。在初始化过程中,如果 Kubelet 的 failSwapOn
设置为 true,会有一个警告提示节点上已启用 Swap。我们计划在未来版本中移除此警告。
LimitedSwap 如何确定 Swap 限制?
Swap 内存的配置(包括其限制)带来了重大挑战。它不仅容易配置错误,而且作为系统级属性,任何配置错误都可能危及整个节点,而不仅仅是特定的工作负载。为了缓解这一风险并确保节点健康,我们实现了带自动配置限制的 Swap。
使用 LimitedSwap
时,不属于 Burstable QoS 分类的 Pod(即 BestEffort
/Guaranteed
QoS Pod)被禁止使用 Swap 内存。BestEffort
QoS Pod 表现出不可预测的内存消耗模式,并且缺乏其内存使用信息,难以确定安全的 Swap 内存分配量。相反,Guaranteed
QoS Pod 通常用于依赖于工作负载指定的精确资源分配的应用,其内存必须即时可用。为了维持上述安全和节点健康保障,在 LimitedSwap
生效时,这些 Pod 不允许使用 Swap 内存。此外,高优先级 Pod 不允许使用 Swap,以确保它们所需的内存始终位于内存中,因此随时可用。
在详细说明计算之前,需要定义以下术语:
nodeTotalMemory
:节点上可用的物理内存总量。totalPodsSwapAvailable
:节点上可供 Pod 使用的总 Swap 内存量(部分 Swap 内存可能保留供系统使用)。containerMemoryRequest
:容器的内存请求。
Swap 限制配置为:(containerMemoryRequest / nodeTotalMemory) × totalPodsSwapAvailable
换句话说,容器可以使用的 Swap 量与其内存请求、节点总物理内存以及节点上可供 Pod 使用的总 Swap 内存量成比例。
值得注意的是,对于 Burstable QoS Pod 内的容器,可以通过指定内存请求等于内存限制来选择退出 Swap 使用。以这种方式配置的容器将无法访问 Swap 内存。
工作原理?
可以设想在节点上使用 Swap 的几种可能方式。当节点上已配置并可用的 Swap 时,Kubelet 可以配置成如下方式:
- 可以在 Swap 开启时启动。
- 它将指示容器运行时接口 (CRI) 默认情况下为 Kubernetes 工作负载分配零 Swap 内存。
节点上的 Swap 配置通过 KubeletConfiguration 中的 memorySwap
暴露给集群管理员。作为集群管理员,您可以通过设置 memorySwap.swapBehavior
来指定节点在存在 Swap 内存时的行为。
Kubelet 使用 CRI(容器运行时接口)API,并指示容器运行时配置特定的 cgroup v2 参数(例如 memory.swap.max
),以便为容器启用期望的 Swap 配置。对于使用控制组 (cgroup) 的运行时,容器运行时负责将这些设置写入容器级别的 cgroup。
如何监控 Swap?
节点和容器级别指标统计
Kubelet 现在收集节点和容器级别指标统计信息,可以从 /metrics/resource
(主要由 Prometheus 等监控工具使用)和 /stats/summary
(主要由自动伸缩器使用)Kubelet HTTP 端点访问。这允许能够直接查询 Kubelet 的客户端在使用 LimitedSwap
时监控 Swap 使用量和剩余 Swap 内存。此外,cadvisor 中添加了 machine_swap_bytes
指标,用于显示机器的总物理 Swap 容量。更多信息请参阅此页面。
Node Feature Discovery (NFD)
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
在本例中,Swap 配置在节点 k8s-worker1
和 k8s-worker2
上,但不在 k8s-worker3
上。
注意事项
系统上启用 Swap 会降低可预测性。虽然 Swap 可以通过使更多 RAM 可用而提升性能,但将数据换回内存是一项开销大的操作,有时慢几个数量级,可能导致意外的性能下降。此外,在内存压力下,Swap 会改变系统行为。启用 Swap 会增加“吵闹邻居”的风险,即频繁使用 RAM 的 Pod 可能导致其他 Pod 换出。此外,由于 Swap 允许工作负载使用更多内存,而这无法可预测地进行统计,并且由于意外的打包配置,调度器当前不考虑 Swap 内存使用。这会增加“吵闹邻居”的风险。
启用 Swap 内存的节点性能取决于底层物理存储。当使用 Swap 内存时,在受每秒 I/O 操作数 (IOPS) 限制的环境中(例如带有 I/O 限制的云虚拟机),性能将显著变差,而与固态硬盘或 NVMe 等更快的存储介质相比。由于 Swap 可能导致 IO 压力,建议给予系统关键守护进程更高的 IO 延迟优先级。请参阅推荐实践章节中的相关部分。
内存支持的卷
在 Linux 节点上,内存支持的卷(例如 secret
卷挂载,或 emptyDir
设置 medium: Memory
)使用 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 将输出一条警告日志条目,然后继续执行。
- 如果
强烈建议加密交换空间。请参阅上面的章节,其中有一个关于设置未加密交换的示例。然而,处理加密交换不属于 kubelet 的范围;它是一个通用的操作系统配置问题,应该在该层面解决。管理员有责任配置加密交换来降低此风险。
在 Kubernetes 集群中使用交换空间的最佳实践
为系统关键守护进程禁用交换空间
在测试阶段并根据用户反馈,观察到系统关键守护进程和服务的性能可能会下降。这意味着包括 kubelet 在内的系统守护进程运行速度可能比平时慢。如果遇到此问题,建议配置 system slice 的 cgroup 以防止交换(即设置 memory.swap.max=0
)。
保护系统关键守护进程免受 I/O 延迟影响
交换空间会增加节点上的 I/O 负载。当内存压力导致内核快速换入换出页面时,依赖 I/O 操作的系统关键守护进程和服务可能会经历性能下降。
为缓解此问题,对于 systemd 用户,建议在 I/O 延迟方面优先处理 system slice。对于非 systemd 用户,建议为系统守护进程和进程设置一个专门的 cgroup,并以同样的方式优先处理 I/O 延迟。这可以通过为 system slice 设置 io.latency
来实现,从而赋予其更高的 I/O 优先级。有关更多信息,请参阅cgroup 文档。
交换空间与控制平面节点
Kubernetes 项目建议运行控制平面节点时不安置任何交换空间。控制平面主要托管 Guaranteed QoS Pods,因此通常可以禁用交换。主要顾虑是控制平面上关键服务的交换操作可能会对性能产生负面影响。
为交换空间使用专用磁盘
建议为交换分区使用单独的、加密的磁盘。如果交换空间位于某个分区或根文件系统上,工作负载可能会干扰需要写入磁盘的系统进程。当它们共享同一磁盘时,进程可能会使交换空间不堪重负,从而扰乱 kubelet、容器运行时和 systemd 的 I/O,进而影响其他工作负载。由于交换空间位于磁盘上,因此确保磁盘对于预期的用例足够快至关重要。或者,可以在单个后备设备的不同映射区域之间配置 I/O 优先级。
展望未来
如您所见,最近交换功能得到了显著改进,为功能的正式发布铺平了道路。然而,这只是开始。这是一个基础实现,标志着增强型交换功能的开启。
在不久的将来,计划增加更多功能以进一步提升交换能力,包括更好的驱逐机制、扩展的 API 支持、更高的可定制性、更强的调试能力等等!
如何了解更多?
您可以查阅当前关于在 Kubernetes 中使用交换空间的文档。
如何参与?
随时欢迎您的反馈!SIG Node 定期会面,您可以通过Slack(频道 #sig-node)或 SIG 的邮件列表与我们联系。还有一个专门讨论交换空间的 Slack 频道:#sig-node-swap。
如果您想提供帮助或有其他问题,请随时联系我,Itamar Holder(Slack 和 GitHub 上的 @iholder101)。