一次 Linux SUnreclaim Slab 异常增长排查:从 free -h 到 audit_lost
目录
11. 确认 audit daemon pid 被其他进程占用
1. 背景
最近在一台 Linux 云服务器上做日常检查时,发现系统内存占用偏高。从 free -h 看,节点总内存约 8G,已用内存长期处于较高水平,可用内存明显偏低。
一开始很容易把这个问题理解成"某个进程吃内存",但后续排查发现,真正的问题并不在用户态进程,而是在内核 Slab,尤其是不可回收的 SUnreclaim 部分。
2. 现象
首先查看系统内存:
free -h

现象大致如下:
Mem: 7.8Gi total used: 5.9Gi free: 177Mi available: 2.0Gi / 1.9Gi Swap: 0B
从表面看,系统内存使用率较高,而且没有 Swap,继续增长可能导致节点进入内存压力状态。
接着查看 /proc/meminfo:
cat /proc/meminfo | egrep -i 'MemAvailable|Slab|SReclaimable|SUnreclaim'

这里重点是:
Slab 很高 SUnreclaim 很高 SReclaimable 很低
这说明当前内存高并不是普通缓存,也不是单纯 page cache,而是内核不可回收 Slab 异常占用。
3. 关键概念说明
这里简单解释几个关键名词。
3.1 Slab
Slab 是 Linux 内核的对象缓存机制。内核频繁创建和释放各种对象,比如 socket、inode、dentry、网络包对象等。为了提高性能,Linux 会用 Slab 缓存这些对象。
可以简单理解为:
Slab = 内核自己的对象内存池
3.2 SReclaimable
SReclaimable 表示理论上可以回收的 Slab。
例如 dentry、inode 这类缓存,在系统内存紧张时可以被回收一部分。
3.3 SUnreclaim
SUnreclaim 表示不可回收或暂时不能回收的 Slab。
如果这部分特别高,就要警惕,因为它通常不是简单执行 drop_caches 就能释放的。
3.4 kmalloc-2k
kmalloc 是 Linux 内核申请内存的接口。kmalloc-2k 表示内核里大量 2KB 大小对象的缓存池。
如果 kmalloc-2k 很高,说明内核中有大量 2KB 对象被分配且未释放。
3.5 skbuff_head_cache
skbuff 是 Linux 内核网络包和内核消息传递中的核心结构。
网络包、netlink 消息、部分内核到用户态的通信都可能涉及 skbuff。
本次案例中,skbuff_head_cache 和 kmalloc-2k 同时异常增大,是后续定位 audit 问题的关键线索。
4. 查看 Slab 具体对象
继续使用 slabtop 查看 Slab 大头:
slabtop -o | head -20

异常对象主要集中在:
kmalloc-2k skbuff_head_cache
示例:
kmalloc-2k 约 3.xG skbuff_head_cache 约 xxxM
这说明内核中大量对象集中在:
通用 kmalloc 分配对象 网络 / netlink / skbuff 相关对象
此时排查方向不应该继续只盯着用户态进程,而应该转向:
conntrack socket 网络组件 CNI / Flannel / WireGuard audit 云厂商 agent 内核日志
5. 排除普通进程内存问题
先检查是否存在明显大进程。
ps aux --sort=-%mem | head

也可以结合具体服务:
systemctl status <service>
本次排查中,发现某个服务占用约 1G 用户态内存。停止该服务后,free -h 中 used 有所下降,available 有所回升。
但是再次查看 Slab:
cat /proc/meminfo | egrep -i 'MemAvailable|Slab|SReclaimable|SUnreclaim' slabtop -o | head -20
发现:
Slab / SUnreclaim 仍然很高 kmalloc-2k / skbuff_head_cache 仍然很高
因此判断:
用户态大进程不是本次 Slab 异常的根因
6. 排除 conntrack 表打满
因为 skbuff_head_cache 和网络路径相关,所以先检查 conntrack。
cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max conntrack -S

如果是 conntrack 打满,通常可能看到:
nf_conntrack_count 接近 nf_conntrack_max insert_failed 增长 drop 增长 early_drop 增长
本次结果中:
nf_conntrack_count 很低 nf_conntrack_max 很高 insert_failed=0 drop=0 early_drop=0
结论:
排除 conntrack 表打满
7. 排除 socket / TCP 连接异常
继续查看 socket 状态:
ss -s cat /proc/net/sockstat cat /proc/net/sockstat6

重点看:
sockets used TCP inuse orphan timewait TCP mem
本次结果中:
socket 总量不高 TCP established 数量正常 orphan 为 0 timewait 不多
结论:
排除 socket 数量异常、orphan socket 堆积、timewait 爆炸
8. 排查 K8s / 网络组件噪声
由于节点上运行 K8s,同时存在 CNI、Flannel、WireGuard 等网络组件,因此继续检查网络接口和 Pod 状态。
查看接口统计:
ip -s link show ip -s link show wg0 2>/dev/null ip -s link show flannel.1 2>/dev/null ip -s link show cni0 2>/dev/null

查看 Pod:
kubectl get pod -A -o wide --field-selector spec.nodeName=<node-name> kubectl get pod -A | egrep -i 'CrashLoopBackOff|Error|Unknown|ContainerCreating|ImagePullBackOff|Evicted' || true

本次发现存在一个长期 ImagePullBackOff 的 Pod。它会制造 kubelet/containerd 拉镜像失败、网络请求、事件日志等噪声。
处理方式:
root@master02:~# kubectl -n ingress-nginx scale deployment ingress-nginx-controller --replicas=0 deployment.apps/ingress-nginx-controller scaled
root@master02:~# kubectl -n ingress-nginx get deployments.apps NAME READY UP-TO-DATE AVAILABLE AGE ingress-nginx-controller 0/0 0 0 21d root@master02:~#
处理后,噪声被清理,但 Slab 异常并未立即消失。
结论:
异常 Pod 是噪声,不是根因
9. 排查 netns / veth 残留
继续查看网络命名空间和 veth 数量:
lsns -t net -o NS,TYPE,NPROCS,PID,COMMAND ip -o link | egrep -i 'veth|cni|flannel|wg0'

结果显示:
netns 数量与当前 Pod 基本匹配 veth 数量未明显异常
结论:
排除大量残留 netns / veth 导致的内核对象堆积
10. 关键突破:audit 审计链路异常
前面排除了用户态进程、conntrack、socket、K8s Pod 噪声后,问题仍集中在:
kmalloc-2k skbuff_head_cache
这时开始检查 audit 审计链路。
执行:
auditctl -s

发现关键异常:
enabled 0 pid <某个非 auditd 进程 PID> rate_limit 512 backlog_limit 16384 lost 190xxxx backlog 0
重点是:
lost 约 190 万 pid 不是 auditd
同时,slabtop 中:
kmalloc-2k 对象数约 190 万 skbuff_head_cache 对象数约 190 万
这三个数字高度接近:
audit lost ≈ 190 万 kmalloc-2k 对象数 ≈ 190 万 skbuff_head_cache 对象数 ≈ 190 万
这是本次定位的关键。
11. 确认 audit daemon pid 被其他进程占用
继续查看 audit pid 对应进程:
ps -fp <pid> readlink -f /proc/<pid>/exe cat /proc/<pid>/cmdline | tr '\0' ' '

发现该 PID 对应某云厂商主机监控 / 安全 Agent 进程,而不是正常的 auditd。
再检查 auditd:
systemctl status auditd.service --no-pager -l journalctl -u auditd --since "5 days ago" --no-pager | tail -20

发现 auditd 启动失败,关键报错类似:
Error setting audit daemon pid Permission denied
说明:
auditd 想注册为 audit daemon 但 audit daemon pid 已经被其他进程占用 所以 auditd 启动失败
12. 检查内核 audit lost 日志
执行:
dmesg -T | egrep -i 'audit|kaudit|backlog|rate limit|lost' | tail -30 journalctl -k --since "5 days ago" | egrep -i 'audit|kaudit|backlog|rate limit|lost' | tail -30

可以看到内核持续输出:
audit: audit_lost=xxxxxxx audit_rate_limit=512 audit_backlog_limit=16384
说明:
audit 事件持续丢失 audit 链路长期异常
13. 根因分析
本次问题的根因可以概括为:
Linux audit 审计链路异常导致内核不可回收 Slab 持续增长
具体链路:
audit 被启用
↓
某云厂商 Agent 占用 audit daemon pid
↓
正常 auditd 无法启动
↓
内核 audit 事件无法被正常消费
↓
audit_lost 持续增长
↓
audit 事件相关的 skbuff / kmalloc 对象大量累计
↓
kmalloc-2k 与 skbuff_head_cache 对象数均达到百万级
↓
SUnreclaim Slab 增长到 4G+
↓
表现为系统 used 内存偏高、MemAvailable 下降
14. 处理过程
14.1 清空 audit 规则
auditctl -D auditctl -l

期望结果:
No rules
14.2 确认 audit 已关闭
auditctl -s

关注:
enabled 0 lost 不再增长 backlog 0
14.3 禁用 auditd 服务
systemctl disable --now auditd.service systemctl mask auditd.service systemctl status auditd.service --no-pager -l

确认:
Loaded: masked
14.4 修改 GRUB,防止下次重启复发
备份:
cp /etc/default/grub /etc/default/grub.bak.$(date +%F_%H%M)
加入 audit=0:
grep -q 'audit=0' /etc/default/grub || sed -i '/^GRUB_CMDLINE_LINUX="/ s/cgroup.memory=nokmem /cgroup.memory=nokmem audit=0 /' /etc/default/grub
确认:
grep '^GRUB_CMDLINE_LINUX' /etc/default/grub

期望看到:
audit=0
更新 grub:
update-grub
验证:
grep -n 'audit=0' /boot/grub/grub.cfg | head
如果能看到启动项中包含 audit=0,说明下次重启后内核启动阶段会禁用 audit。
15. 验证
处理后持续观察:
while true; do
echo "===== $(date '+%F %T') ====="
auditctl -s | egrep 'enabled|pid|lost|backlog|rate_limit'
grep -E 'MemAvailable|Slab|SReclaimable|SUnreclaim' /proc/meminfo
awk '/kmalloc-2k|skbuff_head_cache/ {print}' /proc/slabinfo
echo
sleep 60
done

重点观察:
lost 是否继续增长 backlog 是否为 0 SUnreclaim 是否继续增长 kmalloc-2k 是否继续增长 skbuff_head_cache 是否继续增长
本次处理后,audit 当前已关闭,audit_lost 未继续增长,backlog 为 0,SUnreclaim、kmalloc-2k、skbuff_head_cache 在观察期内基本稳定,说明 audit 相关异常增长源头已被止住。
16. 为什么内存没有立即释放?
这是本次排查中容易误解的一点。
关闭 audit 后,SUnreclaim 并不会立刻下降。
原因是:
auditctl -D / auditctl -e 0 / audit=0 只能阻止后续继续产生问题 不能强制释放已经分配出去的内核不可回收 Slab
SUnreclaim 属于内核认为暂时不能安全回收的对象。Linux 不会随意释放这类对象,否则可能引发内核异常、网络异常甚至系统崩溃。
因此:
关闭 audit = 关水龙头 释放历史 SUnreclaim = 通常需要重启
本次由于不希望立即重启,因此采取:
先止血 后续维护窗口再重启释放历史 Slab
17. 结论
本次 Linux 节点内存偏高的根因不是普通进程内存泄漏,也不是 conntrack、socket、K8s Pod 数量异常。
真正原因是:
audit 审计链路异常导致内核 skbuff / kmalloc 对象大量累计,最终引发 SUnreclaim Slab 异常增长
关键证据:
Slab 超过 4G SUnreclaim 超过 4G kmalloc-2k 约 190 万对象 skbuff_head_cache 约 190 万对象 audit_lost 约 190 万 audit daemon pid 被非 auditd 进程占用 auditd 启动失败
处理结果:
audit 规则已清空 audit 当前已关闭 auditd 已禁用并 mask GRUB 已加入 audit=0 audit_lost 不再增长 SUnreclaim 不再明显增长
遗留问题:
历史累计的 SUnreclaim 无法在不重启的情况下可靠释放
后续计划:
短期:继续观察节点内存和 Slab 是否稳定 中期:保持 audit 禁用,避免复发 长期:维护窗口重启节点,释放历史 Slab
18. 总结
本次问题是一次由 Linux audit 审计链路异常引发的内核 Slab 排障案例:由于 audit daemon pid 被非 auditd 进程占用,导致正常 auditd 无法消费审计事件,audit_lost 持续增长,并引发 kmalloc-2k 与 skbuff_head_cache 大量累计,最终造成 4G+ SUnreclaim 内存占用。通过清空 audit 规则、关闭 audit、mask auditd、GRUB 增加 audit=0 完成止血与防复发,历史 Slab 占用等待后续重启释放。
这次排查有几个经验点:
第一,free -h 只能看到表象。看到 used 高,不要立刻认定是进程吃内存。
第二,遇到内存异常,要看 /proc/meminfo:
cat /proc/meminfo | egrep -i 'MemAvailable|Slab|SReclaimable|SUnreclaim'
第三,SUnreclaim 高时,要继续看 slabtop:
slabtop -o | head -20
第四,看到 kmalloc-2k 和 skbuff_head_cache 高,不要只想到 conntrack,还要考虑:
audit netlink socket CNI 云厂商 agent 内核网络路径
第五,audit_lost、kmalloc-2k、skbuff_head_cache 数量接近时,要高度怀疑 audit 审计链路异常。
第六,SUnreclaim 不是普通缓存。drop_caches 通常解决不了这类问题,很多情况下只能止血,不能立即释放。
更多推荐

所有评论(0)