制作自己的Docker镜像主要有如下两种方式:
- 运行一个已有的镜像,启动镜像后,进入容器,进行相关的操作(安装、配置等)然后将容器保存为一个新的镜像
- 构建一个DockerFile(记录了镜像创建步骤),然后使用docker build创建一个新的镜像。
本文主要介绍通过DockerFile进行镜像的创建,commit的创建可以参考另外一篇文章。
Dockerfile是一个文本文档,其中包含用户可以在命令行上运行调用以生成镜像的所有命令。获得 Dockerfile 文件后,我们可以使用 docker build 来完成镜像的创建。
要创建一个 Dockerfile 文件,我们首先介绍一下 DockerFile
文件支持的指令和文件格式。
获得一个DokcerFile
通过已知的镜像获取DockerFile
dfimage
是一款第三方工具,可用来从镜像中提取 Dockerfile1
2alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage"
dfimage -sV=1.36 test:v1.0
如果我们有一个基本满足我们需求的镜像,但是可能这个镜像相对我们有一些冗余的功能,我们可以通过已知的镜像生成一个DockerFile进行微调。当然也可以通过这种方式了解一些镜像的构建过程,相比于 docker history
使用dfimage得到的镜像构建信息会更全面。
从头写一个 DockerFile
要自己写 DockerFile, 我们先来简单的了解一下DockerFile的语法。
Dockerfile 中使用#
来进行注释,同时Dockerfile支持多种不同的命令分别用以指定初始镜像(FROM
)、工作目录(WORKDIR
)、运行数据挂载和网络设置(RUN
)、命令行执行(CMD
)、设置环境变量(ENV
)、添加文件到镜像文件系统中(ADD
)、添加文件到容器文件系统中(COPY
)、创建挂载点(VOLUME
)、设置用户(USER
)、变量(ARG
)、镜像添加元数据(LABEL
)、监听网络(EXPOSE
)、入口点(ENTRYPOINT
)等等。
现在为大家介绍一些镜像构建过程中会用到的基本命令,一些指令在现在的个人进行镜像构建的过程中并未用到,暂时不进行扩展,大家有需要的时候可以参考官方文档DockerFile reference
DockerFile 指令说明
DockerFile指令 | 作用 | 示例 |
---|---|---|
FROM | 初始基础镜像,后续所有操作都是以基础镜像为初始环境 | FROM ubuntu:14.04 |
RUN | 用于执行后面跟着的命令行命令。有以下俩种格式。 | RUN <命令行命令> |
COPY | 复制指令,从上下文目录中复制文件或者目录到容器里指定路径。 | COPY [--chown=<user>:<group>] <源路径1>... <目标路径> |
ADD | 添加本地或远程文件和目录。 | |
ARG | 使用构建时变量。 | |
CMD | 指定默认命令。 | |
ENTRYPOINT | 指定默认可执行文件。 | |
ENV | 设置环境变量。 | |
EXPOSE | 描述您的应用程序正在侦听哪些端口。 | |
HEALTHCHECK | 在启动时检查容器的运行状况。 | |
LABEL | 将元数据添加到图像。 | |
MAINTAINER | 指定图像的作者。 | |
ONBUILD | 指定在构建中使用映像时的说明。 | |
SHELL | 设置图像的默认外壳。 | |
STOPSIGNAL | 指定退出容器的系统调用信号。 | |
USER | 设置用户和组 ID。 | |
VOLUME | 创建卷挂载。 | |
WORKDIR | 更改工作目录。 |
解析器指令
顾名思义,解析器指令和之前列的这些镜像操作指令不一样,解析器指令是用来配置DockerFile解释器如何解析文件的,
会影响 Dockerfile 中后续行的处理方式。解释器指令不是必须的,只有需要对解析逻辑进行调整时,需要配置。解析器指令不会向构建添加层,也不会显示为构建步骤。解析器指令以 # directive=value 形式编写为特殊类型的注释。单个指令只能使用一次。
解析器指令位于 Dockerfile 的顶部,否则都会视为注释。
1 | # 指定DockerFile的版本 |
ENV
环境变量(使用 ENV 语句声明)也可以在某些指令中用作由 Dockerfile 解释的变量。还可以处理转义以将类似变量的语法按字面意思包含到语句中。
1 |
|
word 可以是任何字符串,包括其他环境变量。1
2
3
4
5
6# 示例(解析后的表示显示在 # 之后):
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO} # WORKDIR /bar
ADD . $FOO # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux
在 ADD、COPY、ENV、EXPOSE、FROM、LABEL、STOPSIGNAL、USER、VOLUME、WORKDIR
中都支持使用环境变量。
** 环境变量替换在整个指令中对每个变量使用相同的值。更改变量的值仅在后续指令中生效。
From
1 | # 三种语法示例 |
FROM 初始化一个新的构建阶段并为后续指令设置基础镜像。因此,有效的 Dockerfile 必须以 FROM 开头。该镜像可以是任何有效的镜像(尽可能选择满足需求的最小镜像)。
- ARG是唯一可以位于FROM之前的指令
- FROM 可以在单个 Dockerfile 中出现多次,以创建多个镜像或使用一个构建阶段作为另一个构建阶段的依赖项。前面的FROM及对应的构建阶段都不会保存,只有最后一个阶段构建的镜像会被直接保存。
tag
或digest
值是可选的。如果省略其中任何一个,构建器默认采用 latest 标记。
RUN
RUN 指令将执行任何命令以在当前图像之上创建新层。添加的层将在 Dockerfile 的下一步中使用。 RUN 有两种形式:1
2
3
4
5
6
7 shell格式,<命令行命令> 等同于,在终端操作的 shell 命令。
RUN <命令行命令>
exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
例如:
RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
shell 形式是最常用的,它允许您将较长的指令分成多行,可以使用换行符转义,也可以使用下属方法:1
2
3
4
5#shell格式,<命令行命令> 等同于,在终端操作的 shell 命令。相当于命令行续航符"\"
RUN <<EOF
apt-get update
apt-get install -y curl
EOF
前面的RUN
构建过程相对容易,比较复杂的时相关文件系统的处理,因为可能在构建过程中,我们需要使用到一些特定的数据,这些数据我们需要在执行某些特定命令的时候,挂载到镜像上。 RUN –mount 允许您创建构建可以访问的文件系统挂载。这可以用于:
- 创建到主机文件系统或其他构建阶段的绑定挂载
- 访问构建机密或 ssh-agent 套接字
- 使用持久的包管理缓存来加快构建速度
类型 | 说明 |
---|---|
bind(default) | 用于挂载一个上下文目录(不能挂载宿主机的目录)到容器中 ,默认时只读的 |
cache | 主要用于挂载一个临时目录来缓存编译器和包管理器的目录。 |
tmpfs | 主要用于挂载一个tmpfs |
secret | 允许构建容器访问诸如私钥之类的安全文件,并且此类文件不会出现在构建好的镜像中,避免密钥外泄。 |
ssh | 允许构建容器通过SSH代理访问SSH密钥,并支持密码短语 |
这部分使用因为在自己业务场景中没有太大需求,这里介绍一些简单的示例。知道有这个功能就好,就不进行展开了。1
2
3
4
5RUN --mount=[type=TYPE][,option=<value>[,option=<value>]...]
# 挂载一个大小为100MB的临时文件系统到 /mnt 目录,并在其中执行命令
RUN --mount=type=tmpfs,size=100m,uid=1000,gid=1000,mode=0755 \
touch /mnt/test_file
CMD
CMD 指令设置从映像运行容器时执行的命令。
Dockerfile 中只能有一条 CMD 指令。如果列出多个 CMD ,则只有最后一个生效。
CMD 的目的是为执行容器(创建镜像阶段不会执行)提供默认值。这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定 ENTRYPOINT 指令。
COPY示例
1 |
SHELL
SHELL 指令用于更改执行命令的默认 shell。
Linux 上的默认 shell 是 [“/bin/sh”, “-c”] ,
Windows 上的默认 shell 是 [“cmd”, “/S”, “/C”] 。
SHELL 指令必须以JSON形式写入 Dockerfile
中。 SHELL 指令在 Windows
上特别有用,其中有两种常用且截然不同的本机 shell: cmd
和 powershell
,以及可用的备用其它 shell
。
1 | FROM microsoft/windowsservercore |
当 Dockerfile 中使用 shell 形式时,以下指令可能会受到 SHELL 指令的影响: RUN 、 CMD 和 ENTRYPOINT .
ARG
ARG 指令定义了一个变量,用户可以在构建时使用 –build-arg
ARG 变量定义从 Dockerfile 中定义它的行开始生效,而不是从命令行或其他地方使用参数开始生效。所以尽可能紧跟FROM 完成需要传递变量的定义,从而避免引用变量时,还未进行定义。
1 | FROM busybox |
- 每个构建阶段单独声明
ARG 指令在定义它的构建阶段结束时超出范围。要在多个阶段使用参数,每个阶段必须包含 ARG 指令。1
2
3
4
5
6
7FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
撰写DockerFile
了解了DockerFile的语法之后,我们可以利用对应的指令编写我们需要环境对应的一个 DockerFile。
再编写DockerFile的时候,我们需要注意几个点:
规范要求
将以 # 开头的行视为注释,除非该行是有效的解析器指令。行中任何其他位置的 # 标记都被视为参数。这允许这样的语句:
1
2
3RUN echo hello \
# comment
world约定俗成
- 该指令不区分大小写。然而,惯例是它们是大写的,以便更容易地将它们与参数区分开来。
注意事项
- 编写Dockerfile,Dockerfile中每一条/行指令都创建镜像的一层,所以尽可能合并无意义的层,避免镜像过大,例如:
这时候,也许会想,我们把所有环境安装都放到一层不就可以了么。
创建镜像的时候还要考虑其他问题:
不建议的写法(命令拆分产生无意义的多层镜像):1. 那就是镜像创建环境的网络稳定性。如果只有一层,而网络又不稳定的话,那么可能每次构建都会中途中断,然后需要从头重构。进行合理的分层,相当于设置一些构建过程中的一些重要节点(层),这样在进行build的时候,会记录保存中间层的构建,二次构建的时候,可以直接引用已有层的结果,即使中途中断,也可以从对应节点继续。 2. 如果需要使用的镜像环境非常多,我们需要构建多个镜像,但是他们有一些相同的底层镜像依赖。那么我们可以通过镜像分层构建共同的底座,然后通过继承的方式来构建不同的镜像。
1
2
3
4
5
6
7
8# 这里是注释
# 设置继承自哪个镜像
FROM ubuntu:14.04
# 下面是一些创建者的基本信息
MAINTAINER liubo (liubo4@genomics.com)
# 在终端需要执行的命令
RUN apt-get install -y openssh-server # 创建第一层
RUN mkdir -p /var/run/sshd # 创建第二层
建议的写法(如非必要只创建一层)1
2
3
4
5
6# 设置继承自哪个镜像
FROM ubuntu:14.04
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 在终端需要执行的命令
RUN apt-get install -y openssh-server && mkdir -p /var/run/sshd # 创建第一层
通过DockerFile生成镜像
编写完成 Dockerfile 后可以使用 docker build 来生成镜像。
首先创建一个新的目录,将 DockerFile 文件存档道目录中,执行 docker build 命令来生成镜像。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo docker build -t="birdben/ubuntu:v1" .
# 下面是一堆构建日志信息
############
我是日志
############
# 参数:
# -t 标记来添加 tag,指定新的镜像的用户和镜像名称信息。
# “.” 是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。
# 以交互方式运行docker
$ docker run -it birdben/ubuntu:v1 /bin/bash
# 运行docker时指定配置
$ sudo docker run -d -p 10.211.55.4:9999:22 ubuntu:tools '/usr/sbin/sshd' -D
# 参数:
# -i:表示以“交互模式”运行容器,-i 则让容器的标准输入保持打开
# -t:表示容器启动后会进入其命令行,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
# -v:表示需要将本地哪个目录挂载到容器中,格式:-v <宿主机目录>:<容器目录>,-v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
# -p:指定对外80端口
# 不一定要使用“镜像 ID”,也可以使用“仓库名:标签名”
在build阶段设置参数
Dockerfile 最后一行如下:
1
2
3
4
5
6
7
8
9
10
11
12
13[root@fangjike temp]# cat Dockerfile
FROM python:2.7-slim
MAINTAINER yellowtail
COPY startup.sh /opt
RUN chmod +x /opt/startup.sh
ARG envType=xxx
ENV envType ${envType}
CMD /opt/startup.sh ${envType}build
1
docker build -t yellow:4.0 --build-arg envType=dev .
run
1
2[root@fangjike temp]# docker run -ti --rm=true yellow:4.0
in startup, args: dev