学习 Docker(4)-构建镜像
镜像是生成容器的模版,镜像应该是无状态的,不包含具体的配置。
构建镜像有两种方式:从已有的容器构建,从 Dockerfile 构建。
从容器构建
基于容器创建一个新镜像。
|
|
该操作会将容器的文件更改或设置提交到新镜像中,但不包含容器中挂载的数据卷。
—-author
-a
指定镜像作者,例如 “name [email protected]”;-—message
-m
commit 信息;—-change
-c
创建镜像时,应用 Dockerfile 指令;--pause
-p
构建镜像时停止容器运行。
|
|
从 Dockerfile 构建
构建命令
|
|
该 docker build
命令从 Dockerfile 和 “上下文” 构建 Docker 镜像。上下文提供构建过程中引用的文件。
选项:
-t
指定镜像名称和 tag,可以有多个-t
参数;-f
指定 Dockerfile,默认为 上下文根目录下的Dockerfile
文件。--build-arg
指定构建时的参数,比如设置代理--build-arg HTTP_PROXY=http://10.20.30.2:1234
;
如下是一个使用参数的例子
|
|
使用 Dockerfile.debug
文件作为 Dockerfile 文件,使用当前目录作为上下文目录,生成两个镜像,名称为 whenry/fedora-jboss
,tag 分别为 latest
和 v2.1
。
传参:
- 传入
URL
和PATH
时,构建的上下文是位于PATH
或URL
中的文件集。 - 传入
-
表示从 STDIN 中读取 Dockerfile,此时没有上下文。
如果传入的参数是 URL
,该 URL 可以指向三种资源:Git 仓库、tarball 打包文件和纯文本文件。
Git 仓库
当 URL
参数指向 Git 仓库时,仓库为构建上下文。系统递归地获取仓库和子模块内容,但不会保留提交历史记录。
如果 URL 中包含片段(fragment),则会使用 git pull -—recursive
获取仓库的所有分支和子模块。
在 fragment 中,还可以指定上下文的配置,用 :
分隔。:
之前为分支、tag 或 remote ref,:
之后为仓库的一个子目录,该子目录将用作构建上下文。
例如下面的命令将使用 container
分支的 docker
目录作为上下文:
|
|
参考如下:
构建语法后缀 | 使用的提交 | 使用构建上下文 |
---|---|---|
myrepo.git | refs/heads/master | / |
myrepo.git#mytag | refs/tags/mytag | / |
myrepo.git#mybranch | refs/heads/mybranch | / |
myrepo.git#pull/42/head | refs/pull/42/head | / |
myrepo.git#:myfolder | refs/heads/master | /myfolder |
myrepo.git#master:myfolder | refs/heads/master | /myfolder |
myrepo.git#mytag:myfolder | refs/tags/mytag | /myfolder |
myrepo.git#mybranch:myfolder | refs/heads/mybranch | /myfolder |
Tarball 打包文件
如果将 URL 指向远程的 tarball 压缩文件,文件将会在运行 Docker 守护程序的主机被下载(该主机不一定是发出构建命令的主机),解压缩后作为构建时的上下文。
Tarball 上下文必须是符合标准 tar
UNIX 格式的 tar 档案,并且可以使用 xz
、bzip2
、gzip
或 identity
(无压缩)格式中的任何一种进行压缩。
如下,Docker 守护程序将获取 context.tar.gz
并将其用作构建上下文。
|
|
或者通过 -
从 STDIN 读取:
|
|
文本文件
通过 URL 指定一个远端的 Dockerfile 文件:
|
|
或者通过 -
和 STDIN 指定一个本地的 Dockerfile 文件:
|
|
这两种情况下,没有上下文可以使用,-f
和 --file
选项会被忽略。
Dockerfile 指令
Dockerfile 中的注释以 #
开头。
总览
FROM
从哪个基础镜像派生,必须是 Dockerfile 文件的第一条非注释指令;LABEL
生成镜像的一些信息,如name
、description
和maintainer
等等;RUN
构建时执行的命令,用于生成环境、复制文件等,RUN 命令可以有多条;EXPOSE
随机映射端口;CMD
和ENTRYPOINT
容器运行时执行的命令,只能有一条;ADD
和COPY
复制文件或目录到 Docker 镜像中;VOLUME
随机映射数据卷;WORKDIR
指定运行时工作目录,通常使用绝对路径;ENV
设置执行时环境变量;USER
指定运行时用户,格式为<name|uid>[:<group|gid>]
;ONBUILD
构建触发器,当该镜像被作为基础镜像构建其他镜像时执行;MAINTAINER
不推荐使用,请使用LABEL
替代;
shell 模式和 exec 模式
RUN、CMD 和 ENTRYPOINT 命令都有两种格式。以 RUN 举例:
shell 模式,形如 RUN <command>
,实际在构建过程中执行时命令为 /bin/sh -c <command>
,例如:
|
|
exec 模式,形如 RUN ["executable", "param1", "param2"]
|
|
区别在于:
- 运行时的环境变量(CMD 和 ENTRYPOINT),shell 模式能够访问,exec 模式不能访问,因为 exec 模式直接执行命令不通过 shell;
- 在 Dockerfile 中使用 ENV 定义的环境变量,shell 模式可以使用
${ENV_NAME}
或者$ENV_NAME
的形式访问,exec 模式不能访问; - 运行时(CMD 和 ENTRYPOINT),shell 模式命令不是 PID1 进程,exec 模式命令是 PID1 进程。
另外,exec 模式的指令会作为 JSON 列表被读取,因此要使用双引号而不是单引号。
RUN、CMD 和 ENTRYPOINT
RUN 命令在构建过程中执行。
CMD 和 ENTRYPOINT 指定了容器运行时的默认指令。
CMD 和 ENTRYPOINT 命令单独使用时:
- CMD 和 ENTRYPOINT 单独使用时只能有一条(如果有多条指令,只有最后一条会生效)。
- 如果容器运行时指定了运行指令,CMD 指令会被覆盖,但 ENTRYPOINT 指令会被执行;
- 如果容器运行时指定了
--entrypoint
参数,ENTRYPOINT 指令会被覆盖;
CMD 和 ENTRYPOINT 命令配合使用时(都要在 exec 模式下):
- ENTRYPOINT 指令作为运行指令,CMD 指令作为运行指令的参数;
- 容器运行时可以替换 CMD 中指定的运行参数;
举个例子,以下 Dockerfile 文件构建的镜像
|
|
执行时
|
|
ADD 和 COPY
都可以复制文件或目录到 Docker 镜像中。
共同点:
- 源路径最好是相对路径(相对构建上下文目录),只能是上下文目录的子目录或文件;
- 目标路径可以是 Docker 镜像中的绝对路径,或相对于 WORKDIR 的相对路径;
不同点:
- ADD 可以接受本地的 tar 压缩文档并解压复制,或者远程的目录。
如果只是单纯复制文件,使用 COPY 就可以;
VOLUME
指定数据卷挂载点,容器运行时可以在该挂载点上挂载数据卷。
格式可以是 JSON 字符串数组,或者以空格分隔的普通字符串,以下是等价的:
|
|
构建过程
使用 Dockerfile 的构建过程
- 从基础镜像运行一个容器;
- 执行 Dockerfile 中的下一条指令,对容器作出修改;
- 执行类似 commit 的操作,提交一个新的镜像;
- 基于刚提交的镜像运行一个新容器;
- 重复步骤 2~4,直到 Dockerfile 中的所有指令执行完成;
- 删除中间层容器。
从以上过程可以看出,Dockerfile 构建过程中会生成中间层镜像并被保留。
使用 docker history <image name>
可以查看一个镜像的构建过程:
|
|
构建缓存
Docker 在构建时,如果曾经执行过相同的操作,会调用之前构建时生成的镜像缓存,从而加快构建速度。
但一些情况下,我们不希望使用构建缓存,比如构建时执行 apt update
等指令,我们希望获取的是最新的软件包版本。此时有两种方法可以避免使用缓存:
- build 时使用
—-no-cache
参数; - 在 Dockerfile 中使用
ENV REFRESH_DATE XXX
来标识缓存刷新时间,在该时间后进行构建不会使用上次的缓存。
提高构建效率
- 合理使用构建缓存;
- 仅向上下文目录中添加 Dockerfile 文件和构建所需的文件;
- 使用
.dockerignore
排除上下文目录中的文件和目录。