☰
Kubernetes节点重启后NotReady?swap残留问题的根治方案
2026/10/20 22:37:07 网站建设 项目流程

1. 问题现象与根因剖析

1.1 重启后的经典场景:节点突然NotReady

如果你在裸机上手工搭过Kubernetes集群,大概率经历过这样一个场景:集群一切正常,Pod跑得好好的,你顺手reboot了一下节点,然后发现kubelet起不来了,节点状态变成了NotReady,kubectl get nodes看到的全是“NotReady”或者是“SchedulingDisabled”,而系统日志里反复刷着类似这样的报错:

kubelet: Running with swap on is not supported, please disable swap

很多新手第一次看到这行日志的时候是懵的,因为明明安装的时候已经执行过swapoff -a了,怎么重启一次就不行了?其实问题很简单:swapoff -a这个操作是临时的,它只对当前运行的内核生效,一旦系统重启,只要swap分区或者swap文件还在/etc/fstab里挂着,系统就会自动重新启用swap,kubelet一见swap又回来了,直接罢工。

这个问题在Kubernetes搭建中遇到的概率非常高,尤其是那些用虚拟机、物理机自建集群的朋友,因为云厂商的镜像通常默认不带swap,或者干脆帮你关掉了,而自己装系统的时候往往会顺手分一个swap分区。本文就围绕“重启后swap干扰Kubernetes”这个具体的坑,把根因、解决方案和几种常见的变种场景一次讲透,顺便把排查思路也整理出来,当作一个速查手册。

1.2 为什么Kubernetes非要禁用swap

先解释一个很多人问过的“为什么”:为什么Kubernetes这么矫情,非得把swap关掉?

Kubernetes的调度和资源管理模型,是建立在“节点上的内存是稳定、可预测”这个前提之上的。调度器在把Pod放到某个节点的时候,会根据Pod的request来汇总计算节点的内存水位,决定是否可以继续调度新的Pod。但如果操作系统开了swap,情况就完全失控了:本应属于Pod的内存可能被换出到磁盘上,Pod实际拿到的物理内存比它申请的要少,而kubelet在统计节点压力时看的却是物理内存和swap混在一起的状态。这就导致调度器的判断完全失真,内存压力大的时候Pod可能会被反复驱逐,甚至OOM Killer杀掉的进程未必是真正超限的那一个。

打个比方,内存就好比一家咖啡馆的座位,swap相当于咖啡馆外面搭的临时棚子。你本来按店里的真实座位数排了预订,结果客人来了发现临时棚子也能坐,你以为不拥挤,实际上棚子里挤成一团,服务质量根本没法保证。Kubernetes的诉求很简单:座位数就是座位数,外面不许搭棚子,这样它才能精确控制每一个客人(Pod)的体验。

从Kubernetes的代码层面来看,kubelet在启动时会检查内核的vm.swappiness和swap启用状态,如果发现swap开着,就会直接拒绝启动。这个行为从很早就一直存在,到后来甚至成了一个硬性约束。虽然在新版本里你可以通过设置kubelet的--fail-swap-on=false来强行跳过这个检查,但这只是掩盖问题,并不是解决问题的正道,内存的不可控性依然存在。

1.3 为什么重启会让swap“复活”

理解了Kubernetes对swap的态度之后,核心问题就变成了:为什么重启后swap就回来了?

答案是/etc/fstab。Linux在启动过程中,systemd(或者传统的rc.sysinit)会读取fstab文件,把里面配置的swap分区或者swap文件全部激活。如果你在安装系统时让安装器自动生成了swap分区,那么fstab里大概率会有一条类似这样的记录:

/dev/mapper/centos-swap swap swap defaults 0 0

或者是文件型的swap:

/swapfile none swap sw 0 0

只要这条记录存在,系统无论重启多少次,都会自动执行swapon -a。你在安装阶段执行的那次swapoff -a,只是把当前的swap关掉了,并没有删除这条自启动配置。

另外一个隐藏的坑是某些云平台的初始化脚本或者systemd服务(比如cloud-init)可能会在启动阶段重新创建或挂载swap。比如某些优化脚本会自动检测内存大小并动态创建swapfile来保证系统稳定。如果遇到这种情况,光改fstab还不够,还需要找到对应的systemd unit并禁用它。

所以,重启后swap问题的根因,本质上就是“临时命令”和“持久配置”之间的错位。知道了这个错位,解决方案的思路就清晰了:要么把持久配置里的swap都删掉,要么让kubelet容忍swap的存在,要么双管齐下。

2. 解决思路与方案选型

2.1 三种主流方案对比

针对“重启后swap干扰Kubernetes”的问题,社区里比较常见的处理方式有三种,我先把它们放在一起对比,方便你根据自身的场景做选择。

方案核心操作优点缺点适用场景
方案A:彻底禁用swap临时swapoff -a+ 永久删除fstab里的swap记录完全符合K8s对节点资源的要求,最干净操作系统层面少了一块应急内存,极端内存压力下可能直接OOM生产环境、资源规划清晰的集群
方案B:保留swap但让kubelet容忍设置--fail-swap-on=false(或failSwapOn: false)无需动系统分区,对系统影响最小调度器预测会失真,Pod性能不可控,只建议测试环境本地开发、临时测试集群
方案C:swap不关,但把swappiness降到极低保留swap分区,设置vm.swappiness=0/1兼顾系统稳定性与K8s运行仍然过不了kubelet的默认检查,需要配合方案B使用内存较小的边缘节点

说白了,方案C并没有“解决”Kubernetes对swap的容忍问题,它只是让系统尽可能少用swap,相当于把临时棚子拆了一半但没拆干净,kubelet检查依然过不了,所以如果你不想关swap,最终还是要配上方案B一起用。

在我个人的实践中,生产环境一律走方案A,这是最干净、最符合Kubernetes设计哲学的路线。测试环境我反而有时候故意留着swap,再把failSwapOn设成false,模拟极端情况下的节点表现。下面我分别讲一下各个方案的实施细节。

2.2 方案A:彻底禁用swap的实施思路

方案A的完整链路是:先查看当前swap状态 -> 临时关闭swap -> 从fstab中永久移除或注释swap记录 -> 执行systemctl daemon-reload以避免systemd重新挂载 -> 重启验证。

这里有一个很多人不知道的细节:改了fstab之后,最好执行一次systemctl daemon-reload。因为现代Linux发行版的systemd会把fstab里的条目自动转换成对应的swap unit,如果你改了fstab但没daemon-reload,systemd的内存状态里可能还残留着旧的挂载定义,重启后依然可能重新激活swap。这个坑我踩过一次,当时只改fstab没reload,结果reboot后swap又回来了,折腾了半天才发现是systemd缓存作祟。

另外,注释或删除fstab里的swap记录,推荐用“注释”而不是“删除”。原因很简单:万一以后你想恢复swap,把注释去掉就行,不至于忘记原来的配置长什么样。虽然Kubernetes环境里大概率不需要恢复,但留有备份意识总是好的。

# /dev/mapper/centos-swap swap swap defaults 0 0

如果你用的是swapfile,同样注释掉对应行:

# /swapfile none swap sw 0 0

2.3 方案B:让kubelet容忍swap的部署要点

如果你由于某些原因不得不保留swap(比如物理机内存特别小、运行的应用有突发内存需求等),那么方案B是唯一能让你继续用Kubernetes的路。

kubelet关于swap的检查开关在不同版本里有不同的写法:

  • 早期版本:直接在kubelet启动参数里加--fail-swap-on=false
  • 使用kubeadm部署的集群:修改/var/lib/kubelet/config.yaml,加一行failSwapOn: false
  • 使用systemd管理kubelet的节点:在/etc/systemd/system/kubelet.service.d/10-kubeadm.conf中给KUBELET_EXTRA_ARGS追加一个--fail-swap-on=false

改完之后需要重启kubelet:

systemctl daemon-reload systemctl restart kubelet

这里要特别提醒:如果你用的是kubeadm初始化的集群,并且希望在初始化阶段就跳过swap检查,可以在kubeadm init时传入--ignore-preflight-errors=Swap。但注意这只是在初始化那一关放行,kubelet本身的检查还需要通过上述方式处理,两条路都要走通才行。

虽然方案B能让kubelet跑起来,但我必须说一句:性能方面你会有明显感知。如果节点上运行的是内存密集型应用(比如Java程序、缓存型中间件),数据被换出到磁盘之后,访问延迟会涨一个数量级。用这个方案跑生产,迟早会在某个凌晨被alert惊醒。

3. 实操过程与核心环节实现

3.1 实操前的状态确认

在动手之前,先花十几秒确认一下当前节点的swap真实状态。这一步看起来啰嗦,但能帮你判断是「临时开启」还是「fstab或systemd配置导致重启复活」。

# 查看当前swap的使用情况 free -h # 查看哪些分区或文件正在被用作swap swapon --show # 查看fstab里是否有swap条目 grep -i swap /etc/fstab # 查看systemd当前激活的swap unit systemctl list-units --type=swap

这套命令的组合拳打下来,你会拿到三份关键信息:swap的实际用量、正在生效的swap设备列表、以及系统启动时自动激活swap的配置来源。我习惯把它们截图存下来,改完之后再对比一次,处理前后一目了然。

有一种场景比较隐蔽:swapon --show没有任何输出,但free -h里明明有Swap一栏显示有空间被占用。这通常意味着swap启用了但对应设备没有被正确标识,多半是内核参数或初始化脚本在搞鬼。遇到这种情形,优先查/proc/swaps文件。

cat /proc/swaps

这个文件是内核直接输出的当前swap设备列表,比任何工具都可靠。

3.2 完整解决步骤:从临时关闭到永久生效

下面这套步骤是我在多个生产节点上验证过的“标准作业流程”,既适合一次性修复,也适合批量执行。以root身份操作,全程大概三分钟。

第一步:临时关闭所有swap
swapoff -a

执行完这一步,当前系统的swap就已经全部关闭了。可以再用swapon --show确认一下,如果没有输出就说明已经关干净了。

第二步:注释或删除fstab中的swap条目

打开/etc/fstab,找到包含swap的行,行首加#注释掉。以防万一,先备份一下:

cp /etc/fstab /etc/fstab.bak sed -i.bak '/swap/s/^/#/' /etc/fstab

注意上面这行sed命令的含义:在所有包含swap的行前加上#,同时生成一个/etc/fstab.bak备份文件。如果你系统里有其他文件路径也包含swap字样(虽然罕见),加个注释相当于把它们也一并禁了,所以更稳妥的做法是先grep -n swap /etc/fstab看看行号,手动编辑。

第三步:让systemd重新加载配置
systemctl daemon-reload

如果有任何swap unit处于active状态,可以一并停掉:

systemctl mask swap.target

关于mask swap.target这个操作,有些人觉得太激进,因为一旦mask掉,系统层面就再也无法手动启用swap了。但话说回来,既然我们决定让Kubernetes节点上不出现swap,mask掉反而是更彻底的做法,防止有人在节点上手贱执行swapon。如果以后想恢复,执行systemctl unmask swap.target即可。

第四步:验证当前状态
swapon --show free -h

如果swapon --show无输出,free -h里Swap一栏全部为0,说明当前状态符合预期。

第五步:重启验证
reboot

重启之后,重复执行swapon --show和grep -i swap /etc/fstab。只要fstab里没有未注释的swap条目,这里就不会输出任何内容。同时确认kubelet的状态:

systemctl status kubelet kubectl get nodes

我来解释一下为什么步骤顺序是“先关swap、再改fstab、再daemon-reload”:先关swap解决当前运行态,改fstab解决下次启动的自动挂载,daemon-reload解决systemd内部的unit状态残留。三步缺一不可,漏了任何一步,重启后都可能在某个环节被重新激活。

3.3 kubeadm初始化时的额外处理

如果你还没有初始化集群,是在kubeadm init阶段就遇上了swap报错,那处理起来更简单,但也别只想着加--ignore-preflight-errors=Swap糊弄过去。正确的做法是:先把swap彻底关掉(按上面的步骤),再正常执行初始化。

比如初始化命令:

kubeadm init --pod-network-cidr=10.244.0.0/16

如果swap没有完全关闭,这条命令会因为预检失败而中断,报错就是开头提到的那句经典提示。把swap处理完之后,不需要加任何跳过参数就直接能过。

我在帮朋友排查时经常发现一种情况:他用swapoff -a关了swap,但fstab没改,结果kubeadm初始化成功了,集群也在正常工作。过了几天一重启,节点挂了,他一脸懵。所以kubeadm初始化成功并不能说明swap已经永久关闭,这一点一定要在初始化前就处理好fstab,别事后再补。

3.4 容器运行时与kubelet的联动验证

很多人处理完swap之后只盯着kubelet,其实容器运行时(比如containerd)在swap没关的情况下也可能出现异常行为,尤其是内存敏感型的容器。Kubernetes节点上有两个核心组件跟内存强相关:一个是kubelet,一个是容器运行时。

当你执行完上面的swap禁用操作后,建议顺便检查一下这两个组件的状态:

systemctl status kubelet systemctl status containerd

如果kubelet因为swap被关闭而重启失败,日志里除了swap的报错,还可能出现failed to run Kubelet之类的信息,此时要先确认kubelet的启动参数里是否还残留着--fail-swap-on=false。这里有一个很容易被忽视的地方:如果你在测试环境用方案B跑了一段时间,后来决定改成方案A彻底禁swap,却忘了把配置里的failSwapOn: false改回true,那么swap虽然关了,kubelet也依然能启动,但节点实际上跑在一个“本该报错却不报错”的状态里。这个状态比较隐蔽,因为它不影响使用,只是让你误以为配置已经收敛。建议在排查的时候把配置文件打开看一眼:

cat /var/lib/kubelet/config.yaml | grep -i swap

如果这里还写着failSwapOn: false,而你已经决定彻底禁用swap,建议把这行改成true或直接删掉,让kubelet恢复默认的严格检查。

4. 常见问题与排查技巧实录

4.1 重启后swap又回来了

这是最经典的场景,百分之百是fstab或systemd的swap unit残留导致的。排查路径如下:

# 查看当前swap设备 swapon --show # 查看fstab grep -i swap /etc/fstab # 查看systemd的swap unit systemctl list-units --type=swap --all

常见的原因有三种:

  • fstab里的swap记录还在,没有被注释或删除
  • fstab改了,但systemctl daemon-reload没执行,systemd内存里的旧unit还挂着
  • 有第三方脚本(比如云平台的初始化脚本)在启动时自动创建了swapfile并挂载

排查的时候按照这三条逐个排除,基本五分钟内能定位。重点说下第二种:很多人在改完fstab后不reload,这种习惯在普通的存储挂载场景下问题不大(因为重启会重新读取fstab),但在swap场景下就会踩坑。原因是systemd在运行期间已经把fstab的条目转换成了swap.swap这个unit,你修改fstab并不会自动同步修改systemd的内部状态,必须daemon-reload强制它重新读取。

4.2 kubelet启动报错:Running with swap on is not supported

这个报错有两种触发时机:

  • 手动systemctl start kubelet时立即报错
  • 节点重启后kubelet自动启动时失败

如何处理:

# 先彻底关闭swap swapoff -a # 确保fstab里没有swap条目,有的话注释掉 # 重新加载systemd systemctl daemon-reload # 修改kubelet配置,确保failSwapOn为true或不存在 vi /var/lib/kubelet/config.yaml # 重启kubelet systemctl restart kubelet

这里要强调一下检查kubelet日志的习惯。journalctl -u kubelet -n 50能够直接看到最近几次启动失败的具体原因,比猜效率高得多。看日志时注意不是只看最后几行,而是搜关键词swap、fail、unsupported。

还有一个细节:如果你修改过/etc/systemd/system/kubelet.service.d/10-kubeadm.conf这个目录下的配置,修改完千万别忘了systemctl daemon-reload。很多人改了文件直接systemctl restart kubelet,然后发现修改没生效,其实不是文件改错了,而是systemd没重新加载文件。这是所有systemd管理服务修改配置时的通病,不光是kubelet。

4.3 swap已关闭,但节点依然是NotReady

这种情况通常发生在集群中其他节点上,你在这台节点上关掉了swap,但kubelet健康检查有其他问题。排查思路要拓宽,不要只盯着swap:

kubectl describe node <node-name>

重点关注Conditions区域,里面会告诉你节点为什么NotReady。如果是KubeletNotReady,多半是kubelet本身有问题,去看日志。如果是RuntimeNotReady,问题在container runtime。还有MemoryPressure,说明节点内存不足,可能和swap调整后系统内存水位变化有关。

Swap关闭之后内存压力上升,这在内存规划不宽裕的节点上很常见。比如原来开着4G swap,系统实际可用内存相当于物理内存+4G,现在关了swap,可用的就只有物理内存。如果Pod的request加起来超过了节点物理内存,节点就会进入MemoryPressure状态。遇到这种情况,要么减少Pod数量,要么给节点加内存,要么调整Pod的内存request。这不是配置能解决的问题,是资源总量不够了。

4.4 云服务器与虚拟化环境的特殊坑

云服务器和虚拟化环境的swap问题比物理机多一个变量:镜像模板和cloud-init。比如某些云主机的初始化脚本会在启动时根据实例内存大小动态生成swapfile,即便你在fstab里找不到任何swap条目,重启后照样会发现swap被激活了。

遇到这种情况,排查路径要转向systemd服务和脚本:

grep -ri swap /etc/systemd/system /usr/lib/systemd/system 2>/dev/null ls /var/lib/cloud/instances/ grep -ri swap /var/lib/cloud 2>/dev/null

如果是cloud-init的某个部分在创建swap,可以通过cloud-init clean清掉状态,或者直接禁用对应的cloud-init模块,具体做法因发行版而异,这里不展开。

虚拟化环境还有一个坑是“休眠恢复”。虚拟机如果执行了suspend再resume,swap状态可能会被恢复出来,这种场景就不是Linux层面能拦住的,需要从虚拟化平台设置入手,确保虚拟机不会在唤醒时重新挂载swap。

4.5 一张速查表:常见问题定位指南

下面把上面提到的所有问题浓缩成一张速查表,方便你日后直接对照处理。

现象可能原因首选排查命令快速解决办法
重启后swap出现fstab有swap条目未注释grep -i swap /etc/fstab注释条目 +systemctl daemon-reload
kubelet拒绝启动swap当前开启swapon --showswapoff -a并持久化禁用
改了配置不生效缺少daemon-reloadsystemctl daemon-reload配置文件变更后统一reload
节点NotReadykubelet或运行时异常journalctl -u kubelet -n 50定位具体报错,不局限于swap
swap关了但内存压力大Pod总量超过物理内存kubectl describe node调整Pod requests或扩容
云主机重启自动开swapcloud-init或厂商脚本grep -ri swap /etc/systemd/system找到脚本并禁掉对应服务

5. 我的实操心得与扩展建议

5.1 关于swap处理原则的个人总结

我处理过不少节点的swap问题,一个最大的体感是:Kubernetes对内存的态度是“宁缺毋假”。宁可让系统因为内存不足而OOM,也不愿意用swap换来的虚假稳定去糊弄调度器。这和很多传统运维的思维习惯是相反的——以前我们总爱给系统加swap,觉得内存不够时能把数据临时挪到磁盘保住进程不挂。但在Kubernetes场景下,这种“保护”反而会干扰调度器的正常判断,最终导致Pod分布不合理、性能忽好忽坏。

所以我的建议是:如果你是搭生产集群,从一开始就把swap问题当成前置条件来处理。不用等重启之后被坑到了再来修复,在安装操作系统时就做好规划。内存规划上宁可预留宽裕一点,也比事后调swap强。

如果你确实因为客观原因(比如机器内存只有2G、4G)不得不保留swap,那至少要把vm.swappiness调得很低。Kubernetes虽然不赞成swap,但你设了failSwapOn: false之后,它其实不会阻止系统继续使用swap,只是kubelet不报错了而已。将vm.swappiness设成1,可以让系统只在极端情况下才使用swap,尽量保证常规运行时不发生内存交换。

echo "vm.swappiness=1" >> /etc/sysctl.d/99-swap.conf sysctl --system

这种做法适合边缘计算节点、低配开发机,不适合生产环境的性能敏感型应用。

5.2 后续集群运维中的内存观测建议

Swap问题解决完,不意味着内存管护就结束了。我习惯在节点上部署好内存监控之后,还会额外看两个指标:一个是kubelet上报的node_memory_MemAvailable,一个是容器级别的container_memory_working_set_bytes。前者看节点整体水位,后者看单个Pod的实际占用。当节点可用内存持续走低时,不要急着加节点,先看看是不是有Pod的request和limit设置不合理。

另外,/sys/fs/cgroup/memory下的统计信息也值得关注,尤其是cgroup v2的memory.peak字段,能够告诉你在过去一段时间内一个Pod内存用到的最高峰值。这个值对调优非常有价值。比如某个Pod平时看起来占用500M,但memory.peak显示曾经冲到1.2G,那说明这个Pod存在明显的突发内存需求,光看平均水平会让我们低估它的真实压力。

5.3 一条小命令库,建议收藏备用

把下面这些命令整理成一个脚本档,批量排查多节点时能省不少时间。

# 查看所有节点swap状态(如果你有ssh批量工具可扩展) for node in node1 node2 node3; do echo "===== $node =====" ssh $node "free -h | grep -i swap; swapon --show; grep -i swap /etc/fstab" done

类似的批量检查思路,可以纳入你的集群巡检脚本。毕竟Swap这类问题不怕它出现,怕的是你不知道它出现在哪。把检查固化到日常运维清单里,远比事后来排查更省心。

最后再分享一个小技巧,也是我个人在多次处理swap问题后养成的习惯:每次对fstab这类系统级文件做修改时,先在文件头部留下修改注释,并同步修改日期,比如# 2024-05-xx: disable swap for kubernetes。这样做的好处是,三个月后你回头再看这个文件时,能立刻明白当初为什么这么改,排查问题时不用顺着git历史往回翻。这个习惯对任何系统关键文件的长期维护都有帮助,不局限于swap这一件事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询