K8s 最佳实践已更新:请使用 Node Swap!

概述

K8s 最佳实践已更新:

满足以下条件:

  • K8s 1.34 及以上版本
  • Node 的系统盘使用的是本地 SSD 或 NVME 高速存储
  • cgroup v2

请使用 node swap!

要启用,推荐 kubelet config 添加如下配置:

yaml 复制代码
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 里的那一行。

动手前先确认三件事

  1. cgroup v2:stat -fc %T /sys/fs/cgroup 返回 cgroup2fs;
  2. 宿主已启用 swap:swapon --show 有输出,我的 Homelab 集群是 /swap.img;
  3. k3s 版本对应的 Kubernetes 版本已经包含 NodeSwap GA(>=1.34)。

前置条件不满足,改 kubelet 配置也没意义。

LimitedSwap 到底给了哪种 Pod

上游推荐的值是 LimitedSwap。它不是一个「全部放开」的策略。

适用边界由上游确定:

  1. 只对 Burstable 的容器生效;
  2. 且要求 memory request != limit;
  3. Guaranteed、BestEffort 一律 NoSwap;
  4. 关键 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。

yaml 复制代码
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。

验证清单

配置写完不算完,四条命令逐条过。

  1. kubelet 实际生效的配置:
bash 复制代码
kubectl get --raw "/api/v1/nodes/<node>/proxy/configz" \
  | jq '.kubeletconfig | {failSwapOn, memorySwap}'

期望 memorySwap.swapBehavior == "LimitedSwap"。

示例如下:

shell 复制代码
❯ kubectl get --raw "/api/v1/nodes/n100-cheshi-0/proxy/configz" | jq '.kubeletconfig | {failSwapOn, memorySwap}'
{
  "failSwapOn": false,
  "memorySwap": {
    "swapBehavior": "LimitedSwap"
  }
}
  1. 节点 swap 容量有上报,不是 unknown:
shell 复制代码
❯ 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 台节点都应有值。

  1. swap 用量能看:
bash 复制代码
❯ 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(%)。

  1. 最关键的一条,进容器看 cgroup:
bash 复制代码
kubectl exec <pod> -- cat /sys/fs/cgroup/memory.swap.max

拿两个 Pod 对比:

  • request 64Mi / limit 256Mi 的 Burstable Pod,memory.swap.max > 0;
  • request == limit 的 Pod,以及 request/limit 都没设的 Pod,memory.swap.max == 0。

这两组数字对得上,说明边界按预期生效了。示例如下:

Burstable Pod:

shell 复制代码
cat /sys/fs/cgroup/memory.swap.max
36200448

request/limit 都没设的 Pod:

shell 复制代码
cat /sys/fs/cgroup/memory.swap.max
0

我的 homelab 集群一时间没找到 request == limit 的 Pod,这里就不展示了。

总结

  1. NodeSwap 在 v1.34 GA,默认 swapBehavior 还是 NoSwap,单独写 failSwapOn: false 不会让容器拿到 swap 额度;
  2. 需要动的配置只有一处:memorySwap.swapBehavior: LimitedSwap;
  3. 生效范围限定在 Burstable 且 request != limit,额度按 request 占节点内存的比例分;
  4. k3s 走 drop-in 目录下发,.conf 后缀 + 字典序排在 00-k3s-defaults.conf 之后;

以上。

📚️参考文档

相关推荐
励志师code3 小时前
Kubernetes 集群节点生命周期管理实战:从扩容、调度管控到证书续期
运维·docker·容器·kubernetes·gpu算力·kubelet
闲云自留地4 小时前
Kubernetes 安装保姆级:关 swap、装 containerd、init 集群、calico 一篇通
容器·贪心算法·kubernetes
easy_coder6 小时前
Kubernetes 三大探针:Startup、Liveness 与 Readiness
云原生·容器·kubernetes
暖核7 小时前
kubeadm 搭建 K8s v1.30 集群:从系统模板到 Calico 网络全流程
网络·容器·kubernetes
KIDULT°8 小时前
Kubeadm 部署 Kubernetes v1.30.2 实战:Ubuntu24.04 三节点集群 + 排坑记录
ubuntu·云原生·容器·kubernetes·k8s
abcefg_h10 小时前
多卡多机训练任务调度系统:从梯度平均到拓扑感知调度
kubernetes·大模型·分布式训练·nccl·gpu调度
szuaudi10 小时前
K8s 节点假死故障排查过程复盘
网络·容器·kubernetes
小楼昨夜又东风1261 天前
一文理清K8s PVC挂载完整流程
容器·rpc·kubernetes
小猿姐1 天前
KubeBlocks Enterprise v2.3 发布!
redis·kubernetes·aiops