转载

Dockerfile实践优化建议

【编者的话】Dockerfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令。Docker程序将这些Dockerfile指令翻译真正的Linux命令。类似于Makefile,Dockerfile有自己书写格式和支持的命令,Docker程序解决这些命令间的依赖关系。下面是resin.io关于Dockerfile编写经验和建议的总结。

上个月,Docker发起了 Docker Global Mentor Week 2016 ,旨在帮助开发者用户提高各项技术水平。在resin.io技术栈中, Docker 是一个关键的技术之一,而且我们也积攒了很多与Docker关联的最佳实践经验、注意事项、以及提高resin.io开发经验的小技巧。Docker本身已经有很多优秀的 实践范例 ,但并不是所有的场景都在resin.io使用。根据Global Mentor Week的议题精神,在这篇博客中我们整理了关于resin.io应用程序和硬件设备使用Docker场景的一些常见问题。

文章主要分为两个部分:1,必须在实践中使用的; 2,提示部分,建议使用可以提高代码质量和经验,但是并非时强制的。

必须使用部分

以下这些实践经验能在开发中为您缩减痛苦过程。

固定软件版本

固定所有依赖的版本是实现良好实践最佳途径。这包括基本映象,从Github中提取的代码,代码依赖的库等等。通过版本控制,您可以简化应用程序已知的工作版本。

如果没有版本控制,您的组件很容易改变,导致以前工作的Dockerfile不能再构建。

您可以在resin.io官方 Docker Hub 拉取基础映象最新的 可用版本 ,可以依据基础映象的Tags查询选择。例如,使用resin/raspberrypi3-debian关键字搜索列出映象,按照更新日期排序版本的新旧,应当选择当时最新的jessie-20161119版本而不是jessie版本。

FROM resin/raspberrypi3-debian:jessie-20161119

基础映象的架构会发现变化(这种情况极少,但是也是存在的),而使用日期标记排序,就可以标识处稳定可用的最新映象版本。(这样对于Docker来说,他们就一直可以下载可用版本)

一个棘手的事情是固定操作系统中使用 包安装器 安装的软件的版本问题,再Debian中,运行apt-get安装特定的版本信息,例如:

RUN apt-get update && /  

apt-get install -yq --no-install-recommends /

  i2c-tools=3.1.1-1 /

...

Debian软件包 , Alpine软件包 和 Fedora软件包 及其各自的软件包管理器也是如此。 如果你已经安装了大量的软件包,这需要花更多的时间设置版本信息,但是从长远来看它是值得的。

通常,您将从版本控制(例如从git / GitHub)安装软件,在这种情况下,没有理由不使用由唯一ID(如git的hash / SHA)定义的特定提交或标签 。 下面是一个如何使用git检出代码的特定标记版本的示例:

Can use tag or commit hash to set MRAAVERSION

ENV MRAAVERSION v1.3.0  

RUN git clone https://github.com/intel-iot-devkit/mraa.git && /  

cd mraa && /

git checkout -b build ${MRAAVERSION} && /

...

最终,安装版本都来自于不管 任何库 申请都是固定的版本,不论使用了requirements.txt (Python管理安装模块), package.json (Node.js管理安装模块), Cargo.toml (Rust管理安装模块),或者是其他语言的管理的安装包管理器,这样就总是固定版本(或者是经常锁定冻结)依赖版本号或者唯一提交。

自我清理

普遍来讲,加快计算机程序最好的方式之一是消除不必要计算(做的更少)。通常来讲,软件部署也是如此,加快部署和更新的最佳方式不发送不需要的代码。所以,自身来讲从容器中 清除不必要的代码 ,可以提高效率。

什么是不需要的代码? 最常见的是,它们是保存在包管理器中的临时文件或者是在Dockerfile中构建和安装的软件源代码。

在包管理器之后 清理的方式 取决于在您的基本映像中使用的分发方式。 在Debian和Raspbian的情况下使用的是apt-get,Docker已经有 很多建议 Dockerfile中使用 apt-get。 最后,完成安装步骤,删除临时信息,如下:

RUN apt-get update && /  

apt-get install -yq --no-install-recommends /

  <packages> /

&& apt-get clean && rm -rf /var/lib/apt/lists/*

上面的最后一行通过apt-get rm删除了设备上不需要的的临时文件.

如果你使用Alpine Linux, apk包管理工具 有一个方便的--no-cache选项:

RUN apk add --no-cache <package>

Fedora系统中,dnf 包管理器 可以通过apt-get简单处理:

RUN dnf makecache && /  

dnf install -y /

  <packages> /

&& dnf clean all && rm -rf /var/cache/dnf/*

清除已安装软件的源代码通常非常简单,只需删除在生成过程的早期步骤中创建的目录即可。 为了保持上面的MRAA示例,通过git checkout后通过这个方式执行清理:

ENV MRAAVERSION v1.3.0  

RUN git clone https://github.com/intel-iot-devkit/mraa.git && /  

cd mraa && /

git checkout -b build ${MRAAVERSION} && /

<some build steps>

make install && /

cd .. && rm -rf mraa

还要确保所有的清理语句 在同一个部分运行 ,否则它们将看起来清除,但最终仍然存在于Docker容器成为残留。

组合运行语句

上面的最后一个注释引导必须要做,这就要把逻辑上属于一起操作步骤的语句合并进入Dockerfile中,这样以避免类似缓存和不必要地使用磁盘空间有关常见问题。 首先,由于缓存,您可能会有意外的构建结果。 如果您的apt-get更新步骤是从apt-get install <package>步骤单独运行的,则前者可能会被缓存,并且不会在您期望更新的时候更新。 如果你分离你的git克隆和实际构建,类似的事情也可能发生。其次,在单独的后续RUN步骤中删除的文件保留在最终容器中,但不可访问(残留)。

Docker文档有更多的注释和建议说明 。

推荐的实现

强烈推荐以下做法,通常情况下这是从优秀到卓越的经验,但是并不一定是一个瓶颈。

整理 Dockerfile 语句

Docker尝试缓存您的Dockerfile中尚未更改的所有步骤,但如果更改任何语句,将重做其后的所有步骤。 您可以在构建过程中节省相当多的时间,只要有可能,就尽可能按照最不可能更改的顺序编写Dockerfile。 例如,一般设置,如设置工作目录,启用initsystem,设置维护应该更早发生。

MAINTAINER Awesome Developer <awesome@developer.net>  

WORKDIR /usr/src/app  

ENV INITSYSTEM on

这些语句执行之后,可以使用操作系统的软件包管理器安装软件,然后编译依赖关系,启用系统服务和其他设置等。 例如,在Dockerfile末尾的这一部分执行,你应该安装Python:

COPY requirements.txt ./  

RUN pip install -r requirements.txt

或者 Node.js 依赖.

COPY package.json ./  

RUN npm install

复制应用程序源代码的做法放应该在最后,这也是最常用的做法。我们可以适用复制命令:

COPY . ./

这样就可以加快构建部署的过程,而且Dockerfile文件的可读性强!上面的例子只供参考,逻辑上实现可以依赖域当前应用程序的部分。

使用 .dockerignore

接着上一步,我们总是定义一个.dockerignore文件,这个文件是用来区分源码中那些是非必须的设备文件,那些不用经过Copy ../拷贝步骤。忽略的文件可是是README.md或者其他的文件,或者是图片、文件、其他不需要要求应用程序的功能而又必须放在一个库或者其他原因的文件。

使用启动脚本

创建和调试比较大型的项目,我们还有一个建议:不要直接使用CMD命令运行,建议时用一个开始脚本,每次调用运行:

CMD ["bash", "start.sh"]

然后在start.sh脚本文件中你可以使用python app.py之类的命令启动、运行你的应用程序。这样的优势是可以很方便扩展运行脚本文件,增加调试功能,而不用在CMD中一步一步执行。

核心代码发布之前你想增加一些调试信息?仅仅增加几条你想要的几条测试逻辑?

另一方面,你可以使用 Resin sync 可以提高我们的开发进度。Resin sync可以直接拷贝源代码到正在运行的设备中实现更新(不用重新编译Dockerfile文件),然后重启容器加载新的配置即可。然而,这些只有在Docker容器没有缓存的情况下才能生效,例如通过cmd直接重定向的。

创建 Non-Root User

Docker的默认设置中,容器中的代码都是通过Root账户运行的。作为一个良好的预防性安全实践,建议创建一个Non-Root用户,授予它只需要尽可能多的特权即可。

例如:

RUN useradd --user-group --shell /bin/false resin  

USER resin

上面命令是创建一个resin用户,后续的步骤中都使用这个用户。 Docker docs 上有更详细的说明,或者参考这个 博客 。

总结

通过检查我们的 编译优化文档 和 Docker最佳实践经验文档 (那些已经应用),我们可以更进一步的了解。你可能也想看看 Dockerfile Linter 的一些优化建议。

原文链接: dockerfile-tips-and-tricks (翻译:ylzhang)

原文  http://dockone.io/article/2034
正文到此结束
Loading...