什么是Dockerfile?
Dockerfile本质是一个文件,可以理解为一个yml、XML配置文件。只不过它有它独特的语法,它的作用主要是用来构建自定义的Docker image,能够简化部署项目的成本。在使用Dockerfile构建好image后,使用image能够在任何支持docker的平台上运行项目,并且无需任何配置。
基本语法
Dockerfile的语法类似于计算机的分层解耦架构,自上而下的顺序,依次是
FROM
命令FROM debian:stretch
- 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为
scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像 - 如果你以
scratch
为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在 - 不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接
FROM scratch
会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一
- 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为
RUN
命令RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDepsRUN
指令是用来执行命令行命令的。由于命令行的强大能力,RUN
指令在定制镜像时是最常用的指令之一。每出现一次RUN,都代表在基础镜像的基础上创建了一层镜像,而镜像层数是有限制的(好像是127层),所以,要尽可能合并多条命令为一条,减少镜像层数。下面是一个反例RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis installRUN
命令的格式有两种- shell 格式
RUN <命令> 就像直接在命令行中输入的命令一样 - exec 格式
RUN [“可执行文件”, “参数1”, “参数2”]
- shell 格式
ADD
和COPY
命令很多时候,我们在构建镜像时,需要依赖本地的一些项目文件,这时
RUN
命令就显得力不从心了。COPY
命令和
RUN
指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。格式:
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY
指令将从构建上下文目录中<源路径>
的文件/目录复制到新的一层的镜像内的<目标路径>
位置。比如:复制
COPY package.json /usr/src/app/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。此外,还需要注意一点,使用
COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。在使用该指令的时候还可以加上
--chown=<user>:<group>
选项来改变文件的所属用户及所属组。复制
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD
命令语法和
COPY
类似。但它的功能更丰富,而相对的,它的行为不够透明。- 支持从URL获取资源(一般会使用
RUN curl/wget xxx
) - 支持自动将
tar.gz
文件解压缩
- 支持从URL获取资源(一般会使用
对于二者的使用建议是:
优先使用
COPY
,因为COPY
的行为更可预测,更容易调试同样地,每出现一次
ADD
或COPY
命令,都会创建一个新的镜像层,要尽量合并多条命令为一条。
构建镜像
有了Dockerfile文件后,我们就可以使用docker build [选项] <上下文路径/RUL>
命令来构筑镜像,例如:
docker build -t nginx:v5 . |
需要注意的是,命令中最后的. 代表指定上下文路径为当前目录(可以是相对路径也可以是绝对路径),并不是代表Dockerfile所在的路径,在COPY、ADD
中,源路径就是指的是这个上下文路径。
所以,在指定了上下文路径之后,经常需要将所需的文件先复制到上下问路径下,再进行镜像的构筑。
反思
那么,为什么docker build
命令需要指定一个上下文路径呢?
首先我们要理解 docker build
的工作原理。
Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker
命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker
功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
当构建的时候,用户会指定构建镜像上下文的路径,docker build
命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。