深入理解docker ulimit

2015年7月23日 10:00 阅读 12778
【编者的话】Docker大规模应用后,如果你没踩过坑,说出去肯定没人信。昨天就遇到一个ulimit的经典问题:业务Container内ulimit值太小,导致启动失败。ulimit问题,老生常谈,但是在不同的场景与环境,表现出来的灵异,往往需要一些深入的分析才能找到本因。本次问题就与os版本,docker版本及配置方法息息相关,我们来回顾一下。
 
一  问题:
1,背景:
微博平台业务经过去年三节Docker化后,已稳定运行半年多了,由于采用的都是较保守的版本,虽然也踩过很多坑,但都在可控内。最近正与RD一起推进一个大项目,前提也是平台业务全Docker化,这部分已完成90%了。其基本信息如下:
     1)OS版本:Centos6.5
     2)JDK: 1.7.0_25  Tomcat: 7.0.42
     3)Docker:1.3.2
     4)Docker Registry: 1.0
     其中:正在也推进升级OS到centos7,docker到1.6.2
 
2,现象:
本次出现的问题现象:当人工重启服务器后,再启动业务Container,发现启动失败,并且能够重现。
     ps: 用运维系统做部署时并未发现此问题。
 
3,重现:
重现的主要条件为:
     1)版本:os(centos6.5),docker 1.3.2, docker daemon随开机启动而启动
     2)主机配置:ulimit设置为200000,配置在/etc/profile
     3)操作:手工reboot机器后,登录,启动业务容器,启动起来后,一会就失败。
 
4,分析:
    1)服务器重启,此过程正常
    2)业务Container启动后不久失败,经分析为容器内ulimit不对,只获得默认的值:1024. 后面会讲这个值为什么是1024.
    3)重启Docker daemon进程,再启动Container,发现一切正常。经查看容器内ulimit值为:主机设置的200000.
     现象搞清楚后,且能重现,解决问题就很简单了,方法很多。后面细说。
 
5,总结:
 一句话总结:当服务器重启后,Docker daemon随之系统启动而启动,当启动Container时,因未获取到主机设置的ulimit值而导致启动后一会儿就失败了,重启Docker Deamon后解决(ps:这不是解决问题的方法,这只是瞎猫撞到死耗子)。
  
二  经典理论之ulimit
关于ulimit问题,我相信只要是做过SA,或者玩过服务器的RD都应该遇到过,各种现象,其理论依据其实很简单,大家可以参考@淘宝褚霸 博客的文章,有4篇,足够细致了。13年时,我问过他一个问题,他还从源码角度去分析了,这种分享精神棒棒哒。地址为:http://blog.yufeng.info/archives/tag/ulimit 。这里就不在展开说了。
 
三  经典理论之Linux系统启动与环境变量加载顺序
1,Linux系统启动
这里就直接说CentOS(Redhat类)的启动过程:大家都知道目前Linux系统启动最常用的就是两种:init(SysVinit系)与Systemd系两大阵营,二者对比可参考:
http://images.linoxide.com/systemd-vs-sysVinit-cheatsheet.pdf 。Systemd主要是centos7及之后的版本采用,而之前的发行版均采用SysVinit系,而我们这次出现问题的是centos6.5,也就是SysVinit系。下面来看下它的启动过程:
参考图:
 具体过程:
         1)加载BIOS的硬件信息,执行BIOS内置程序
         2)读取MBR(Master Boot Record)中Boot Loader中的引导信息
         3)加载内核Kernel boot到内存中
         4)内核开始执行/sbin/init,并加载/etc/inittab,执行rc.sysinit进行初始化
         5)启动核心的外挂模块/etc/modules.conf
         6)按照启动级别(服务器默认是3)执行/etc/rc.d/下运行脚本 
               即:[guansheng@xx-xx-xx-yf-core rc3.d]# pwd
               /etc/rc.d/rc3.d/
               这个过程会把chkconfig --list中看到3级下on的服务全部启起来。
         7)执行/bin/login程序
               到这,你就可以看到登录的tty窗口了。
 
2,Linux环境变量加载顺序
对于环境变量加载顺序,各发行版大同小异,这里也只说redhat系的:其大致顺序如下:
--> /etc/profile     #全局环境变量,每个用户第一次登录时设置
    -->~/.bash_profile #用户级环境变量,每个用户第一次登录时设置
    -->~/.bash_login
    -->~/.profile 
-->~/.bashrc   #用户级环境变量,每个用户登录时设置,打开新Shell时也设置
-->/etc/bashrc
-->~/.bash_logout #用户级环境变量,退出时执行
    ps:其他不细说了。
规则:后面的配置文件继续前面的变量及shell设置,相同的被覆盖。
 
3,理论参考之docker ulimit:
docker在1.6版本及之后,才支持ulimit相关选项,看github上,应该是有人提了pr,后来官方才支持的,地址:https://github.com/docker/docker/pull/9437。 在1.6版本之前,docker container继承自 docker daemon的ulimit设置。 参考文章见docker blog 之ulimit部分:
地址:https://blog.docker.com/2015/04/docker-release-1-6/
 
四  问题复盘
经过以上详细介绍及理论引导,其实对于问题复盘就很简单了,我们简要过一下重点:
1,由于我们人工重启服务器后,根据上面的启动过程可知,docker daemon在系统启动时已经起来了,此时在用户未登录的情况下,并不会读取我们设置的、/etc/profile下的ulimit配置,所以docker daemon会以1024的值进行进程的启动。
2,那么后续创建的container由于docker版本为1.3.2,是继续自docker daemon的值,而造成在container只能看到1024的ulimit值,而业务上依赖大量的mc,mcq,redis,mysql及http等,自然1024不够用,而启动失败。
3,当用户登录后,重启docker daemon后,进程自然会能读取到 该用户的环境变量,从而使ulimit设置为200000. 之后再启动container就再无问题了。
    ps:复盘很简单吧,不过不理解上述原理,很多人还是一头雾水,至少我见到是这样。
 
五  新问题
#探讨centos7下的ulimit
1,问题描述:
当os为centos6.5,docker为1.3.2版本情况下的问题理清后,想试试7,于是在7下依然部署了docker 1.3.2,并进行测试,新问题来了,当主机(host)上未进行设置时(即为默认1024),启动container,发现container内的ulimit是1048576. 修改主机的ulimit,再重启dokcer daemon,启动container,container内还是1048576,好奇怪。
2,分析:
经过与同事一起看了下docker源码,daemon启动那部分,一下子就明朗了。
源码部分:https://github.com/docker/docker/tree/master/contrib/init
可以看到,docker daemon针对不同的系统版本,其对ulimit的默认值设置大有差别。
       1), centos7 采用systemd进行系统初始化,自动会调用systemd下的启动脚本docker.service,其申明默认值如下:
             [Service]
             ExecStart=/usr/bin/docker -d -H fd://
             MountFlags=slave
             LimitNOFILE=1048576
             LimitNPROC=1048576
             LimitCORE=infinity
       2), centos6的话,docker daemon启动,并未设置默认值。参考:sysvinit-redhat
       3), 而对于debian类的系统,尽然也设置了默认值为:1048576. 参考:
           https://github.com/docker/docker/blob/master/contrib/init/sysvinit-debian/docker
  
#docker 1.6版本对ulimit的设置:
在很多时候,对于单个容器来说,这样的ulimit实在是太高了。在Docker 1.6里,可以设置1)  全局默认的ulimit:
      docker -d --default-ulimit nproc=1024:2048
      docker -d --default-ulimit nofile=20480:40960 nproc=1024:2048
 2) 在启动容器时,单独对其ulimit进行设置:
      docker run -d --ulimit nofile=20480:40960 nproc=1024:2048 容器名
 这里有一篇介绍,可以加深你的理解:http://dockone.io/article/302
 
#对于docker ulimit的灵活设置,这里还有一个理论需要注意:
1)  docker容器默认移除sys_resource(Linux能力),因而ulimit -n设置只能改小无法改大,改大会报错:ulimit: open files: cannot modify limit: Operation not permitted。
2)  centos7下docker run可以使用–privileged选项来不移除Linux能力,但docker默认移除这个Linux能力肯定是有安全方面的考量,因此尽量别用该选项
3) centos6下要使用–privileged,docker版本不能>=1.0.1,否则会报错;stat /dev/.udev/db/cpuid:cpu0: no such file or directory。
 
六  优雅解决
经过上面的讨论,相应把问题应该说清楚了,也解释清楚了。那么centos6下,除了上述的手动重启docker daemon方法解决外,还有其他方法吗?答案是肯定的,有很多方法,这里简要说一种吧,思路类似。
即:若使用sysV服务,则在/etc/init.d/functions最开头添加一行:ulimit -u 204800 -HSn 204800
原理为:docker服务启动脚本第一行会去执行它。
  [guansheng@xx-xx-xx-yf-core ~]# ll /etc/rc.d/rc3.d/ |grep docker
  lrwxrwxrwx  1 guansheng root  16 Jul  3 19:25 S95docker -> ../init.d/docker
 
网友建议:
1) @ARGV 指出,/etc/init.d/functions会被所有随系统启动的服务调用,建议直接设置在../init.d/docker启动脚本里,建议有效,感谢指正。不过,这个相当于去修改docker daemon的自启动脚本了。
2) @枯木-Linux,与之交流,发现最好的方案,还是直接修改 /etc/sysconfig/docker 配置文件。赞一个。
 
七  总结
白天出的问题,一个多小时把它理清并解决了,感觉还是蛮好的。深夜,头脑清醒,就想写篇长微博分享给大家,问题虽不难,但崇尚分享精神总是很好的。
ps: 文章快速写成,思路如果不清晰或由错误点,请帮助指出,万分感谢。

[转载] 深入理解 docker ulimit的更多相关文章

  1. 10张图带你深入理解Docker容器和镜像-转

    转载:http://dockone.io/article/783 这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行 ...

  2. 理解Docker单机容器网络

    在” 理解Docker单机容器网络 “一文中,还有一个Docker容器网络的功能尚未提及,那就是Docker容器的端口映射.即将容器的服务端口P’ 绑定到宿主机的端口P上,最终达到一种效果:外部程序通 ...

  3. 理解docker容器和镜像(layer,ufs)和docker命令解释

    博客好文1:http://blog.csdn.net/x931100537/article/details/49633107(理解docker容器和镜像,理解简单,从原理入手,什么是layer,什么是 ...

  4. [serverlet][转载: 深入理解HTTP Session]

    [serverlet][转载: 深入理解HTTP Session] 标签(空格分隔): 未分类 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. ...

  5. 10张图带你深入理解Docker容器和镜像

    http://dockone.io/article/783 [编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. Doc ...

  6. 【原创】深入理解Docker容器和镜像 -- 分析了docker的命令含义

    10张图带你深入理解Docker容器和镜像 镜像(Image)就是一堆只读层(read-only layer)的统一视角 要点:容器 = 镜像 + 读写层.并且容器的定义并没有提及是否要运行容器. 一 ...

  7. 【Docker官方文档】理解Docker

    本文来自Docker的官方文档,详细介绍了Docker的体系结构.重要概念.内部工作机理等内容,推荐不了解Docker内部原理的同学阅读. 什么是Docker? Docker是一个用于开发.交付和运行 ...

  8. 用一个实际例子理解Docker volume工作原理

    要了解Docker Volume,首先我们需要理解Docker文件系统的工作原理.Docker镜像是由多个文件系统的只读层叠加而成.当一个容器通过命令docker run启动时,Docker会加载只读 ...

  9. 如何理解docker镜像build中的上下文

    参考:https://yeasy.gitbooks.io/docker_practice/content/image/build.html 理解上线文概念非常重要,不然可能碰到一些奇怪的问题. 构建镜 ...

随机推荐

  1. jquery easyui常见问题:

    1.jquery easyui1.4.2 demo在ie10 上加载json的时候没有效果 从官网上下载了jquery easyui1.4.2 里面有个demo文件夹,但是发现底下的demo在IE.3 ...

  2. ecshop增加pc扫描二维码微信支付功能代码

    ecshop开发网站,如果没有手机版,又想通过微信支付,可以加入pc二维码扫描微信支付功能 工具/原料 ecshop商城系统,phpqrcode,WxPayPubHelper 公众号已申请微信支付 方 ...

  3. postgresql之ctid的浅谈

       ctid: 表示数据记录的物理行当信息,指的是 一条记录位于哪个数据块的哪个位移上面. 跟oracle中伪列 rowid 的意义一样的:只是形式不一样.    例如这有个一表test:查看每行记 ...

  4. for_each()的返回值

    有三种办法可以从“运用了function object”的算法中获取“结果”或“反馈”: 1.在外部持有状态,并让function object指向它: 2.以by reference方式传递func ...

  5. mini.open打开窗口时传递参数

    mini.open({ url: "xxx.html", showMaxButton: false, allowResize: false, title: '标题', width: ...

  6. miniUI子窗口调父窗口方法

    window.Owner.XXX 其中XXX是父窗口里的方法名

  7. 【20160924】GOCVHelper 图像增强部分(4)

    //使得rect区域半透明     Mat translucence(Mat src,Rect rect,int idepth){         Mat dst = src.clone();     ...

  8. Linux是如何管理内存的

    物理内存的管理 Linux管理物理内存是使用分页机制实现的.为了使分页机制在32位和64位体系结构下高效工作,Linux采用了一个四级分页策略. Linux支持多种内存分配机制.分配物理内存页框的主要 ...

  9. 浅谈CSS样式png、gif、jpg图片优化的方法

    一.PNG.GIF.JPG图片对比 在我们进行图像优化技术前,需要学习有关的图片格式的一些技术细节,每个图形格式都有自己的优势和拖弱点,知道他们会使你得到更好的视觉质量和压缩品质. 网页图片优化是网页 ...

  10. 如何实现百度外卖APP个人中心头像"浪"起来的动画效果

    作为一个中午下班不肯离开工作岗位且勤奋工作的骚年来说,叫外卖就成了不可或缺的习惯.某日瞬间发现百度外卖的APP波浪效果很是吸引人.相比较其他的外卖APP,颜值略高些.(淘宝也有波浪的效果),遂就思考如 ...