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