Linux 单服务器跑超多容器时遇到的一些问题和解决方案

发布于 2024-08-03  13 次阅读


好久没写博客了,评论区好多找我的也没回,不是我不想,是真没空,对不住大家了。

一些需求

  1. 容器大约在500个左右,其中250个是执行容器,250个是tun代理容器,执行容器通过共享网卡的方式连接到tun代理访问外网。
  2. 执行容器主要工作是收发各种网络请求(单条长连接)
  3. 执行容器实际上2500个,只不过同时只有250个在运行,通过定时轮换的形式来循环运行(所以容器会经常性地启停)

问题/排查/方案

IO瓶颈

最开始的时候,碰到的是磁盘IO瓶颈。具体表现为:vmstat中的读写等待队列过大,如r列数值一直远大于0,bibo一直过千甚至过万。具体分析可参考Linux性能分析工具合集 - yanchuang-16

$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
47  0      0 58269072   1876 952416    0    0    46    79 2195 3668 17 12 69  1  2
 2  0      0 58269072   1876 952416    0    0    52  1464 58155 100150 31 23 36  0 10
17  0      0 58269072   1876 952416    0    0   116  2175 69638 121898 34 25 34  0  7
17  0      0 58269072   1876 952416    0    0    32  2027 69675 119238 36 31 25  0  8
39  0      0 58269072   1876 952416    0    0   120  1434 64245 112218 34 24 39  0  4
35  0      0 58269072   1876 952416    0    0   120  1422 56441 93959 32 24 32  0 12
17  0      0 58269072   1876 952416    0    0   104  1308 62526 109288 34 24 34  0  8
16  0      0 58269072   1876 952416    0    0    64   543 60737 106197 34 21 36  0  9
35  0      0 58269072   1876 952416    0    0   192  1087 54720 94879 39 22 27  0 13
 3  0      0 58269072   1876 952416    0    0    28  1863 68585 117591 44 26 23  0  6

这个是我完全意料之外的一个性能短板,因为我的执行容器和tun代理容器都没有相关的IO需求,所以猜测是容器过多导致了某些系统监测服务持续记录日志或其他信息导致的IO瓶颈。

于是装了下iotop来寻找到底是哪个进程在占满IO。

直接裸执行iotop就能看到,journald进程以一骑绝尘的速度遥遥领先于其他进程,疯狂占用磁盘性能。

这里没图也没执行记录,因为写博客的时候已经解决了

所以猜测是journald的日志汇总出了问题,比如说某个服务产生的日志过多,导致其来不及处理。

于是用journalctl -o short-precise | awk '{print $5}' | sort | uniq -c | sort -nr | head统计了一下输出日志的服务名称,发现containerddockerd的日志条数在十万条甚至百万条级别,随便看了下发现绝大多数是由容器启停而带来的无用日志。

awk '{print $5}'在有些系统上可能需要改成awk '{print $6}',具体取决于你的journalctl -o short-precise输出结果中服务名称在哪一列

比如containerd在每次容器启动时都会打个几条内置插件已加载的日志,并且日志等级为INFO,但是实际上明明应该是DEBUG才合理,这个问题今年年初就有人提过:https://github.com/containerd/containerd/issues/9637#issuecomment-1892858715 ,虽然下面有pr给日志等级改成DEBUG了,但是并没有合并到主分支,应该是要等到containerdv2的时候才合并进去,所以不抱期望。

所以想通过限制dockerdcontainerd日志等级的方式去减少其抛出的日志量,比如说限制到WARN级别甚至ERROR级别,但是很不幸,dockerd倒是有这个特性,支持通过配置文件/etc/docker/daemon.json来限制日志等级,containerd却没有(或者说有,但是是半残状态,大部分日志都不归这个配置项管)

那么只能狠下心,把整个系统的日志等级给限制在WARN级别了,这样子虽然那俩服务还是会抛出大量无用日志,但是系统会直接忽略掉,这样也算是变相解决了一部分性能压力,虽然治标不治本。

# /etc/systemd/journald.conf

[Journal]
MaxLevelStore=warning

修改后为了稳妥起见,重启了系统,并且用土方法journalctl --rotate && journalctl --vacuum-time=1s清空了系统日志。(没找到其他能完全清除日志的方法)

然后再运行所有容器,发现IO瓶颈得到解决,重新统计系统日志中的服务名称,也几乎不再出现containerddockerd

DBus 瓶颈

这个是后来在检查系统日志的时候发现的,一堆dbus-daemon报的日志,提示The maximum number of active connections for UID 0 has been reached (max_connections_per_user=256)

可见是用户分配的dbus不足导致的,为了确认是哪个进程在占用dbus,可以通过dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames查询dbus分配的name列表,多连续查询几次,发现列表变动很快,且基本上是满负载状态(256个用完),所以让GPT给我生成了个管道符查询最新一个DBus Name对应的进程PID的进程:

dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames | \
grep string | \
awk -F '"' '{print $2}' | \
tail -n 1 | \
xargs -I {} dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetConnectionUnixProcessID string:{} | \
grep uint32 | \
awk '{print $2}' | \
xargs -I {} ps -p {} -o pid,comm

多执行几次,发现大多数时候是runc进程一直在不停地占用DBus。

这个解决起来其实倒是简单,因为日志里面连配置项的名字都写好了,找到配置文件改就完事了。

最开始我找的是/usr/share/dbus-1/system.conf,里面确实有注释掉的默认值 <!-- <limit name="max_connections_per_user">256</limit> -->

不过,在这个文件的开头,提示了不建议直接修改这个默认配置文件,而是建议去自己建一个system-local.conf文件来覆写部分配置项:

<!-- This configuration file controls the systemwide message bus.
     Add a system-local.conf and edit that rather than changing this 
     file directly. -->

而这个system-local.conf具体应该放哪的话,在这个默认配置文件的结尾也有写:

  <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>

所以去新建个/etc/dbus-1/system-local.conf然后覆写配置项即可。

<busconfig>
    <limit name="max_connections_per_user">1024</limit>
</busconfig>

然后DBus这玩意本身十分底层,建议也是重启整个系统来让配置修改生效。

inotify watch 描述符瓶颈

具体表现为journalctl -f会提示Insufficient watch descriptors available.,虽然没发现明显的性能影响,但是稳妥起见还是调高比较好。

# /etc/sysctl.conf

fs.inotify.max_user_instances = 8192
sysctl -p