一、前言
前段时间共事遇到了一个容器内文件句柄透露的问题,容器外部偶发性呈现Too many open files
报错。过后的文件句柄配置如下:
问题是不是来了,主机的文件句柄和容器的文件句柄数有关系吗?是什么关系呢?这就是以下要探索探讨的问题。
两头记录了试验的过程,导致篇幅有点长,心急的小伙伴们能够间接跳转至四章节《论断剖析》。
二、docker容器启动流程
docker是典型的client-server类型的利用,能够应用docker version
命令查看服务端和客户端的信息:
docker启动容器的大抵流程如下:
注: 右边黄色文字框中对应的是在Linux下的常驻过程名称;
重点在runc
创立容器子过程的时候,会传入相干的namespace参数进行资源隔离(用户隔离是基于Linux的CLONE_NEWUSER
这个namespace);
三、本地试验过程
正好本地有个现成的war包,就用了tomcat容器来测试,用Dockerfile
将war拷贝到webapps下就行了。Dockerfile
如下:
FROM tomcat:8.5.72-jdk8
RUN useradd dockertester -m -d /home/dockertester \
&& gpasswd -a dockertester dockertester \
&& chown dockertester:dockertester -R /usr/local/tomcat
ADD --chown=dockertester pftest.war /usr/local/tomcat/webapps
USER dockertester
CMD ["catalina.sh", "run"]
- CentOS Linux release 7.4.1708 (Core)
- docker-19.03.9
- tomcat:8.5.72-jdk8
(一)应用Linux默认配置,2000并发测试
环境筹备1:
- 应用
Dockerfile
构建利用镜像;- 应用
locust
【比拟省资源】进行并发申请,须要本人写压测脚本;ulimit -n
查看以后文件句柄数限度(默认普通用户都是1024):
[root@localhost ~]# 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) 7216
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
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) 7216
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
cat /proc/sys/fs/file-max
查看零碎最大句柄数限度(默认181953);
[root@localhost ~]# cat /proc/sys/fs/file-max
181953
- 在容器内查看java过程以后文件句柄数限度状况(以后容器内java过程文件句柄数最大为1048576,这数值哪来的前面解释):
dockertester@9798be4fb19c:/usr/local/tomcat$ cat /proc/$(ps -A | grep java | awk '{print $1}')/limits | grep "files"
Max open files 1048576 1048576 files
- 测试执行2分钟,没有任何报错。
- 在容器内查看以后应用句柄数,发现曾经句柄数应用曾经达到3488,远超默认的1024:
[root@localhost ~]# docker exec -it tomcat-test bash
dockertester@9798be4fb19c:/usr/local/tomcat$ cat /proc/sys/fs/file-nr
3488 0 181953
测试小结
docker容器内的文件句柄数限度不受Linux宿主机用户文件句柄数限度。应用普通用户启动容器内利用也是一样,本次试验就是新建了dockertester用户。
(二)批改容器的limit参数,2000并发测试
- 环境筹备1的配置都不必批改;
- 容器启动命令增加限度参数:
--ulimit nofile=1024:1024
,容器启动实现后,在容器内查看java过程句柄数限度(曾经胜利批改为1024):
dockertester@123da4ac778d:/usr/local/tomcat$ cat /proc/$(ps -A | grep java | awk '{print $1}')/limits | grep "files"
Max open files 1024 1024 files
- 压测不到1分钟开始呈现大量报错:
- 查看以后零碎句柄数应用状况,放弃在2432(超出1024的那些是Linux宿主机其余过程占用的):
dockertester@123da4ac778d:/usr/local/tomcat$ cat /proc/sys/fs/file-nr
2432 0 181953
- 查看tomcat的日志,能够发现大量
Too many open files
报错:
测试小结
docker容器内的句柄数受启动参数--ulimit nofile=1024:1024
的限度。
(三)批改Linux零碎最大文件句柄数限度,2000并发测试
环境筹备2:
- 去掉容器启动命令增加限度参数:
--ulimit nofile=1024:1024
;- 批改零碎最大文件句柄数限度:
[root@localhost ~]# cat /proc/sys/fs/file-max
181953
[root@localhost ~]# echo 2000 > /proc/sys/fs/file-max
[root@localhost ~]# cat /proc/sys/fs/file-max
2000
- 容器内查看以后java过程文件句柄数限度(还是之前默认的1048576):
dockertester@c8e0796a224a:/usr/local/tomcat$ cat /proc/$(ps -A | grep java | awk '{print $1}')/limits | grep "files"
Max open files 1048576 1048576 files
- 压测执行不到1分钟,开始大量报错:
- 在容器内查看以后文件句柄数应用状况,文件句柄数曾经用满:
bash: start_pipeline: pgrp pipe: Too many open files in system
bash: /bin/cat: Too many open files in system
- tomcat日志中存在大量
Too many open files
报错:
测试小结
docker容器内的句柄数受宿主机系统文件句柄数限度(/proc/sys/fs/file-max
或/etc/sysctl.conf
文件配置的参数)
四、论断剖析
- 注: 本文不适用于Rootless形式装置的docker利用,针对这种形式装置的docker还未钻研过。
(一)namespace过程隔离【隔离不齐全】
- docker启动容器会基于namespace做过程隔离,且领有CAP_SYS_RESOURCE的能力,像文件句柄这样的参数docker能够进行重设,然而重设之后还是受宿主机零碎参数的限度,即
/proc/sys/fs/file-max
或/etc/sysctl.conf
文件配置的参数;
- 默认docker内过程的启动用户为
root
,容器内的root
权限要比宿主机的权限稍低。失常状况下,容器内无奈批改相似/proc/sys/fs/file-max
这些系统配置,会呈现只读文件报错bash: /proc/sys/fs/file-max: Read-only file system
。但如果在docker run
的时候增加--privileged
参数,就能够批改这些内核参数,而且宿主机的配置也会被同步批改,这是非常危险⚠️的事;
- docker和宿主机共享内核,因而
cat /proc/sys/fs/file-max
这类查看零碎级别的命令在容器内和容器外执行都是一样的。还有top
命令看到的CPU核数并不是docker启动参数限度的核数,还是宿主机的核数。
(二)反对namespace的内核配置项
参见官网文档:Configure namespaced kernel parameters (sysctls) at runtime
IPC(用于隔离过程间通信所需的资源)
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall, kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced
;- fs.mqueue结尾的参数,
fs.mqueue.*
;
Network相干:
- 相似
net.*
的配置参数; - 如果应用了
--network=host
选项, 则不容许以docker run --sysctl net.ipv4.ip_forward=1 someimage
形式启动;
(三)容器内过程默认文件句柄数的起源
分割第二章的容器启动过程,容器过程
是runc
的子过程(创立完完结),runc
是containerd
的子过程。在fork子过程的时候,子过程会继承父过程的所有信息,所以容器内过程默认文件句柄数是继承的containerd
过程的句柄数限度。能够命令查看,其实就是之前默认的1048576;
cat /proc/$(ps -A | grep containerd-PID | awk '{print $1}')/limits | grep "files"
而containerd
过程的句柄数限度在containerd.service
文件中默认设置了1048576
- 新版本的不是在
docker.service
中设置的了- 从Docker 1.11开始,Docker Daemon不再蕴含任何运行时的代码,这部分逻辑迁徙独自的OCI兼容层,而
containerd
就在daemon
和runc
所在的OCI层之间,负责容器的创立,而销毁工作交给shim
。
(四)一个有意思的用户命名空间映射
在Dockerfile构建利用镜像的时候,如果指定非root
启动容器,那么docker默认会映射uid
为1000的用户,如果本地宿主机没有设置uid为1000的用户,容器启动后,ps -ef
会看到用户那一列间接就显示1000。
发表回复