1.前言前段时间有同事遇到容器中文件句柄泄露的问题,容器内部偶尔会出现Toomanyopenfiles错误。当时的文件句柄配置是这样的:问题来了,宿主机上的文件句柄数和容器上的文件句柄数有关系吗?什么关系?这是下面要探讨的问题。中间记录了实验的过程,导致有点篇幅。不耐烦的可以直接跳到第四章《结论分析》。2.Docker容器启动流程docker是一个典型的client-server应用,可以使用dockerversion命令查看server和client的信息:docker启动容器的大致流程如下:注意:上面的黄色文本框左边对应Linux下面的常驻进程名;关键是runc在创建容器子进程时,会传入相关的命名空间参数,用于资源隔离(用户隔离是基于Linux的CLONE_NEWUSER命名空间);3.本地实验过程?本地刚好有一个现成的war包,用tomcat容器进行测试,通过Dockerfile将war拷贝到webapps中。Dockerfile如下:FROMtomcat:8.5.72-jdk8RUNuseradddockertester-m-d/home/dockertester\&&gpasswd-adockertesterdockertester\&&chowndockertester:dockertester-R/usr/local/tomcatADD--chown=dockertesterpftest.war/usr/local/tomcat/webappsUSERdockertesterCMD["catalina.sh","run"]CentOSLinuxrelease7.4.1708(Core)docker-19.03.9tomcat:8.5.72-jdk8(1)使用Linux默认配置,2000并发测试环境准备1:使用Dockerfile构建应用镜像;使用locust【节省资源】并发请求,需要自己写压测脚本;ulimit-n查看当前文件句柄限制(普通用户默认1024):[root@localhost~]#ulimit-acorefilesize(blocks,-c)0datasegsize(kbytes,-d)unlimited调度优先级(-e)0文件大小(blocks,-f)无限挂起信号(-i)7216最大锁定内存(kbytes,-l)64最大内存大小(kbytes,-m)无限打开文件(-n)1024管道大小(512字节,-p)8个POSIX消息队列(字节,-q)819200实时优先级(-r)0堆栈大小(kbytes,-s)8192cpu时间(seconds,-t)unlimitedmaxuserprocesses(-u)7216virtualmemory(kbytes,-v)unlimitedfilelocks(-x)unlimitedcat/proc/sys/fs/file-max查看系统最大句柄数(默认181953);[root@localhost~]#cat/proc/sys/fs/file-max181953查看容器中java进程的当前文件句柄限制(当前容器中java进程文件句柄的最大数量为1048576,其中这个value的来源后面会解释):dockertester@9798be4fb19c:/usr/local/tomcat$cat/proc/$(ps-A|grepjava|awk'{print$1}')/limits|grep"files"Maxopenfiles10485761048576files测试执行了2分钟,没有报错。查看容器当前使用的句柄数,发现使用的句柄数已经达到3488个,远超默认的1024个:[root@localhost~]#dockerexec-ittomcat-testbashdockertester@9798be4fb19c:/usr/local/tomcat$cat/proc/sys/fs/file-nr34880181953测试总结docker容器中的文件句柄数量不受Linux主机Host用户文件句柄限制。使用普通用户启动容器中的应用也是如此。本次测试是新建一个dockertester用户。(2)修改容器的limit参数。2000并发测试环境准备1的配置不需要修改;容器启动命令添加limit参数:--ulimitnofile=1024:1024。容器启动后,查看容器内java进程句柄数Limit(已成功修改为1024):dockertester@123da4ac778d:/usr/local/tomcat$cat/proc/$(ps-A|grepjava|awk'{print$1}')/限制|grep"files"Maxopenfiles10241024files压测不到1分钟就开始出现大量错误:查看当前系统句柄使用率,保持在2432(超过1024的被本进程其他进程占用)Linuxhost):dockertester@123da4ac778d:/usr/local/tomcat$cat/proc/sys/fs/file-nr24320181953查看tomcat日志,可以发现很多Toomanyopenfileserror:testsummarydocker容器中的句柄受启动参数--ulimitnofile=1024:1024的限制。(3)修改Linux系统最大文件句柄数限制,2000并发测试环境准备2:去掉容器启动命令,增加限制参数:--ulimitnofile=1024:1024;修改系统最大文件句柄数限制:[root@localhost~]#cat/proc/sys/fs/file-max181953[root@localhost~]#echo2000>/proc/sys/fs/file-max[root@localhost~]#cat/proc/sys/fs/file-max2000查看容器中当前java进程文件句柄限制(还是之前的默认1048576):dockertester@c8e0796a224a:/usr/local/tomcat$cat/proc/$(ps-A|grepjava|awk'{print$1}')/limits|grep"files"Maxopenfiles10485761048576files压测执行不到1分钟,就开始报大量错误:查看当前容器中文件句柄数的使用情况,以及filehandleshasbeenusedup:bash:start_pipeline:pgrppipe:Toomanyopenfilesinsystembash:/bin/cat:Toomanyopenfilesinsystemtomcat日志中有大量的Toomanyopenfiles错误报告:测试总结docker容器中的句柄数受宿主系统文件句柄数的限制(/proc/sys/fs/file-max或/etc/sysctl.conf文件中配置的参数)四、结论分析注:这个文章不适用于Rootless方式安装的docker应用,这种方式安装的docker没研究过。(1)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-onlyfilesystem会出现。但是如果在docker运行的时候加上--privileged参数,就可以修改这些内核参数,宿主机的配置也会同步修改,非常危险??;docker和宿主机共享内核,所以cat/proc/sys/fs/file-max等系统级命令在容器内外执行相同。还有,top命令看到的CPU核数不是docker启动参数限制的核数,而是宿主机的核数。(2)支持命名空间的内核配置项见官方文档:Configurenamespacedkernelparameters(sysctls)atruntimeIPC(用于隔离进程间通信所需的资源)kernel.msgmax,kernel.msgmnb,kernel.msgmni,kernel.sem,kernel.shmall,kernel.shmmax,kernel.shmmni,kernel.shm_rmid_forced;fs.mqueue开头的参数,fs.mqueue.*;网络相关:类似net.*的配置参数;如果使用--network=host选项,则不允许以dockerrun--sysctlnet.ipv4.ip_forward=1someimage启动;(3)容器内进程默认文件句柄号的来源?联系第2章容器启动进程,容器进程是runc(createend)的子进程,runc是containerd的子进程。fork子进程时,子进程会继承父进程的所有信息,所以容器中进程的默认文件句柄数是被继承的containerd进程句柄数的限制。可以通过命令查看,其实就是之前默认的1048576;cat/proc/$(ps-A|grepcontainerd-PID|awk'{print$1}')/限制|grep"files"并且containerd进程的句柄数量限制为containerd1048576,默认情况下在.service文件中设置。docker.service中没有设置新版本。从Docker1.11开始,DockerDaemon不再包含任何运行时代码。这部分逻辑迁移到单独的OCI兼容层,containerd在daemon和runc所在的OCI层之间,负责容器的创建,销毁交给shim。(4)有趣的用户命名空间映射在Dockerfile中构建应用镜像时,如果指定非root启动容器,那么docker默认会映射uid为1000的用户。如果本地主机没有设置uid为1000的用户,容器启动后,ps-ef会看到用户一栏直接显示1000。
