Dockerfile之优化经验浅谈
n6xb
10年前
本文主要讲述如何优化Dockerfile,来缩短docker镜像构建需要的时间,以及Dockerfile的一些编辑规范,推荐所有的Docker爱好者阅读,非常基础的文章,本文也许会给你一些启发和指导。
优化您的Dockerfiles
Docker镜像应该是小而快的。然而,假设你在BusyBox镜像中预编译GO二进制文件,他们就会变得又大又复杂。如果不能构建一个良好的Dockerfile来帮助你提高构建缓存命中率,那么你的镜像构建过程将会变得相当的缓慢。比如一个用于软件安装的bash脚本,里面堆砌着大量的curl、wget等命令语句,大家在写Dockerfile的时候通常就会像写这个bash脚本一样,将一系列的Docker命令堆砌在其中,这种Dockerfile在构建镜像的时候是比较低效和缓慢的。
秩序
当你正在为一个应用程序构建一个新的Dockerfile,在决定需要引哪些包、运行什么命令的时候肯定会进行很多次尝试,也会遇到很多的问题。优化你的Dockerfile确保命中“构建缓存”的概率越来越大,这样之后的每一次构建中会比前一次要更快一些。一般的规律是有频率的改变Dockerfile中命令的排序,观察分析运行命令所耗费的时间及与其他镜像共享资源的方式。
这就意味着像WORKDIR、CMD、ENV这些命令应该在底部,然而一个RUN apt-get -y update更新应该在上面,因为它需要更长时间来运行,也可以与你所有的镜像共享。
最后任何ADD(或其它缓存失效的命令)命令应该尽可能地在Dockerfile底部,在那里你有可能做出很多改变,然后后续命令缓存失效。
明智地选择你的基础镜像
在如Ubuntu这样的操作系统镜像和Python或Java7中一个特定的应用程序中,有很多基础镜像可供选择。常识告诉你使用Ruby2来运行基于Ruby应用程序并且使用Python3运行Python应用程序。但是现在你有两个几乎没有共同之处的基础镜像,所以你需要下载和构建。相反,如果你使用Ubuntu运行这两个程序,你只需要下载一次基础镜像。将层作为你的优势
在一个Dockerfile中每个命令都会在原来的基础上生成一层镜像。你可以很快的在三十多层的时候就结束了,这未必是一个问题,但也可以通过组合RUN命令,并使用一行EXPOSE命令列出你所有的开放端口,这样可以有效减少镜像的层数。通过将RUN命令分组,可以在容器间分享更多的层。当然如果你有一组命令可以多个容器通用,那么你应该创建一个独立的基础镜像,它包含你建立的所有镜像。
对于每一层来说你都可以跨多个镜像分享,这样可以节省大量的磁盘空间。
容器的体积
在创建容器并考虑到体积问题的时候,不要为了节省空间去使用体积小的镜像,尽量使用你将要提供数据的应用程序打成的镜像。如果你这样做了,并且提交了磁盘数据,你不仅在容器中储存了你的数据,而且对实际应用程序的调试也非常有用。消耗
当你已经构建了一个镜像,在运行它的时候发现有一个package缺少了,把它添加到Dockerfile的底部,而不是添加到顶部的run apt-get命令那里。这意味着你能尽快的重新构建这个镜像了。一旦你的镜像可以正常工作,你可以再提交源码之前重新优化整理Dockerfile。案例
如果一个Dockerfile是由类似于一个bash脚本写出来的,那么它可能会是这样的:FROM ubuntu:trusty MAINTAINER Paul Czarkowski "paul@paulcz.net" RUN apt-get -yq update # Apache RUN \ apt-get -yqq install \ apache2 \ apache2-utils \ libapache2-mod-python \ python-dev \ python-pip \ python-cairo \ python-pysqlite2 \ python-mysqldb \ python-jinja2 sqlite3 \ curl \ wget \ git \ software-properties-common RUN \ curl -sSL https://bootstrap.pypa.io/get-pip.py | python && \ pip install whisper \ carbon \ graphite-web \ 'Twisted<12.0' \ 'django<1.6' \ django-tagging # Add start scripts etc ADD . /app RUN mkdir -p /app/wsgi RUN useradd -d /app -c 'application' -s '/bin/false' graphite RUN chmod +x /app/bin/* RUN chown -R graphite:graphite /app RUN chown -R graphite:graphite /opt/graphite RUN rm -f /etc/apache2/sites-enabled/* ADD ./apache-graphite.conf /etc/apache2/sites-enabled/apache-graphite.conf # Expose ports. EXPOSE 80 EXPOSE 2003 EXPOSE 2004 EXPOSE 7002 ENV APACHE_CONFDIR /etc/apache2 ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_PID_FILE $APACHE_RUN_DIR/apache2.pid ENV APACHE_LOCK_DIR /var/lock/apache2 ENV APACHE_LOG_DIR /var/log/apache2 WORKDIR /app # Define default command. CMD ["/app/bin/start_graphite"]
然而这个Dockerfile的优化版本是基于之前所讨论的内容的,它看起来是这样的:
# 1 - Common Header / Packages FROM ubuntu:trusty MAINTAINER Paul Czarkowski "paul@paulcz.net" RUN apt-get -yq update \ && apt-get -yqq install \ wget \ curl \ git \ software-properties-common # 2 - Python RUN \ apt-get -yqq install \ python-dev \ python-pip \ python-pysqlite2 \ python-mysqldb # 3 - Apache RUN \ apt-get -yqq install \ apache2 \ apache2-utils # 4 - Apache ENVs ENV APACHE_CONFDIR /etc/apache2 ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_PID_FILE $APACHE_RUN_DIR/apache2.pid ENV APACHE_LOCK_DIR /var/lock/apache2 ENV APACHE_LOG_DIR /var/log/apache2 # 5 - Graphite and Deps RUN \ apt-get -yqq install \ libapache2-mod-python \ python-cairo \ python-jinja2 \ sqlite3 RUN \ pip install whisper \ carbon \ graphite-web \ 'Twisted<12.0' \ 'django<1.6' \ django-tagging # 6 - Other EXPOSE 80 2003 2004 7002 WORKDIR /app VOLUME /opt/graphite/data # Define default command. CMD ["/app/bin/start_graphite"] # 7 - First use of ADD ADD . /app # 8 - Final setup RUN mkdir -p /app/wsgi \ && useradd -d /app -c 'application' -s '/bin/false' graphite \ && chmod +x /app/bin/* \ && chown -R graphite:graphite /app \ && chown -R graphite:graphite /opt/graphite \ && rm -f /etc/apache2/sites-enabled/* \ && mv /app/apache-graphite.conf /etc/apache2/sites-enabled/apache-graphite.conf
1 - Common Header / Packages
这是最常见的共享层,在同一个主机上运行所有镜像应该从它开始。你可以看到我已经添加了一些诸如curl和git的操作,他们不是必须的,但是对调试很有用。而且因为他们在分享层,所以它们不会占用太多空间。2 - Python, 3 - Apache
现在说一下我们的语言规范。在这里我已经包含了python和apache的部分,因为到底把谁放在第一位并不十分清楚。如果我们把apache放在第一位,我们可以获得一个包含层和免费得到apache的ruby应用程序。4 - Apache Envs
我把这些单独说出来是为了以下这些原因。首先,镜像中添加Apache部分之后直接构建其他需要的部分模块,以便于在构建多个镜像的过程中尽量多应用公共缓存。你也许认为这并不重要,因为类似EVN的调用是很便宜的,但是我见到过随机的ENV调用耗费了10秒钟甚至更多时间。
有一个很好的例子:你可能想要在容器的底部启动,但这些命令不能被改变的,那么最好把他们移到略微靠前一点的地方。
其次,我真希望Docker能够在同一行指定多个环境,这样可以减少层数,最终提供了一种最简化的构建方式。
5 - Graphite and Deps
这包含了一些特定的apt和pip等资源包。你可以在一个单一的命令中加入他们,利用&&符号最为分隔符,如果需要修改只需要修改这条组合命令即可。6 - Other
这包含了一大堆简易的命令,如ADD和VOLUME,比起以前安装的包,他们更不可能改变,但运行的效率也不慢,所以在这些命令的缓存失效以后就会变得并不那么重要了。所以建议把类似的这些命令放在Dockerfile的底部。
7 - First ADD
你应该在最后面使用ADD命令。8 - Final setup
将这些类似ADD命令的操作放入最后一层。最后
希望这篇文章能够帮你编写一个更好的Dockerfile文件。这些都是我在构建我自己的镜像的时候所经历过的,虽然他们可能并不适用于所有情况(或可能是错误的),但他们确实提高了我的开发经验。原文链接:Optimizing your Dockerfiles (翻译:王康 翻译:李颖杰)
来自:http://dockerone.com/article/255