这几天发现部署的一个 WebSocket 应用容器很不稳定,WebSocket 连接老是会无缘无故的批量掉线。跟踪了一下 haproxy 路由转发容器的日志,发现以下报错:
[WARNING] 311/072614 (22534) : [haproxy.main()] Cannot raise FD limit to 131116, limit is 65536.
带着这个错误信息,找到了以下帖子:
使用四种框架分别实现百万 websocket 常连接的服务器
这里详细地描述了如何通过调优实现 WebSocket 的百万连接,而我的应用不过几千的连接,服务器的配置也差不太多,理论上应该是完全不会有问题发生的,现在有问题肯定就是没有调优造成的。
需要解决的问题是:作者是在物理机上直接进行的调优,那么在容器里面该如何做呢?
当时的第一反应就是 docker exec 进入容器直接进行参数调整,发现会报错:
ulimit: open files: cannot modify limit: Operation not permitted
原因是:docker 容器默认移除 sys_resource(Linux 能力),因而 ulimit -n 设置只能改小无法改大,改大会报错。
详情见:深入理解docker ulimit 。
那应该怎么办呢,经过对资料的一番思考与尝试(艰辛的过程就不多说了),终于找到了解决办法:
一、调整宿主机的 ulimit
# 查看文件最大的打开数
$ cat /proc/sys/fs/file-max
# 如果数值较小,则需要调大,这里我是调整为 100W
# 临时设置
$ echo 1000000 > /proc/sys/fs/file-max
# 永久设置
$ echo "fs.file-max = 1000000" >> /etc/sysctl.conf
$ echo "fs.nr_open = 1000000" >> /etc/sysctl.conf
# 修改文件,将最后 4 行的 65535 改为 1000000
$ vi /etc/security/limits.conf
# 改成大于 1000000,这里我设置为 2000000
$ echo 2000000 > /proc/sys/fs/nr_open
# 查看当前系统使用的打开文件描述符数
$ cat /proc/sys/fs/file-nr
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 63470
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1000000
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 63470
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
二、调整系统容器的 ulimit
由于应用请求需要走 haproxy 路由转发容器,这个是某云的系统容器,因此需要走特别的途径进行修改(这也算是某云的一个 Bug 吧),解决的方式也很变态,因为这个容器是使用 docker-compose 编排文件发布的,因此需要改写 docker-compose 编排文件。这里需要使用谷歌浏览器的开发工具,从前端找到对应的隐藏的系统按钮,去掉 ng-hide
属性,将按钮显示出来,就可以修改编排文件了。追加以下 yaml 文件内容即可:
ulimits:
nofile:
soft: 1000000
hard: 1000000
privileged: true
系统容器重启后,使用以下命令查看系统容器的进程 ID 还会不会变化(变化说明容器挂掉重启了):
docker exec -ti <routing_container_id> ps aux
这步看不懂的请跳过即可。
三、调整应用容器的 ulimit
应用容器我也是使用 docker-compose 编排文件启动的,而且完全受我控制,这就比较简单了,一样的处理方式,追加以下 yaml 文件内容即可:
ulimits:
nofile:
soft: 1000000
hard: 1000000
privileged: true
总结
调整完成后,重启相应的容器,并进入容器用 ulimit -a
可以查看到文件打开数限制已经生效,haproxy 路由转发容器不再频繁挂掉重启,而 WebSocket 应用容器也变得稳定起来了。
经过这次的问题处理过程,我们应该对 docker 的 ulimit 重视起来,在一些复杂的应用场景下,需要将容器就看作是物理机,来考虑调优的事宜。
这一点是我们使用容器后容易忽略的事情,希望能给大家在上生产前一些警示。
参考资料
使用四种框架分别实现百万 websocket 常连接的服务器