Kubernetes 1.32 中为 Linux 用户带来的全新 Swap 功能
Swap 是 Linux 的一项基本且无价的功能。它提供了许多好处,例如通过交换未使用的数据来有效地增加节点的内存,防止节点因系统级内存峰值而崩溃,防止 Pod 在达到内存限制时崩溃,以及更多。因此,Kubernetes 项目中的节点特别兴趣小组(node special interest group)为支持 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 版本是向前迈出的巨大一步。它不仅修复了大量错误并使 swap 稳定运行,还带来了 cgroup v2 支持,引入了各种测试,包括节点级压力等复杂场景。它还带来了许多令人兴奋的新功能,例如 LimitedSwap
行为(为容器设置自动计算的 swap 限制)、OpenMetrics 仪器支持(通过 /metrics/resource
端点)以及用于 VerticalPodAutoscaler 的 Summary API(通过 /stats/summary
端点),等等。
今天,我们正在进行更多改进,为 GA 铺平道路。目前,重点特别放在确保节点稳定性、增强调试能力、解决用户反馈、完善功能并使其稳定。例如,为了提高稳定性,高优先级 Pod 中的容器无法访问 swap,这确保了它们所需的内存已准备就绪。此外,UnlimitedSwap
行为已被删除,因为它可能会损害节点的健康。还引入了防止 swap 泄露的 Secret 内容保护(有关更多信息,请参阅相关的安全风险部分)。
总而言之,与之前的版本相比,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 安装一个启用交换的集群
开始之前
此演示要求安装 kubeadm 工具,并遵循kubeadm 安装指南中的步骤。如果节点上已启用 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 单元来激活(加密)swap,或者将类似 /swapfile swap swap defaults 0 0
的行添加到 /etc/fstab
。
设置使用启用 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
创建一个单节点集群。在 init 过程中,会出现一个警告,提示节点上启用了 swap,并且 kubelet 的 failSwapOn
设置为 true。我们计划在未来的版本中删除此警告。
使用 LimitedSwap 时如何确定交换限制?
Swap 内存的配置(包括其限制)是一个重大挑战。它不仅容易配置错误,而且作为一个系统级属性,任何配置错误都可能潜在地损害整个节点,而不仅仅是特定的工作负载。为了降低此风险并确保节点的健康,我们已实现 Swap 并自动配置限制。
在 LimitedSwap
模式下,不允许不属于 Burstable QoS 分类(即 BestEffort
/Guaranteed
QoS Pods)的 Pod 使用 swap 内存。BestEffort
QoS Pods 的内存消耗模式不可预测,并且缺乏关于其内存使用量的信息,因此难以确定安全的 swap 内存分配。相反,Guaranteed
QoS Pods 通常用于依赖于工作负载精确分配的资源的应用,其内存可立即使用。为了维护上述的安全性和节点健康保证,当 LimitedSwap
生效时,这些 Pod 不允许使用 swap 内存。此外,高优先级 Pod 不允许使用 swap,以确保它们消耗的内存始终驻留在磁盘上,从而随时可用。
在详细说明交换限制的计算之前,有必要定义以下术语:
nodeTotalMemory
:节点上可用的物理内存总量。totalPodsSwapAvailable
:节点上可供 Pod 使用的交换内存总量(一些交换内存可能为系统使用而保留)。containerMemoryRequest
:容器的内存请求。
Swap 限制配置为:(containerMemoryRequest / nodeTotalMemory) × totalPodsSwapAvailable
换句话说,一个容器能够使用的交换量与其内存请求、节点的总物理内存以及节点上可供 Pod 使用的总交换内存量成正比。
需要注意的是,对于 Burstable QoS Pods 中的容器,可以通过将内存请求指定为等于内存限制来选择不使用交换。以这种方式配置的容器将无法访问交换内存。
它是如何工作的?
有多种可能的方式可以设想节点上的 swap 使用。当节点上已配置并可用 swap 时,kubelet 可以配置为
- 它可以在交换开启的情况下启动。
- 默认情况下,它将指示容器运行时接口(Container Runtime Interface)为 Kubernetes 工作负载分配零交换内存。
节点上的 Swap 配置通过 KubeletConfiguration 中的 memorySwap
向集群管理员公开。作为集群管理员,您可以通过设置 memorySwap.swapBehavior
来指定节点在存在 swap 内存时的行为。
kubelet 使用 CRI(容器运行时接口)API,并指示容器运行时以适当的方式配置特定的 cgroup v2 参数(例如 memory.swap.max
),以启用容器所需的 swap 配置。对于使用控制组的运行时,容器运行时负责将这些设置写入容器级别的 cgroup。
如何监控 swap?
节点和容器级别的指标统计信息
Kubelet 现在收集节点和容器级别的指标统计信息,这些信息可以通过 /metrics/resource
(主要用于 Prometheus 等监控工具)和 /stats/summary
(主要用于 Autoscalers)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 允许 Kubernetes 中工作负载使用不可预测地计数的内存,并且由于意外的打包配置,调度器目前不考虑 swap 内存使用量。这增加了“嘈杂邻居”的风险。
启用了 swap 内存的节点的性能取决于底层的物理存储。当使用 swap 内存时,在 IOPS(每秒输入输出操作)受限的环境(例如具有 I/O 节流的云虚拟机)中,性能会比固态驱动器或 NVMe 等更快的存储介质差得多。由于 swap 可能会导致 I/O 压力,因此建议为系统关键守护进程提供更高的 I/O 延迟优先级。请参阅下方推荐实践部分的相关部分。
内存支持的卷
在 Linux 节点上,内存支持的卷(例如 secret
卷挂载,或具有 medium: Memory
的 emptyDir
)是通过 tmpfs
文件系统实现的。这些卷的内容应始终保留在内存中,因此不应交换到磁盘。为确保这些卷的内容保留在内存中,正在使用 noswap
tmpfs 选项。
Linux 内核正式支持从 6.3 版本开始的 noswap
选项(有关更多信息,请参阅Linux Kernel Version Requirements)。但是,不同的发行版通常会选择将此挂载选项反向移植到更旧的 Linux 版本。
为了验证节点是否支持 noswap
选项,kubelet 将执行以下操作:
- 如果内核版本高于 6.3,则假定
noswap
选项受支持。 - 否则,kubelet 将在启动时尝试使用
noswap
选项挂载一个 dummy tmpfs。如果 kubelet 因未知选项错误而失败,则假定noswap
不受支持,因此将不使用。将发出一个 kubelet 日志条目,警告用户内存支持的卷可能会交换到磁盘。如果 kubelet 成功,将删除 dummy tmpfs 并使用noswap
选项。- 如果
noswap
选项不受支持,kubelet 将发出警告日志条目,然后继续执行。
- 如果
强烈建议加密 swap 空间。请参阅上面的部分,其中有一个设置未加密 swap 的示例。然而,处理加密 swap 不在 kubelet 的范围之内;而是操作系统级别的通用配置,应在该级别处理。管理员有责任配置加密 swap 以减轻此风险。
在 Kubernetes 集群中使用 swap 的最佳实践
为系统关键守护进程禁用 swap
在测试阶段和根据用户反馈,观察到系统关键守护进程和服务的性能可能会下降。这意味着系统守护进程(包括 kubelet)的运行速度可能会比平时慢。如果遇到此问题,建议配置系统切片(system slice)的 cgroup 以防止 swap(即,设置 memory.swap.max=0
)。
保护系统关键守护进程免受 I/O 延迟的影响
Swap 会增加节点的 I/O 负载。当内存压力导致内核快速地将页面 in 和 out 交换时,依赖 I/O 操作的系统关键守护进程和服务可能会经历性能下降。
为了缓解这种情况,建议 systemd 用户在 I/O 延迟方面优先考虑系统切片。对于非 systemd 用户,建议为系统守护进程和进程设置专用的 cgroup,并以相同的方式优先 I/O 延迟。这可以通过为系统切片设置 io.latency
来实现,从而使其获得更高的 I/O 优先级。有关更多信息,请参阅cgroup 的文档。
Swap 和控制平面节点
Kubernetes 项目建议在运行控制平面节点时不要配置任何 swap 空间。控制平面主要托管 Guaranteed QoS Pods,因此 swap 通常可以禁用。主要顾虑是控制平面上的关键服务进行 swap 可能会对性能产生负面影响。
使用专用磁盘进行 swap
建议为 swap 分区使用单独的、加密的磁盘。如果 swap 位于分区或根文件系统上,工作负载可能会干扰需要写入磁盘的系统进程。当它们共享同一磁盘时,进程可能会使 swap 饱和,从而破坏 kubelet、容器运行时和 systemd 的 I/O,这将影响其他工作负载。由于 swap 空间位于磁盘上,因此确保磁盘足够快以满足预期用例至关重要。或者,可以配置单个后端设备的不同映射区域之间的 I/O 优先级。
展望未来
正如您所见,swap 功能在最近得到了极大的改进,为功能 GA 铺平了道路。然而,这仅仅是开始。这是一个基础性的实现,标志着增强 swap 功能的开端。
在不久的将来,计划增加更多功能来进一步改进 swap 的能力,包括更好的驱逐机制、扩展的 API 支持、更高的可定制性、更好的调试能力等等!
我如何了解更多信息?
您可以查阅当前文档以了解如何在 Kubernetes 中使用 swap。
我如何参与?
您的反馈始终受到欢迎!SIG Node 定期开会,并可通过 Slack(频道 #sig-node)或 SIG 的邮件列表联系。还有一个专门讨论 swap 的 Slack 频道,名为 #sig-node-swap。
如果您想帮忙或有进一步的问题,请随时联系我,Itamar Holder(Slack 和 GitHub 上为@iholder101)。