1、节点 Crash 与 Vmcore 分析
kdump 介绍
目前大多 Linux 发新版都会默认开启 kdump 服务,以方便在内核崩溃的时候, 可以通过 kdump 服务提供的 kexec 机制快速的启用保留在内存中的第二个内核来收集并转储内核崩溃的日志信息(vmcore
等文件), 这种机制需要服务器硬件特性的支持, 不过现今常用的服务器系列均已支持.
如果没有特别配置 kdump,当发生 crash 时,通常默认会将 vmcore 保存到 /var/crash
路径下,也可以查看 /etc/kdump.conf
配置来确认:
$ grep ^path /etc/kdump.conf
path /var/crash
参考:kdump详解-CSDN博客
快速查看原因
在需要快速了解崩溃原因的时候, 可以简单查看崩溃主机(如果重启成功)的 vmcore-dmesg.txt
文件, 该文件列出了内核崩溃时的堆栈信息, 有助于我们大致了解崩溃的原因, 方便处理措施的决断. 如下所示为生成的日志文件通常的路径:
/var/crash/127.0.0.1-2019-11-11-08:40:08/vmcore-dmesg.txt
2、节点高负载
1)如何判断节点高负载?
可以通过 top
或 uptime
命令行来确定 load 大小。load 负载判定规则:
- load负载值 <= 0.7 * CPU 核数:低负载
- 0.7 * CPU 核数 < load负载值 <= CPU 核数:负载存在压力
- load负载值 > CPU 核数: 高负载
2)排查思路
观察监控:通常不是因为内核 bug 导致的高负载,在系统卡死之前从操作系统监控一般能看出一些问题,可以观察下系统各项监控指标与load监控指标的变化趋势,观察是否存在与load监控指标相同变化趋势的指标。
排查现场:如果没有相关监控或监控维度较少不足以查出问题,尝试登录节点分析现场。
注:有时负载过高通常使用 ssh 登录不上,如果可以用 vnc,可以尝试下使用 vnc 登录。
3)排查现场思路
load avg 可以认为是 R状态线程数和D状态线程数的总和 (R 代表需要 cpu,是 cpu 负载。 D 通常代表需要 IO,是 IO 负载),因此一般导致load负载高基本是CPU或者IO。
简单判断办法:
ps -eL -o lwp,pid,ppid,state,comm | grep -E " R | D "
然后统计一下各种状态多少个进程,看看是 D 多还是 R多。
如果是长时间 D多,可以进一步查看进程堆栈看看 D 多的原因是什么,如果是存在大量写请求,可以考虑业务请求数据量调整批次大小,多批次小数据量写入,同时优化磁盘落盘的频率。
cat /proc/<PID>/stack
如果是大量进程/线程在 R 状态,那就是同时需要 CPU 的进程/线程数过多,CPU 忙不过来了,可以利用 perf 分析程序在忙什么,但此时可以考虑业务扩容或者机器调大CPU核数。
perf -p <PID>
4)线程数量过多
如果 load 高但 CPU 利用率不高,通常是同时 running 的进程/线程数过多,排队等 CPU 切换的进程/线程较多。
通常在 load 高时执行任何命令都会非常卡,因为执行这些命令也都意味着要创建和执行新的进程,所以下面排查过程中执行命令时需要耐心等待。
看系统中可创建的进程数实际值:
cat /proc/sys/kernel/pid_max
修改方式: sysctl -w kernel.pid_max=65535
通过以下命令统计当前 PID 数量:
ps -eLf | wc -l
如果数量过多,可以大致扫下有哪些进程,如果有大量重复启动命令的进程,就可能是这个进程对应程序的 bug 导致。
还可以通过以下命令统计线程数排名:
printf "NUM\tPID\tCOMMAND\n" && ps -eLf | awk '{$1=null;$3=null;$4=null;$5=null;$6=null;$7=null;$8=null;$9=null;print}' | sort |uniq -c |sort -rn | head -10
找出线程数量较多的进程,可能就是某个容器的线程泄漏,导致 PID 耗尽。
随便取其中一个 PID,用 nsenter 进入进程 netns:
nsenter -n --target <PID>
然后执行 ip a
看下 IP 地址,如果不是节点 IP,通常就是 Pod IP,可以通过 kubectl get pod -o wide -A | grep <IP>
来反查进程来自哪个 Pod。
5)陷入内核态过久
有些时候某些 CPU 可能会执行耗时较长的内核态任务,比如大量创建/销毁进程,回收内存,需要较长时间 reclaim memory,必须要执行完才能切回用户态,虽然内核一般会有 migration 内核线程将这种负载较高的核上的任务迁移到其它核上,但也只能适当缓解,如果这种任务较多,整体的 CPU system 占用就会较高,影响到用户态进程任务的执行,对于业务来说,就是 CPU 不够用,处理就变慢,发生超时。
CPU 内核态占用的 Prometheus 查询语句:
sum(irate(node_cpu_seconds_total{instance="10.10.1.14",mode="system"}[2m]
6)IO 高负载
系统如果出现 IO WAIT 高,说明 IO 设备的速度跟不上 CPU 的处理速度,CPU 需要在那里干等,这里的等待实际也占用了 CPU 时间,导致系统负载升高,可能就会影响业务进程的处理速度,导致业务超时。
如何判断IO处于高负载?
使用 top
命令看下当前负载:
top - 19:42:06 up 23:59, 2 users, load average: 34.64, 35.80, 35.76
Tasks: 679 total, 1 running, 678 sleeping, 0 stopped, 0 zombie
Cpu(s): 15.6%us, 1.7%sy, 0.0%ni, 74.7%id, 7.9%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 32865032k total, 30989168k used, 1875864k free, 370748k buffers
Swap: 8388604k total, 5440k used, 8383164k free, 7982424k cachedPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND9783 mysql 20 0 17.3g 16g 8104 S 186.9 52.3 3752:33 mysqld5700 nginx 20 0 1330m 66m 9496 S 8.9 0.2 0:20.82 php-fpm6424 nginx 20 0 1330m 65m 8372 S 8.3 0.2 0:04.97 php-fpm
%wa
(wait) 表示 IO WAIT 的 cpu 占用,默认看到的是所有核的平均值,要看每个核的 %wa
值需要按下 "1":
top - 19:42:08 up 23:59, 2 users, load average: 34.64, 35.80, 35.76
Tasks: 679 total, 1 running, 678 sleeping, 0 stopped, 0 zombie
Cpu0 : 29.5%us, 3.7%sy, 0.0%ni, 48.7%id, 17.9%wa, 0.0%hi, 0.1%si, 0.0%st
Cpu1 : 29.3%us, 3.7%sy, 0.0%ni, 48.9%id, 17.9%wa, 0.0%hi, 0.1%si, 0.0%st
Cpu2 : 26.1%us, 3.1%sy, 0.0%ni, 64.4%id, 6.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu3 : 25.9%us, 3.1%sy, 0.0%ni, 65.5%id, 5.4%wa, 0.0%hi, 0.1%si, 0.0%st
Cpu4 : 24.9%us, 3.0%sy, 0.0%ni, 66.8%id, 5.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu5 : 24.9%us, 2.9%sy, 0.0%ni, 67.0%id, 4.8%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu6 : 24.2%us, 2.7%sy, 0.0%ni, 68.3%id, 4.5%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu7 : 24.3%us, 2.6%sy, 0.0%ni, 68.5%id, 4.2%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu8 : 23.8%us, 2.6%sy, 0.0%ni, 69.2%id, 4.1%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu9 : 23.9%us, 2.5%sy, 0.0%ni, 69.3%id, 4.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu10 : 23.3%us, 2.4%sy, 0.0%ni, 68.7%id, 5.6%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu11 : 23.3%us, 2.4%sy, 0.0%ni, 69.2%id, 5.1%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu12 : 21.8%us, 2.4%sy, 0.0%ni, 60.2%id, 15.5%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu13 : 21.9%us, 2.4%sy, 0.0%ni, 60.6%id, 15.2%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu14 : 21.4%us, 2.3%sy, 0.0%ni, 72.6%id, 3.7%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu15 : 21.5%us, 2.2%sy, 0.0%ni, 73.2%id, 3.1%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu16 : 21.2%us, 2.2%sy, 0.0%ni, 73.6%id, 3.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu17 : 21.2%us, 2.1%sy, 0.0%ni, 73.8%id, 2.8%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu18 : 20.9%us, 2.1%sy, 0.0%ni, 74.1%id, 2.9%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu19 : 21.0%us, 2.1%sy, 0.0%ni, 74.4%id, 2.5%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu20 : 20.7%us, 2.0%sy, 0.0%ni, 73.8%id, 3.4%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu21 : 20.8%us, 2.0%sy, 0.0%ni, 73.9%id, 3.2%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu22 : 20.8%us, 2.0%sy, 0.0%ni, 74.4%id, 2.8%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu23 : 20.8%us, 1.9%sy, 0.0%ni, 74.4%id, 2.8%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 32865032k total, 30209248k used, 2655784k free, 370748k buffers
Swap: 8388604k total, 5440k used, 8383164k free, 7986552k cached
wa
通常是 0%,如果经常在 1% 之上,说明存储设备的速度已经太慢,无法跟上 cpu 的处理速度。
IO高负载如何排查?
使用 iostat 检查设备是否 hang 住?
iostat -xhd 2
如果有 100% 的 %util
的设备,说明该设备基本 hang 住了
观察高 IO 的磁盘读写情况
# 捕获 %util 超过 90 时 vdb 盘的读写指标,每秒检查一次
while true; do iostat -xhd | grep -A1 vdb | grep -v vdb | awk '{if ($NF > 90){print $0}}'; sleep 1s; done
如果读写流量或 IOPS 不高,但 %util
不高,通常是磁盘本身有问题了,需要检查下磁盘。 在云上托管的 k8s 集群通常就使用的云厂商的云盘(比如腾讯云CBS),可以拿到磁盘 ID 反馈下。
如果读写流量或 IOPS 高,继续下面的步骤排查出哪些进程导致的 IO 高负载。
查看哪些进程占住磁盘
fuser -v -m /dev/vdb
查找 D 状态的进程
D 状态 (Disk Sleep) 表示进程正在等待 IO,不可中断,正常情况下不会保持太久,如果进程长时间处于 D 状态,通常是设备故障
ps -eo pid,ppid,stat,command## 捕获 D 状态的进程
while true; do ps -eo pid,ppid,stat,command | awk '{if ($3 ~ /D/) {print $0}}'; sleep 0.5s; done
观察高 IO 进程
iotop -oP
# 展示 I/O 统计,每秒更新一次
pidstat -d 1
# 只看某个进程
pidstat -d 1 -p 3394470
使用 pidstat 统计
timeout 10 pidstat -dl 3 > io.txt
cat io.txt | awk '{if ($6>2000||$5>2000)print $0}'
使用 ebpf 抓高 IOPS 进程
安装 bcc-tools:
yum install -y bcc-tools
分析:
$ cd /usr/share/bcc/tools
$ ./biosnoop 5 > io.txt
$ cat io.txt | awk '{print $3,$2,$4,$5}' | sort | uniq -c | sort -rn | head -106850 3356537 containerd vdb R1294 3926934 containerd vdb R864 1670 xfsaild/vdb vdb W578 3953662 kworker/u180:1 vda W496 3540267 logsys_cfg_cli vdb R459 1670 xfsaild/vdb vdb R354 3285936 php-fpm vdb R340 3285934 php-fpm vdb R292 2952592 sap1001 vdb R273 324710 python vdb R
$ pstree -apnhs 3356537
systemd,1 --switched-root --system --deserialize 22└─containerd,3895└─{containerd},3356537
$ timeout 10 strace -fp 3895 > strace.txt 2>&1
# vdb 的 IOPS 高,vdb 挂载到了 /data 目录,这里过滤下 "/data"
$ grep "/data" strace.txt | tail -10
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2338.log", {st_mode=S_IFREG|0644, st_size=6509, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2339.log", {st_mode=S_IFREG|0644, st_size=6402, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2340.log", {st_mode=S_IFREG|0644, st_size=6509, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2341.log", {st_mode=S_IFREG|0644, st_size=6509, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2342.log", {st_mode=S_IFREG|0644, st_size=6970, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2343.log", {st_mode=S_IFREG|0644, st_size=6509, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2344.log", {st_mode=S_IFREG|0644, st_size=6402, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2345.log", <unfinished ...>
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2346.log", {st_mode=S_IFREG|0644, st_size=7756, ...}, AT_SYMLINK_NOFOLLOW) = 0
[pid 19562] newfstatat(AT_FDCWD, "/data/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6974/fs/data/log/monitor/snaps/20211010/ps-2347.log", Process 3895 detached
$ grep "/data" strace.txt > data.txt
# 合并且排序,自行用脚本分析下哪些文件操作多
$ cat data.txt | awk -F '"' '{print $2}' | sort | uniq -c | sort -n > data-sorted.txt
7)如果负载太高导致机器完全无法操作怎么办?
此情况下建议直接重启。
3、磁盘爆满
1)什么情况下磁盘可能会爆满 ?
kubelet 有 gc 和驱逐机制,通过以下参数:
--image-gc-high-threshold
--image-gc-low-threshold
--eviction-hard
,--eviction-soft
--eviction-minimum-reclaim
控制 kubelet 的 gc 和驱逐策略来释放磁盘空间,如果配置正确的情况下,磁盘一般不会爆满。
通常导致爆满的原因可能是配置不正确或者节点上有其它非 K8S 管理的进程在不断写数据到磁盘占用大量空间导致磁盘爆满。
2)磁盘爆满会有什么影响 ?
影响 K8S 运行我们主要关注 kubelet 和容器运行时这两个最关键的组件,它们所使用的目录通常不一样:
- kubelet 一般不会单独挂盘,直接使用系统磁盘,因为通常占用空间不会很大;
- 容器运行时单独挂盘的场景比较多,一般情况下会单独挂盘。
当磁盘爆满的时候我们也要看 kubelet 和 容器运行时使用的目录是否在这个磁盘,通过 df
命令可以查看磁盘挂载点。
3)容器运行时使用的目录所在磁盘爆满
如果容器运行时使用的目录所在磁盘空间爆满,可能会造成容器运行时无响应,比如 docker,执行 docker 相关的命令一直 hang 住, kubelet 日志也可以看到 PLEG unhealthy,因为 CRI 调用 timeout,当然也就无法创建或销毁容器,通常表现是 Pod 一直 ContainerCreating 或 一直 Terminating。
docker 默认使用的目录主要有:
/var/run/docker
: 用于存储容器运行状态,通过 dockerd 的--exec-root
参数指定。/var/lib/docker
: 用于持久化容器相关的数据,比如容器镜像、容器可写层数据、容器标准日志输出、通过 docker 创建的 volume 等
Pod 启动可能报类似下面的事件:
Warning FailedCreatePodSandBox 53m kubelet, 172.22.0.44 Failed create pod sandbox: rpc error: code = DeadlineExceeded desc = context deadline exceeded
Warning FailedCreatePodSandBox 2m (x4307 over 16h) kubelet, 10.179.80.31 (combined from similar events): Failed create pod sandbox: rpc error: code = Unknown desc = failed to create a sandbox for pod "apigateway-6dc48bf8b6-l8xrw": Error response from daemon: mkdir /var/lib/docker/aufs/mnt/1f09d6c1c9f24e8daaea5bf33a4230de7dbc758e3b22785e8ee21e3e3d921214-init: no space left on device
Warning Failed 5m1s (x3397 over 17h) kubelet, ip-10-0-151-35.us-west-2.compute.internal (combined from similar events): Error: container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"rootfs_linux.go:58: mounting \\\"/sys\\\" to rootfs \\\"/var/lib/dockerd/storage/overlay/051e985771cc69f3f699895a1dada9ef6483e912b46a99e004af7bb4852183eb/merged\\\" at \\\"/var/lib/dockerd/storage/overlay/051e985771cc69f3f699895a1dada9ef6483e912b46a99e004af7bb4852183eb/merged/sys\\\" caused \\\"no space left on device\\\"\""
Pod 删除可能报类似下面的事件:
Normal Killing 39s (x735 over 15h) kubelet, 10.179.80.31 Killing container with id docker://apigateway:Need to kill Pod
4)kubelet 使用的目录所在磁盘爆满
如果 kubelet 使用的目录所在磁盘空间爆满(通常是系统盘),新建 Pod 时连 Sandbox 都无法创建成功,因为 mkdir 将会失败,通常会有类似这样的 Pod 事件:
Warning UnexpectedAdmissionError 44m kubelet, 172.22.0.44 Update plugin resources failed due to failed to write checkpoint file "kubelet_internal_checkpoint": write /var/lib/kubelet/device-plugins/.728425055: no space left on device, which is unexpected.
kubelet 默认使用的目录是 /var/lib/kubelet
, 用于存储插件信息、Pod 相关的状态以及挂载的 volume (比如 emptyDir
, ConfigMap
, Secret
),通过 kubelet 的 --root-dir
参数指定。
5)如何分析磁盘占用 ?
如果运行时使用的是 Docker,请参考:分析 Docker 磁盘占用-CSDN博客
6)如何恢复 ?
如果容器运行时使用的 Docker,我们无法直接重启 dockerd 来释放一些空间,因为磁盘爆满后 dockerd 无法正常响应,停止的时候也会卡住。我们需要先手动清理一点文件腾出空间好让 dockerd 能够停止并重启。
可以手动删除一些 docker 的 log 文件或可写层文件,通常删除 log:
$ cd /var/lib/docker/containers
$ du -sh * # 找到比较大的目录
$ cd dda02c9a7491fa797ab730c1568ba06cba74cecd4e4a82e9d90d00fa11de743c
$ cat /dev/null > dda02c9a7491fa797ab730c1568ba06cba74cecd4e4a82e9d90d00fa11de743c-json.log.9 # 删除log文件
注意: 使用
cat /dev/null >
方式删除而不用rm
,因为用 rm 删除的文件,docker 进程可能不会释放文件,空间也就不会释放;log 的后缀数字越大表示越久远,先删除旧日志。
然后将该 node 标记不可调度,并将其已有的 pod 驱逐到其它节点,这样重启 dockerd 就会让该节点的 pod 对应的容器删掉,容器相关的日志(标准输出)与容器内产生的数据文件(没有挂载 volume, 可写层)也会被清理:
kubectl drain <node-name>
重启 dockerd:
systemctl restart dockerd
# or systemctl restart docker
等重启恢复,pod 调度到其它节点,排查磁盘爆满原因并清理和规避,然后取消节点不可调度标记:
kubectl uncordon <node-name>
7)如何规避 ?
正确配置 kubelet gc 和 驱逐相关的参数,即便到达爆满地步,此时节点上的 pod 也都早就自动驱逐到其它节点了,不会存在 Pod 一直 ContainerCreating 或 Terminating 的问题。
4、PID 爆满
1)如何判断 PID 耗尽
首先要确认当前的 PID 限制,检查全局 PID 最大限制:
cat /proc/sys/kernel/pid_max
也检查下线程数限制:
cat /proc/sys/kernel/threads-max
再检查下当前用户是否还有 ulimit
限制最大进程数。
确认当前实际 PID 数量,检查当前用户的 PID 数量:
ps -eLf | wc -l
如果发现实际 PID 数量接近最大限制说明 PID 就可能会爆满导致经常有进程无法启动,低版本内核可能报错: Cannot allocate memory
,这个报错信息不准确,在内核 4.1 以后改进了: fork: report pid reservation failure properly · torvalds/linux@35f71bc · GitHub
2)如何解决
临时调大 PID 和线程数限制:
echo 65535 > /proc/sys/kernel/pid_max
echo 65535 > /proc/sys/kernel/threads-max
永久调大 PID 和线程数限制:
echo "kernel.pid_max=65535 " >> /etc/sysctl.conf && sysctl -p
echo "kernel.threads-max=65535 " >> /etc/sysctl.conf && sysctl -p
k8s 1.14 支持了限制 Pod 的进程数量: Process ID Limiting for Stability Improvements in Kubernetes 1.14 | Kubernetes
参考:如何限制pod 进程/线程数量?-CSDN博客
5、判断 arp_cache 是否溢出
node 内核日志会有有下面的报错:
arp_cache: neighbor table overflow!
查看当前 arp 记录数:
$ arp -an | wc -l
1335
查看 arp gc 阀值:
$ sysctl -a | grep gc_thresh
net.ipv4.neigh.default.gc_thresh1 = 128
net.ipv4.neigh.default.gc_thresh2 = 512
net.ipv4.neigh.default.gc_thresh3 = 1024
net.ipv6.neigh.default.gc_thresh1 = 128
net.ipv6.neigh.default.gc_thresh2 = 512
net.ipv6.neigh.default.gc_thresh3 = 1024
当前 arp 记录数接近 gc_thresh3
比较容易 overflow,因为当 arp 记录达到 gc_thresh3
时会强制触发 gc 清理,当这时又有数据包要发送,并且根据目的 IP 在 arp cache 中没找到 mac 地址,这时会判断当前 arp cache 记录数加 1 是否大于 gc_thresh3
,如果没有大于就会 时就会报错: arp_cache: neighbor table overflow!
解决方案
调整节点内核参数,将 arp cache 的 gc 阀值调高 (/etc/sysctl.conf
):
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000
分析是否只是部分业务的 Pod 的使用场景需要节点有比较大的 arp 缓存空间。
如果不是,就需要调整所有节点内核参数。
如果是,可以将部分 Node 打上标签,比如:
kubectl label node host1 arp_cache=large
然后用 nodeSelector 或 nodeAffnity 让这部分需要内核有大 arp_cache 容量的 Pod 只调度到这部分节点,推荐使用 nodeAffnity,yaml 示例:
template:spec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: arp_cacheoperator: Invalues:- large
参考:K8S node ARP 表爆满 如何优化-CSDN博客
6、inotify 资源耗尽
inotify详解参考:linux inotify 资源详解-CSDN博客
1)inotify 耗尽的危害
如果 inotify 资源耗尽,kubelet 创建容器将会失败:
Failed to watch directory "/sys/fs/cgroup/blkio/system.slice": inotify_add_watch /sys/fs/cgroup/blkio/system.slice/var-lib-kubelet-pods-d111600d\x2dcdf2\x2d11e7\x2d8e6b\x2dfa163ebb68b9-volumes-kubernetes.io\x7esecret-etcd\x2dcerts.mount: no space left on device
2)查看 inotify watch 的限制
每个 linux 进程可以持有多个 fd,每个 inotify 类型的 fd 可以 watch 多个目录,每个用户下所有进程 inotify 类型的 fd 可以 watch 的总目录数有个最大限制,这个限制可以通过内核参数配置: fs.inotify.max_user_watches
。
查看最大 inotify watch 数:
$ cat /proc/sys/fs/inotify/max_user_watches
8192
3)查看进程的 inotify watch 情况
使用下面的脚本查看当前有 inotify watch 类型 fd 的进程以及每个 fd watch 的目录数量,降序输出,带总数统计:
#!/usr/bin/env bash
#
# Copyright 2019 (c) roc
#
# This script shows processes holding the inotify fd, alone with HOW MANY directories each inotify fd watches(0 will be ignored).
total=0
result="EXE PID FD-INFO INOTIFY-WATCHES\n"
while read pid fd; do \exe="$(readlink -f /proc/$pid/exe || echo n/a)"; \fdinfo="/proc/$pid/fdinfo/$fd" ; \count="$(grep -c inotify "$fdinfo" || true)"; \if [ $((count)) != 0 ]; thentotal=$((total+count)); \result+="$exe $pid $fdinfo $count\n"; \fi
done <<< "$(lsof +c 0 -n -P -u root|awk '/inotify$/ { gsub(/[urw]$/,"",$4); print $2" "$4 }')" && echo "total $total inotify watches" && result="$(echo -e $result|column -t)\n" && echo -e "$result" | head -1 && echo -e "$result" | sed "1d" | sort -k 4rn;
示例输出:
total 7882 inotify watches
EXE PID FD-INFO INOTIFY-WATCHES
/usr/local/qcloud/YunJing/YDEyes/YDService 25813 /proc/25813/fdinfo/8 7077
/usr/bin/kubelet 1173 /proc/1173/fdinfo/22 665
/usr/bin/ruby2.3 13381 /proc/13381/fdinfo/14 54
/usr/lib/policykit-1/polkitd 1458 /proc/1458/fdinfo/9 14
/lib/systemd/systemd-udevd 450 /proc/450/fdinfo/9 13
/usr/sbin/nscd 7935 /proc/7935/fdinfo/3 6
/usr/bin/kubelet 1173 /proc/1173/fdinfo/28 5
/lib/systemd/systemd 1 /proc/1/fdinfo/17 4
/lib/systemd/systemd 1 /proc/1/fdinfo/18 4
/lib/systemd/systemd 1 /proc/1/fdinfo/26 4
/lib/systemd/systemd 1 /proc/1/fdinfo/28 4
/usr/lib/policykit-1/polkitd 1458 /proc/1458/fdinfo/8 4
/usr/local/bin/sidecar-injector 4751 /proc/4751/fdinfo/3 3
/usr/lib/accountsservice/accounts-daemon 1178 /proc/1178/fdinfo/7 2
/usr/local/bin/galley 8228 /proc/8228/fdinfo/10 2
/usr/local/bin/galley 8228 /proc/8228/fdinfo/9 2
/lib/systemd/systemd 1 /proc/1/fdinfo/11 1
/sbin/agetty 1437 /proc/1437/fdinfo/4 1
/sbin/agetty 1440 /proc/1440/fdinfo/4 1
/usr/bin/kubelet 1173 /proc/1173/fdinfo/10 1
/usr/local/bin/envoy 4859 /proc/4859/fdinfo/5 1
/usr/local/bin/envoy 5427 /proc/5427/fdinfo/5 1
/usr/local/bin/envoy 6058 /proc/6058/fdinfo/3 1
/usr/local/bin/envoy 6893 /proc/6893/fdinfo/3 1
/usr/local/bin/envoy 6950 /proc/6950/fdinfo/3 1
/usr/local/bin/galley 8228 /proc/8228/fdinfo/3 1
/usr/local/bin/pilot-agent 3819 /proc/3819/fdinfo/5 1
/usr/local/bin/pilot-agent 4244 /proc/4244/fdinfo/5 1
/usr/local/bin/pilot-agent 5901 /proc/5901/fdinfo/3 1
/usr/local/bin/pilot-agent 6789 /proc/6789/fdinfo/3 1
/usr/local/bin/pilot-agent 6808 /proc/6808/fdinfo/3 1
/usr/local/bin/pilot-discovery 6231 /proc/6231/fdinfo/3 1
/usr/local/bin/sidecar-injector 4751 /proc/4751/fdinfo/5 1
/usr/sbin/acpid 1166 /proc/1166/fdinfo/6 1
/usr/sbin/dnsmasq 7572 /proc/7572/fdinfo/8 1
4)调整 inotify watch 限制
如果看到总 watch 数比较大,接近最大限制,可以修改内核参数调高下这个限制。
临时调整:
sudo sysctl fs.inotify.max_user_watches=524288
永久生效:
echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.conf && sysctl -p
打开 inotify_add_watch 跟踪,进一步 debug inotify watch 耗尽的原因:
echo 1 >> /sys/kernel/debug/tracing/events/syscalls/sys_exit_inotify_add_watch/enable
7、soft lockup (内核软死锁)
1)内核报错
Oct 14 15:13:05 VM_1_6_centos kernel: NMI watchdog: BUG: soft lockup - CPU#5 stuck for 22s! [runc:[1:CHILD]:2274]
2)原因
发生这个报错通常是内核繁忙 (扫描、释放或分配大量对象),分不出时间片给用户态进程导致的,也伴随着高负载,如果负载降低报错则会消失。
3)什么情况下会导致内核繁忙
短时间内创建大量进程 (可能是业务需要,也可能是业务bug或用法不正确导致创建大量进程)
4)如何优化?