目录

1. 背景

2. 现象

3. 关键概念说明

3.1 Slab

3.2 SReclaimable

3.3 SUnreclaim

3.4 kmalloc-2k

3.5 skbuff_head_cache

4. 查看 Slab 具体对象

5. 排除普通进程内存问题

6. 排除 conntrack 表打满

7. 排除 socket / TCP 连接异常

8. 排查 K8s / 网络组件噪声

9. 排查 netns / veth 残留

10. 关键突破:audit 审计链路异常

11. 确认 audit daemon pid 被其他进程占用

12. 检查内核 audit lost 日志

13. 根因分析

14. 处理过程

14.1 清空 audit 规则

14.2 确认 audit 已关闭

14.3 禁用 auditd 服务

14.4 修改 GRUB,防止下次重启复发

15. 验证

16. 为什么内存没有立即释放?

17. 结论

18. 总结


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_cachekmalloc-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 -hused 有所下降,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
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-2kskbuff_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-2kskbuff_head_cache 高,不要只想到 conntrack,还要考虑:

audit
netlink
socket
CNI
云厂商 agent
内核网络路径

第五,audit_lostkmalloc-2kskbuff_head_cache 数量接近时,要高度怀疑 audit 审计链路异常。

第六,SUnreclaim 不是普通缓存。drop_caches 通常解决不了这类问题,很多情况下只能止血,不能立即释放。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐