启动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)并打包更新到问题环境。

Docker代替虚拟机

目的:为docker container分配一个与宿主机同网段的静态IP,允许同网段用户ssh访问这个container。

步骤1:启动ubuntu镜像:

docker run -itd --name container1 --net=none ubuntu

步骤2:进入container做一些设置:

docker exec -ti container1 bash
apt-get update && apt-get install openssh-server -y
echo 'root:mypassword'|chpasswd
service ssh restart
vi /etc/ssh/sshd_config //将PermitRootLogin值改yes

步骤3:可以用docker commit将这些工作打成镜像,以后开新container就方便了:

docker stop container1
docker commit container1 ssh_image:1.0
docker start container1
docker exec -tid container1 service ssh start #开启ssh服务

步骤4:安装并使用pipework设置网络(直连宿主机所在子网):

wget https://github.com/jpetazzo/pipework/archive/master.zip
unzip master.zip
pipework eth0 container1 10.1.10.86/24@10.1.10.1

参考资料

Docker安装和基本操作

安装Docker

环境ubuntu 14.04 LTS。如果已经安装过docker,请先卸载,方法见这个链接

在Linux系统安装docker使用下面的命令:

curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh

如果上述命令由于网络原因无法成功,可以使用阿里云的镜像(未验证,链接):

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

启动docker:

sudo service docker start

配置

官方文档,在/etc/docker/daemon.json里(如果没有这个文件可以创建一个)修改dockerd启动配置。但如果在systemd里已经指定的选项,是不能在daemon.json里再次指定的,例如-H选项。

其他配置dockerd的方式:

  • 在centos里,启动dockerd的选项在/etc/sysconfig/docker里修改。
  • 在ubuntu里,启动dockerd的选项在/lib/systemd/system/docker.service里修改。

要连接远程dockerd,在dockerd启动选项里要加上-H tcp://0.0.0.0:2375配置,在客户端docker命令里加上-H tcp://ip:2375选项。

镜像库

官方镜像:https://github.com/docker-library/official-images/tree/master/library

基本操作

在Docker里,容器(container)是镜像(image)的实例,查看所有image和所有container的命令分别为:

# docker images //查看所有image
# docker ps //查看所有container,加-l参数表示最后一个创建的container

Dockerfile相当于image的源代码,下面是一个示例Dockerfile:

FROM my-image
ADD local/my.jar /opt/my.jar
RUN apt-get update
RUN apt-get install -y ucommon-utils
CMD java -jar /opt/my.jar

从Dockerfile创建image的命令如下,其中tag是可选的一般填版本号,缺省值为“latest”

# docker build -t name:tag /path/of/dockerfile

启动一个image(从image创建一个新container并启动),以tomcat为例,-d表示后台运行,-p表示将8080端口映射到宿主机的8888端口,tomcat1是container名字,tomcat是image名字:

# docker run -d -p 8888:8080 --name tomcat1 tomcat

删除一个container,-f表示强制删除即使container在running状态:

# docker rm -f tomcat1

有时会遗留一些orphan的container和image,如果一直积累下去可能会占满硬盘空间。Docker没有提供专门的命令清理它们,可以考虑用下面的命令(参考链接):

# docker rm `docker ps --no-trunc -aq`
# docker images -q --filter "dangling=true" | xargs docker rmi

要查看每个容器占用资源情况(显示容器名):

docker stats $(docker ps --format={{.Names}})

(待续)

参考资料:

Docker从入门到实践