K8s 最佳实践已更新:请使用 Node Swap!
概述
K8s 最佳实践已更新:
满足以下条件:
- K8s 1.34 及以上版本
- Node 的系统盘使用的是本地 SSD 或 NVME 高速存储
- cgroup v2
请使用 node swap!
要启用,推荐 kubelet config 添加如下配置:
failSwapOn: false
memorySwap:
swapBehavior: LimitedSwap
👍收益
👍️优点
- 省钱:符合条件的工作负载能在内存压力下把空闲匿名页换出去,Pod 密度会提高很多;
- 稳定性提高:内存尖峰有了缓冲,不再只有被 OOM 杀掉这一条路;
- 改动小:宿主 swap 文件、分区、
/etc/fstab一行没改,改动面就一个 kubelet 配置项; - 可观测能力已经就绪:可使用
node.status.nodeInfo.swap.capacity和kubectl top nodes --show-swap检查 swap。
📝备忘:
- 只有 Burstable 且 request < limit 的容器吃到红利,Guaranteed 工作负载完全无感;
- 调度器并不会感知 swap,容量规划还是按 node 物理内存规划,不能把它算进去;
- 换出的是匿名页,落到磁盘上就是 I/O,系统盘不是本地 SSD 或 NVME 高速存储请勿开启。
社区普遍的看法是:把 swap 当内存尖峰的保险,不当 RAM 的替代。
接下来是我在 Homelab 的实战总结:
Homelab 集群概况
4 台 N100 小主机,cgroup v2,宿主机 /swap.img 早就 swapon 起来了(swap没开的实战时还需要开下swap)。上面跑的是 k3s v1.36.5+k3s1。
NodeSwap 在 Kubernetes v1.34 已经 GA,我们来启用看看。
Swap 默认情况
kubelet 的 memorySwap.swapBehavior 默认值是 NoSwap。
这个默认值下,即使宿主机开了 swap、即使你写了 failSwapOn: false,容器 cgroup 的 memory.swap.max 依然是 0。
📝Notes:
failSwapOn: false管的是「节点有 swap 时 kubelet 要不要拒绝启动」,swapBehavior管的是「容器能不能用 swap」。两个开关,两件事。
宿主机层面的 swapon、分区、/etc/fstab,在我的 Homelab 集群上都是现成的。缺的只是 kubelet 里的那一行。
动手前先确认三件事
- cgroup v2:
stat -fc %T /sys/fs/cgroup返回cgroup2fs; - 宿主已启用 swap:
swapon --show有输出,我的 Homelab 集群是/swap.img; - k3s 版本对应的 Kubernetes 版本已经包含 NodeSwap GA(>=1.34)。
前置条件不满足,改 kubelet 配置也没意义。
LimitedSwap 到底给了哪种 Pod
上游推荐的值是 LimitedSwap。它不是一个「全部放开」的策略。
适用边界由上游确定:
- 只对 Burstable 的容器生效;
- 且要求 memory request != limit;
- Guaranteed、BestEffort 一律 NoSwap;
- 关键 Pod(
IsCriticalPod)也是 NoSwap,不看 QoS。
| Pod QoS | request vs limit | 能否用 swap |
|---|---|---|
| Burstable | request != limit | ✓ LimitedSwap |
| Burstable | request == limit | ✗ NoSwap |
| Guaranteed | request == limit | ✗ NoSwap |
| BestEffort | 都没设 | ✗ NoSwap |
| 任意 | IsCriticalPod | ✗ NoSwap |
额度按这个公式算:
containerSwapLimit = (containerMemoryRequest / nodeTotalMemory) × totalPodsSwapAvailable
request 越小,能换出去的空间越小。这是刻意的设计:swap 只用来兜住内存尖峰,不参与容量规划。
🐾 注意: 调度器不感知 swap。它看到的内存还是节点物理 RAM。不要把 swap 当成「多出来的内存」去超卖,节点迟早会抖。
落地:修改 k3s 的 kubelet config
k3s 每次启动都会重写 /var/lib/rancher/k3s/agent/etc/kubelet.conf.d/00-k3s-defaults.conf。
要覆盖默认值,就得往同一个目录里放一个排在后边的文件。
文件名必须以 .conf 结尾,按字典序加载。这里用的是 90-swap.conf。
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: false
memorySwap:
swapBehavior: LimitedSwap
failSwapOn: false 显式写进去,别依赖默认。
00-k3s-defaults.conf 不要覆盖、不要删除,那是 k3s 自己管的。
实施
重启 kubelet, 对于 k3s 就是重启 k3s 服务。
上线顺序:worker 先,master 逐台来。
每台之间确认 kubectl get nodes 全部 Ready、etcd 成员健康,再动下一台。多台 master 同时重启,etcd quorum 会短暂丢失。
整个过程不重启节点,不驱逐 Pod。
验证清单
配置写完不算完,四条命令逐条过。
- kubelet 实际生效的配置:
kubectl get --raw "/api/v1/nodes/<node>/proxy/configz" \
| jq '.kubeletconfig | {failSwapOn, memorySwap}'
期望 memorySwap.swapBehavior == "LimitedSwap"。
示例如下:
❯ kubectl get --raw "/api/v1/nodes/n100-cheshi-0/proxy/configz" | jq '.kubeletconfig | {failSwapOn, memorySwap}'
{
"failSwapOn": false,
"memorySwap": {
"swapBehavior": "LimitedSwap"
}
}
- 节点 swap 容量有上报,不是
unknown:
❯ kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.swap.capacity}{"\n"}{end}'
n100-cheshi-0 8589930496
n100-jumper-0 4294963200
n100-jumper-1 4294963200
n100-jumper-2 4294963200
4 台节点都应有值。
- swap 用量能看:
❯ kubectl top nodes --show-swap
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%) SWAP(bytes) SWAP(%)
n100-cheshi-0 671m 16% 11088Mi 35% 0Mi 0%
n100-jumper-0 853m 21% 8065Mi 53% 0Mi 0%
n100-jumper-1 759m 18% 6317Mi 41% 0Mi 0%
n100-jumper-2 466m 11% 7270Mi 47% 0Mi 0%
在最后两列 - SWAP(bytes) SWAP(%)。
- 最关键的一条,进容器看 cgroup:
kubectl exec <pod> -- cat /sys/fs/cgroup/memory.swap.max
拿两个 Pod 对比:
- request
64Mi/ limit256Mi的 Burstable Pod,memory.swap.max > 0; - request == limit 的 Pod,以及 request/limit 都没设的 Pod,
memory.swap.max == 0。
这两组数字对得上,说明边界按预期生效了。示例如下:
Burstable Pod:
cat /sys/fs/cgroup/memory.swap.max
36200448
request/limit 都没设的 Pod:
cat /sys/fs/cgroup/memory.swap.max
0
我的 homelab 集群一时间没找到 request == limit 的 Pod,这里就不展示了。
总结
- NodeSwap 在 v1.34 GA,默认
swapBehavior还是NoSwap,单独写failSwapOn: false不会让容器拿到 swap 额度; - 需要动的配置只有一处:
memorySwap.swapBehavior: LimitedSwap; - 生效范围限定在 Burstable 且 request != limit,额度按 request 占节点内存的比例分;
- k3s 走 drop-in 目录下发,
.conf后缀 + 字典序排在00-k3s-defaults.conf之后;
以上。
📚️参考文档
- Kubernetes 文档:Node swap 管理
- Kubernetes v1.34 发布说明(NodeSwap GA)
- k3s 文档
- 上游 KEP 与 kubelet 配置中
memorySwap字段定义
浙公网安备 33010602011771号