九个编写Dockerfiles的常见错误

本文讲的是九个编写Dockerfiles的常见错误编者的话】我们每天基于Dockerfiles工作;所有运行的代码都来自一系列的Dockerfiles。这篇文章将会讨论编写Dockerfile时人们经常犯的错误以及如何改进。对于Docker专家说,这篇文章里的许多技巧可能会非常明显进而会得到很多的认同。但是对于初级到中级开发者,该文章将会是一份很有用的指南,它有助于理清以及加速你们的工作流程。

1. 执行 apt-get

执行apt-get install是每一个Dockerfile都有的东西之一。你需要安装一些外部的包来运行代码。但使用apt-get相应地会带来一些问题。

一个是运行apt-get upgrade 会更新所有包到最新版本 —— 不能这样做的理由是它会妨碍Dockerfile构建的持久与一致性。

另一个是在不同的行之间运行apt-get updateapt-get install命令。不能这样做的原因是,只有apt-get update的代码会在构建过程中被缓存,而且你需要运行apt-get install命令的时候不会每次都被执行。因此,你需要将apt-get update跟所要安装的包都在同一行执行,来确保它们正确的更新。

在以下 Golang Dockerfileapt-install命令就是一个不错的例子:

# From https://github.com/docker-library/golang
RUN apt-get update && \
apt-get install -y --no-install-recommends \
g++ \
gcc \
libc6-dev \
make \
&& rm -rf /var/lib/apt/lists/*

2. 使用ADD而非COPY

ADDCOPY是完全不同的命令。COPY是这两个中最简单的,它只是从主机复制一份文件或者目录到镜像里。ADD同样可以这么做,但是它还有更神奇的功能,像解压TAR文件或从远程URLs获取文件。为了降低Dockerfile的复杂度以及防止意外的操作,最好用COPY来复制文件。

FROM busybox:1.24

ADD example.tar.gz /add #解压缩文件到add目录
COPY example.tar.gz /copy #直接复制文件

3. 在一行内添加整个应用目录

明确代码的哪些部分以及什么时候应该放在构建镜像内或许是最重要的事了,它可以显著加快构建速度。

Dockerfile里经常会看到如下这些内容:

# !!! ANTIPATTERN !!!
COPY ./my-app/ /home/app/
RUN npm install # or RUN pip install or RUN bundle install
# !!! ANTIPATTERN !!!

这就意味着每次修改文件之后都需要重新构建那行以下的所有东西。多数情况下(包括上面的例子),它意味着重新安装应用依赖。为了尽可能地使用Docker的缓存,首先复制所有安装依赖所需要的文件,然后执行命令安装这些依赖。在复制剩余文件(这一步尽可能放到最后一行)之前先做这两个步骤,会使代码的变更被快速的重建。

COPY ./my-app/package.json /home/app/package.json # Node/npm packages
WORKDIR /home/app/
RUN npm install

或许还要安装python依赖?

COPY ./my-app/requirements.txt /home/app/requirements.txt
RUN pip install -r requirements.txt
COPY ./my-app/ /home/app/

这样做会确保构建尽可能快的执行。

4. 使用:latest标签

许多Dockerfiles在开头都使用FROM node:latest模板,用来从Docker registry拉取最新的镜像。简单地说,使用latest标签的镜像意味着如果这个镜像得到更新,那么Dockerfile的构建可能会突然中断。弄清这件事可能会非常难,因为Dockerfile的维护者实际上并没做任何修改。为了防止这种情况,只需要确保镜像使用特定的标签(例如:node:6.2.1)。这样就可以确保Dockerfile的一致性。

5. 构建镜像时使用外部服务

很多人会忽视构建Docker镜像与运行一个Docker容器的区别。在构建镜像时,Docker读取Dockerfile里的命令并创建镜像。在依赖或代码修改之前,镜像是保持不变以及可重复使用的。这个过程完全独立于其它容器。需要与其它容器或服务(如数据库)进行交互则会在容器运行的时候发生。

举一个例子,执行数据库迁移。很多人试图在构建镜像时执行此操作。这样做会导致许多问题。首先,在构建时数据库可能不可用,因为它可能没建在它将要运行的服务器上。其次,你可能想使用同一个镜像来连接不同的数据库(在开发或生产环境中),在这种情况下,如果它在构建过程中,迁移是不能进行的。

# !!! ANTIPATTERN !!!
COPY /YOUR-PROJECT /YOUR-PROJECT
RUN python manage.py migrate
# 尝试迁移数据,但是并不能
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
# !!! ANTIPATTERN !!!

6. 在Dockerfile开始部分加入EXPOSE和ENV

EXPOSE和ENV是廉价的执行命令。如果你破坏它们的缓存,几乎瞬时就可以重建。所以,最好尽可能晚地声明这些命令。在构建过程中应该直到需要的时候才声明ENV。如果在构建的时候不需要他们,那么应该在Dockerfile的末尾附加EXPOSE

再次查看Golang的Dockerfile,你会看到,所有ENVS都是在使用前声明的,并且在最后声明其余的:

ENV GOLANG_VERSION 1.7beta1
ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
ENV GOLANG_DOWNLOAD_SHA256 a55e718935e2be1d5b920ed262fd06885d2d7fc4eab7722aa02c205d80532e3b
RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
&& echo "$GOLANG_DOWNLOAD_SHA256  golang.tar.gz" | sha256sum -c - \
&& tar -C /usr/local -xzf golang.tar.gz \
&& rm golang.tar.gz
ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

如需修改ENV GOPATHENV PATH,镜像几乎会马上重建成功。

7. 多个FROM声明

尝试使用多个FROM声明来将不同的镜像组合到一起,这样不会起任何作用。Docker仅使用最后一个FROM并且忽略前面所有的。

所以如果你有这样的Dockerfile:

# !!! ANTIPATTERN !!!
FROM node:6.2.1
FROM python:3.5
CMD ["sleep", "infinity"]
# !!! ANTIPATTERN !!! 

那么docker exec进入运行的容器中,会得到下面的结果:

$ docker exec -it d86fcf0775d3 bash
root@d86fcf0775d3:/# which python
/usr/local/bin/python
root@d86fcf0775d3:/# which node
root@d86fcf0775d3:/#

这其实是GitHub上的一个问题:合并不同的镜像,但它看起来不会很快就增加的功能。

8. 多个服务运行在同一个容器内

这可能是了解Docker的开发者遇到的最大问题。而公认的最佳实践是:每个不同的服务,包括应用,应该在它自己的容器中运行。在一个Docker镜像里面加入多个服务非常容易,但是有一定的负面影响。

首先,横向扩展应用会变得很困难。其次,额外的依赖和层次会使镜像构建变慢。最终,增大了Dockerfile的编写、维护以及调试难度。

当然,像所有的技术建议一样,你需要用你的最佳判断。如果想快速安装一个Django+Nginx的应用的开发环境,那么让它们运行在同一个容器里面,同时生产环境中有一个不同的Dockerfile,让他们分开运行,是合理可行的。

9. 在构建过程中使用VOLUME

Volume是在运行容器时候加入的,而不是构建的时候。与第五个误区类似,在构建过程中不应该与你声明的volume有交互。相反地,你只是在运行容器的时候使用它。例如,如果在以下构建过程中创建文件并且在运行那个镜像时候使用它,一切正常:

FROM busybox:1.24
RUN echo "hello-world!!!!" > /myfile.txt
CMD ["cat", "/myfile.txt"]
...
$ docker run volume-in-build
hello-world!!!!

但是,如果我对一个存储在volume上的文件做同样的事,就不会起作用。

FROM busybox:1.24
VOLUME /data
RUN echo "hello-world!!!!" > /data/myfile.txt
CMD ["cat", "/data/myfile.txt"]
...
$ docker run volume-in-build
cat: can't open '/data/myfile.txt': No such file or directory

一个有趣的问题是:如果你前面的任何一个层次声明了一个VOLUME(也可能是几个FROMS)依然会遇到同样的问题。因此,最好留意一下父类镜像都声明了什么volume。如果遇到问题,请使用docker inspect检查。

结论

理解怎样写好一个Dockerfile将会是一个漫长的路程,它会带你理解Docker是如何工作的,同时也帮助你建立你的基础架构。理解Docker缓存会为你节省好多等待构建完成的时间!

原文链接:9 Common Dockerfile Mistakes (翻译:陈晏娥 校对:田浩浩 )

原文发布时间为:2016-06-16

本文作者:田浩浩 

本文来自合作伙伴Dockerone.io,了解相关信息可以关注Dockerone.io。

原文标题:九个编写Dockerfiles的常见错误

时间: 2024-12-05 10:53:37

九个编写Dockerfiles的常见错误的相关文章

两星级ASP版社区之星编写的ASP常见问题解答

解答|社区|问题 1 CDONTS.NewMail不能发邮件 1 确定已经安装了IIS的SMTP服务. 2 在IIS管理器中,设置smtp服务的属性.在中继对话框里设置"仅除以下列表以外".因为默认情况下它只给某列表中列出的服务器转信,但是这个列表是空的,所以发不出去. 2 无法登陆SQL SERVER 这是因为安装SQL SERVER时使用了NT验证模式,而ASP以匿名身份运行,不够资格访问数据库服务器.解决方法:把SQL SERVER改成混合验证模式(包含SQL验证) 3 ODBC

关于JSP中,中文问题(泛指字符问题),以及常见错误调试手记

js|错误|问题|中文 关于JSP中,中文问题(泛指字符问题),以及常见错误调试手记 sports98写于2001-1-9 序言:在论坛中,生活中,工作中,有不少和我一样属于鸟级的用户,在面对着不明白的问题的时候就盲目了,多交流,多写写日记就好的多了 文章内容关于:2001-01-08日,编写/调试[用户注册程序] 编写环境:WIN2K(简体中文版) server sp2 + TOMCAT4.0 + J2SDK1.3+MYSQL4.0(alpha)检测数据库内容环境:WIN2K(简体中文版) s

审查Java代码的十一种常见错误

代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效.由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug.并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来.本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误. 通常给别人的工作挑错要比找自己的错容易些.别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原

hadoop 集群常见错误解决办法

hadoop 集群常见错误解决办法: (一)启动hadoop集群时易出现的错误: 1.   错误现象:java.net.NoRouteToHostException: No route to host.    原因:master服务器上的防火墙没有关闭.    解决方法: 在master上关闭防火墙: chkconfig iptables off. 2.    错误现象:org.apache.hadoop.ipc.RPC: Server at JMN/10.22.1.203:9000 not a

[译] 学习 JavaScript:9 个常见错误阻碍你进步

本文讲的是[译] 学习 JavaScript:9 个常见错误阻碍你进步, 原文地址:Learning JavaScript: 9 Common Mistakes That Are Holding You Back 原文作者:Yaphi Berhanu 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:lekenny 校对者:lampui,Yuuoniy 很多人尝试学习 JavaScript ,但是不久就放弃了.然后他们就告诉自己,"JavaScript

万字长文深解金融科技50强、Gartner报告指出企业区块链十大常见错误 | AI金融评论周刊

业界  万字长文深解金融科技50强:Fintech领域上半场余热未散,下半场大战即将打响 在毕马威Fintech 50强的研讨会上,高瓴资本的董事总经理洪婧女士提到:"Fintech领域分为上下半场,上半场触达的游戏已经结束,下半场价值链的重构游戏才刚刚开始."通过对毕马威Fintech 50强回顾概览,作者表示,消费金融方面,在竞争如此激烈的态势下,场景入口和数据风控成为了消费金融领域的关键,相关企业的竞争力取决于是否具备结合场景.将数据变现的能力. 此外支付领域,"基于互

《C语言程序设计进阶教程》一3.2 常见错误

3.2 常见错误 本文讲的是C语言程序设计进阶教程一3.2 常见错误,这里是一系列我所见过我的学生编写程序中的常见错误(有时甚至是我自己也会犯的).很多学生向我保证他们再也不会犯这些错误.事实上是人们还是会犯这些错误,而且比他们想象中的要更经常.这一节只考虑编程错误,而非设计错误.设计上的错误需要一本另外的关于设计软件方面的书来讲述. 原文标题:C语言程序设计进阶教程一3.2 常见错误

Linux管理常见错误的解决方法

对于linux管理员来说有时会犯一些小的linux管理常见错误,但是是对于一些刚步入Linux管理大门的管理员来说,如果不避免一些常见的错误,就容易给单位的网络或系统带来安全风险.这里介绍十个linux管理常见的错误,以帮助新手来改进工作. linux管理常见错误一:不经过严格审核,从多种渠道下载安装各种类型的应用程序 乍看起来,这也许是一个不错的主意.如果你在运行Ubuntu,你会知道包管理程序使用的是.deb软件包.不过,你找到的许多应用程序是以源代码 的形式提供的.没有问题吗?这些程序安装

网站优化常见错误观点方法及优化方法

网站优化常见错误观点方法及优化方法 1.频繁修改网站的title,keyword,description. 那个时候做站,看到网络上今天这个关键字搜索不错,明天那个关键字搜索不错,就总是匆匆的改个题目啊,改个关键字啊,改个说名什么的.这一改不要紧,本来收录了很多的页一下都没了,本来在搜索能排在前2页的,一下就找不到哪里去了--后来在A5上,以及网络上学习其他只是才知道,这种情况估计是网站被降权甚至是被K了--当时那叫一个伤心啊...在这里奉劝各位新站长,千万不要把keyword,descript