为了账号安全,请及时绑定邮箱和手机立即绑定

Dockerfile 参考手册(一)

标签:
Docker

最近学习docker过程中,发现Dockerfile是一个非常重要的文档,本文系统学习一下。
文档是基于Docker v17.09 版本。
翻译作品,原文请见官网英文文档

00 前言

Docker可以读取Dockerfile中的指令自动构建镜像,Dockerfile是一个文本文件,它包含很多命令,用户可以在命令行上调用这些命令组装镜像。用户可以使用docker build来自动构建镜像,它可以连续执行若干命令行指令。

本文将介绍在Dockerfile中你可以使用命令,你读完这篇文章之后,Dockerfile
Best Practices
是另一篇很好的指导。

01 用法

docker build命令根据Dockerfile和上下文来构建镜像,构建过程的上下文是通过PATH或者URL指定的一系列文件。PATH是一个本地文件系统的目录,URL是一个Git仓库的位置。

上下文是一个递归的处理过程。因此,PATH可以包含任何的子目录,URL`包括仓库和它的子模块。下面是一个构建镜像的命令的示例,使用当前目录作为上下文:

$ docker build .
Sending build context to Docker daemon  6.51 MB
...

Build是通过Docker daemon(docker 守护进程),而不是 CLI(命令行界面)执行的。Build过程要做的第一件事是发送整个上下文(递归)到Docker的守护进程。最佳实践是,开始创建一个空的文件夹作为上下文,然后将你的Dockerfile文件放在那个文件夹下,仅添加一些你在编译Dockerfile过程中需要的文件。

注意:千万不要使用根路径 / 作为PATH,这将导致Build会发送你的硬盘上的所有内容到Docker的守护进程。

在Build的上下文中为了使用Dockerfile中指定的一个文件,这个文件是某个指令(例如COPY指令)用到的。为了提高Build的性能,通过添加.dockerignore文件,可以排除上下文目录中的某些文件和目录,关于如何创建.dockerignore文件更多信息见本文的下面章节。

一般认为,Dockerfile文件都应该位于上下文的根目录下,你可以在docker build后使用-f标识来指定你的文件系统中任意位置的Dockerfile文件。

$ docker build -f /path/to/a/Dockerfile .

你还可以指定用来存储成功编译的镜像文件的仓库和标签:

$ docker build -t shykes/myapp .

Build的时候也可以为镜像添加多个仓库标签,在你执行Build命令的时候添加多个-t参数即可:

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

Docker守护进程在执行Dockerfile中的指令之前,会首先对Dockerfile做一个初步校验,如果有语法错误,它会返回一个错误:

$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kBError response from daemon: Unknown instruction: RUNCMD

Docker守护进程是逐步执行Dockerfile中的指令的,如果需要的话,会提交每个指令的结果到新的镜像中,最后输出新镜像的的ID。Docker的守护进程也会自动清除你发送的上下文。

注意,每一条指令都是独立执行的,因此在创建一个镜像的时候,RUN cd /tmp这条指令不会对下一条指令有任何影响。

无论任何可能的时候,Docker都将会重用中间状态(缓存)的镜像,这样能够明显地加速docker build的过程,这是通过控制台输出的信息Using cache来标识的。(更多信息参见,在Dockerfile的最佳实践指导中的Build cache section):

$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
 ---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
 ---> Using cache
 ---> 2a1c91448f5f
Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
 ---> Using cache
 ---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
 ---> Using cache
 ---> 7ea8aef582cc
Successfully built 7ea8aef582cc

仅在编译那些有具有本地主链的镜像时使用缓存,意思是这些镜像的创建依赖前面的Build,或者整个镜像链都已经通过docker load加载进来了。如果你希望对一个指定镜像使用build cache,你可以使用--cache-from来指定,通过--cache-from指定的镜像不需要有一个主链,也可能是从其他的中心拉取的。

当你编译完成的时候,你该学习 Pushing a repository to its registry

02 格式

下面是Dockerfile文件的格式:

# CommentINSTRUCTION arguments

指令对字母大小写是不敏感的,但是,习惯上将它们大写,以便容易和参数区分开。

Docker是按照顺序来执行Dockerfile中的指令的。一个Dockerfile文件必须以FROM指令开始,FROM指令指定了你正在编译镜像的基础镜像。在Dockerfile文件中,FROM指令的前面仅可以是一个或者多个ARG指令,这些声明的参数被用于FROM指令。

Docker认为以#开头的行是注释,除非这一行是一个有效的转义的指令。#标识出现在一行的任何其它地方,都会被认为是一个参数。就像下面这段:

# CommentRUN echo 'we are running some # of cool things'

注释中不支持继续字符。

03 转义指令

转义指令是可选的,它会影响在Dockerfile中后续行的处理方式。转义指令并不会添加任何层到构建的镜像中,也不会作为构建一个步骤展示,转义指令是被写作一个特殊类型的注释,形式为# directive=value,一个指令可能只会被使用一次。

一旦有一行注释、空行或者编译指令被执行,Docker就不会再检查转义指令了,而是将任何格式的转义指令认为是注释,不会尝试去验证它是否是转义指令。因此所有的转义指令必须放在Dockerfile文件的第一行。

转义指令不是大小写敏感的,但是通常使用小写的形式,习惯上任何的转义指令后面都跟一个空行。转义指令不支持续行符。

根据上面这些规则,下面是一些无效的转义指令的例子:

由于续行符,导致无效:

# direc \tive=value

由于出现两次,导致无效:

# directive=value1# directive=value2FROM ImageName

由于出现在了编译指令之后,被当作了注释:

FROM ImageName# directive=value

由于出现在了注释之后,被当作了注释,而不是转义指令:

# About my dockerfile# directive=valueFROM ImageName

未知的指令由于无法识别被当作了注释,另外一个已知的指令由于出现在了注释的后面,被当作了注释而不是转义指令。

# unknowndirective=value# knowndirective=value

转义指令中允许出现非断行的空格,所以下面几行都是相同的:

#directive=value# directive =value#   directive= value# directive = value#     dIrEcTiVe=value

下面的转义指令是支持的:
escape

04 转义符指令

# escape=\ (backslash)

或者

# escape=` (backtick)

escape指令是用来设置Dockerfile中转义字符的字符,如果不指定的话,默认的转义字符是\

转义字符不仅用在一行中的转义字符上,也用在开启一个新行。Dockerfile中指令允许是多行的。注意,无论在Dockerfile中是否包含escape转义指令,在RUN命令中是不会执行转义的,除非是在一行的末尾。

在Windows环境下,设置转义字符为 ` ,是非常有用的,由于\是目录路径的分隔符,`和windows下的转义字符是一致的。

考虑下面的一个例子,在windows环境下是失败的,在第二行的第二个\被解释成了换行的转义符,而不是被第一个\转义了的目标,同样的,在第三行末尾的\也是,它们被认作是一个指令,\被认为是续行符。这个Dockerfile的结果就是第二行和第三行被认为是一行指令:

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

结果是:

PS C:\John> docker build -t cmd .
Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS C:\John>

一个解决办法是,上面都使用/作为COPY指令和dir的目标。然而,最好的情况下,这只是看着windows下的路径不自然,最坏的情况下,并不是所有的windows命令都支持/作为路径分隔符。

另一种解决办法,添加一个escape转义指令,下面的Dockerfile成功的执行,如预期的一样windows平台很自然路径表示语义:

# escape=`FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

结果是:

PS C:\John> docker build -t succeeds --no-cache=true .
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
 ---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
 ---> Running in a2c157f842f5
 Volume in drive C has no label.
 Volume Serial Number is 7E6D-E0F7

 Directory of c:\10/05/2016  05:04 PM             1,894 License.txt10/05/2016  02:22 PM    <DIR>          Program Files10/05/2016  02:14 PM    <DIR>          Program Files (x86)10/28/2016  11:18 AM                62 testfile.txt10/28/2016  11:20 AM    <DIR>          Users10/28/2016  11:20 AM    <DIR>          Windows           2 File(s)          1,956 bytes           4 Dir(s)  21,259,096,064 bytes free
 ---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS C:\John>

05 环境变量占位符

环境变量(ENV声明)可以被用在某些指令中作为变量(可以被Dockerfile解释)。转义指令也可以用于处理语句中包含类似变量的语法。

环境变量在Dockerfile中表示为$variable_name 或者 ${variable_name},他们是等效的,大括号的语法通常用来强调没有空格的变量名,例如${foo}_bar${variable_name}语法也支持一些标准的bash修饰符,例如下面:

  • ${variable:-word}意思是,如果variable被设置了,结果将是那个值,如果variable没被设置,那个word就是结果。

  • ${variable:+word}意思是,如果variable被设置了,word就是结果,否则结果就是空。

以上所有情形,word可以是任何字符串,包括其它的环境变量。

转义可以在变量之前添加\:例如,\$foo或者\${foo}将被转义为$foo${foo}两个常量。

举个例子(转义之后的结果展示在#的后面):

FROM busybox
ENV foo /bar
WORKDIR ${foo}   # WORKDIR /barADD . $foo       # ADD . /barCOPY \$foo /quux # COPY $foo /quux

环境变量在下面这些Dockerfile指令中都是支持的:

  • ADD

  • COPY

  • ENV

  • EXPOSE

  • FROM

  • LABEL

  • STOPSIGNAL

  • USER

  • VOLUME

  • WORKDIR

此外还有:

  • ONBUILD(当与上面任何一个指令结合时)

注意:在1.4版本之前,ONBUILD是不支持环境变量的,即使与上面列出的指令结合时。

在整个指令中环境变量的替换值都是用同一个值,换句话说,就是下面的例子:

ENV abc=hello
ENV abc=bye def=$abcENV ghi=$abc

结果是,def的值是hello,而不是byeghi的值是bye,因为它不是设置abcbye的指令的一部分。

06 .dockerignore文件

在docker命令行界面中发送上下文到docker的守护进程之前,它会检查上下文目录根路径下名为.dockerignore的文件,如果这个文件存在,命令行界面会修改上下文,排除那些被.dockerignore中的模式匹配到的文件和目录。这有助于避免一些不必要的(大的或者敏感的文件和目录)发送到守护进程,还能避免一些潜在的使用ADD 或者 COPY添加文件和目录到镜像中。

命令行解释.dockerignore文件为一个换行符分割的模式列表,类似于Unix shell的glob文件。由于这个匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar都是在排除目录foo下面一个叫bar的文件或者目录,目录fooPATH的子目录或者URL指定的git仓库下的子目录。不排除任何其它的。

如果在.dockerignore文件中有一行以#开头,那么这一行被认为是注释,命令行解释之前为忽略它。

下面是一个.dockerignore文件的例子:

# comment*/temp*
*/*/temp*
temp?

这个文件将引发下面的构建行为:

规则行为
# comment忽略。
*/temp*排除根目录下的子目录中任何以temp开头的文件和目录,例如,/somedir/temporary.txt 这个文本文件会被排除,/somedir/temp这个目录也会被排除。
*/*/temp*排除来自子目录的任何以temp开头的文件和目录,这个子目录是根目录下两层,例如,/somedir/subdir/temporary.txt被排除的。
temp?排除那些根目录下名字以temp开始拓展一个字符的文件和目录,例如,/tempa/tempb是被排除的。

完成这个匹配使用的是Go语言的文件路径匹配规则,在预处理步骤中会去除掉开头和结尾的空格,并清除...元素,在这个过程中使用的是Go语言的文件路径清理方法,预处理过程中会忽略掉空白行。

在Go语言的文件路径匹配规则之外,Docker还支持一个特殊的通配符**,用于匹配任意数量的目录(包括零),例如,**/*.go将排除所有以.go结尾的文件,它会在编译上下文的根目录的所有目录中找。

以感叹号!开始的行被用于标出排除中的异常文件,下面的这个.dockerignore文件的例子就使用了这种机制:

*.md!README.md

在上下文中除了README.md之外,所有markdown文件都会被排除。

异常规则!的位置影响行为:.dockerignore文件的最后一行匹配一个特定文件,它是包含还是排除呢?看下面的例子:

*.md!README*.mdREADME-secret.md

除了README 文件之外,没有任何markdown文件被包含进上下文,并没有README-secret.md

现在看这个例子:

*.mdREADME-secret.md!README*.md

所有的README文件都会被包含进去,中间一行是没有任何影响的,因为!README*.md 能够匹配 README-secret.md,并且在后面。

你甚至可以用.dockerignore来排除Dockerfile文件和.dockerignore,这些文件仍然是会被送到守护进程的,因为需要它们做这些工作,但是ADDCOPY指令是不会copy它们到镜像中去的。

最后,你可能想要指定文件包含进上下文,而不是排除它们,为了实现这个目的,可以使用*作为第一个模式,下面使用一个或者多个!异常模式。

注意:由于历史原因,模式.是被忽略的。

到此为止介绍Dockerfile文件中工作原理和一些语法,以及相关的一些东西,其中03和04节不太常用,翻译不是太好,请高手指正。Dockerfile中常用的指令下一篇文章再介绍。



作者:rabbitGYK
链接:https://www.jianshu.com/p/24327e27d6c3


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
51
获赞与收藏
178

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消