启动Docker容器超过100个后报错问题

环境信息

AMD EPYC 7551 32-Core Processor 64bit / 452GB / 4.0TB
Kylin Linux Advanced Server V10 (Sword)
Linux-419.90-24.4.v2101.ky10.x86 64-x86 64-with-kylin-10-Sword
Docker 19.03.9

问题现象

Docker容器超过一定数量后无法启动新的容器。

排查和解决

先检查目前正常运行的容器数量,有134个:

> docker ps| wc-l
134

查看了系统可用CPU和内存资源,发现可用16个CPU线程和24GB内存,可用资源还比较宽裕。

考虑到目前运行的容器已经比较多,首先怀疑是打开文件数量(open files)达到上限,检查系统设置值:

> ulimit -n
1024

因为其他类似环境里一般都设置为65535,所以用ulimit -n 65536将打开文件数上限改大,但问题并没有解决。

既然启动新的容器失败,可能docker daemon日志会有线索,查了一下果然发现有错误信息如下:

>  journalctl -ru docker.service
...
04月 26 14:39:35 k2box-211 dockerd[3016]: time="2023-04-26T14:39:35.212798766+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:35 k2box-211 dockerd[3016]: time="2023-04-26T14:39:35.212674036+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:26 k2box-211 dockerd[3016]: time="2023-04-26T14:39:26.731792766+08:00" level=warning msg="Health check for container 443a24419f9864ca13a6772c35bb7fe9eabcf317d670d7c73c0383e5473cb397 error: context deadline exceeded"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.706135654+08:00" level=warning msg="Health check for container d18b85908134d2c45869f68daf825d0c7f6541e77673e8c83a03d34a1c0a3ad9 error: OCI runtime exec failed: exec failed: container_linux.go:318: starting container process caused \"exec: \\\"curl\\\": executable file not found in $PATH\": unknown"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.699741119+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.699578169+08:00" level=error msg="stream copy error: reading from a closed fifo"
...

日志提示context deadline exceeded,这个错误信息虽然比较模糊,但从时间上看与尝试创建新容器的时间是吻合的,并且应该是某个阈值的限制。在网上google了一下没有找到相同的问题。

尝试停止了一个正在运行的容器,此时启动新容器能正常成功1个,再启动则会失败,因此确认了是容器数量达到了某个阈值,但为什么是134个?

经过仔细分析,发现这134个容器的启动方式有区别,其中34个是通过docker run命令行启动的,另外100个是通过docker-java以程序方式启动的(http接口),查看相关代码发现后者果然有限制:

DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
    .dockerHost(config.getDockerHost())
    .sslConfig(config.getSSLConfig())
    .maxConnections(100)  // 这里对连接数量做了限制
    .connectionTimeout(Duration.ofSeconds(30))
    .responseTimeout(Duration.ofSeconds(45))
    .build();

至此定位到问题原因,解决方法是将maxConnections(100)改为maxConnections(1024)并打包更新到问题环境。