一、Dockerfile 介绍
Dockerfile 是 docker 中用于定义镜像自动化构建流程的配置文件,在 Dockerfile 中,包含了构建镜像过程中需要执行的命令和其他操作。通过 Dockerfile 可以更加清晰、明确的给定 docker 镜像的制作过程,而由于其仅是简单、小体积的文件,在网络等其他介质中传递的速度极快,能够更快的帮助我们实现容器迁移和集群部署。
简单来说 Dockerfile 就是构建容器的过程。
Dockerfile 的定义就是针对一个名为 Dockerfile 的文件,没有扩展名,但本质就是一个文本文件,可以通过常见的文本编辑器或者 IDE 创建和编辑它。
Dockerfile 的内容很简单,主要以两种形式呈现,一种是注释行(以 # 开头),另一种是指令行,指令行拥有一套独立的指令语法,其用于给出镜像构建过程中所要执行的过程。Dockerfile 里的指令行,就是由指令与其相应的参数所组成。
二、Dockerfile 比容器的优势
- Dockerfile 的体积远小于镜像包,更容易进行快速迁移和部署。
- 环境构建流程记录了 Dockerfile 中,能够直观的看到镜像构建的顺序和逻辑。
- 使用 Dockerfile 来构建镜像能够更轻松的实现自动部署等自动化流程。
- 在修改环境搭建细节时,修改 Dockerfile 文件要比从新提交镜像来的轻松、简单。
三、Dockerfile 的结构
总体上来说,可以将 Dockerfile 理解为一个由上往下执行指令的脚本文件。当调用构建命令让 Docker 通过 Dockerfile 构建镜像时,Docker 会逐一按顺序解析 Dockerfile 中的指令,并根据它们不同的含义执行不同的操作。
Dockerfile 的指令简单分为五大类。
- 基础指令:用于定义新镜像的基础和性质。
- 控制指令:是指导镜像构建的核心部分,用于描述镜像在构建过程中需要执行的命令。
- 引入指令:用于将外部文件直接引入到构建镜像内部。
- 执行指令:能够为基于镜像所创建的容器,指定在启动时需要执行的脚本或命令。
- 配置指令:对镜像以及基于镜像所创建的容器,可以通过配置指令对其网络、用户等内容进行配置。
这五类命令并非都会出现在一个 Dockerfile 里,但却对基于这个 Dockerfile 所构建镜像形成不同的影响。
四、常见 Dockerfile 指令
1、FROM
通常来说,不会从零开始搭建一个镜像,而是会选择一个已经存在的镜像作为新镜像的基础。在 Dockerfile 里,可以通过 FROM 指令指定一个基础镜像,接下来所有的指令都是基于这个镜像所展开的。在镜像构建的过程中,Docker 也会先获取到这个给出的基础镜像,再从这个镜像上进行构建操作。
FROM 指令支持三种形式:
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
既然选择一个基础镜像是构建新镜像的根本,那么 Dockerfile 中的第一条指令必须是 FROM 指令,因为没有了基础镜像,一切构建过程都无法开展。但是一个 Dockerfile 以 FROM 指令作为开始并不意味着 FROM 只能是 Dockerfile 中的第一条指令。在 Dockerfile 中可以多次出现 FROM 指令,当 FROM 第二次或者之后出现时,表示在此刻构建时,要将当前指出镜像的内容合并到此刻构建镜像的内容里。
2、RUN
镜像的构建虽然是按照指令执行的,但指令只是引导,最终大部分内容还是控制台中对程序发出的命令,而 RUN 指令就是用于向控制台发送命令的指令。
在 RUN 指令之后,直接拼接上需要执行的命令,在构建时,Docker 就会执行这些命令,并将它们对文件系统的修改记录下来,形成镜像的变化。
RUN <command>
RUN ["executable", "param1", "param2"]
RUN 指令是支持 \
换行的,如果单行的长度过长,建议对内容进行切割,方便阅读。
3、ENTRYPOINT 和 CMD
基于镜像启动的容器,在容器启动时会根据镜像所定义的一条命令来启动容器中进程号为 1 的进程。而这个命令的定义,就是通过 Dockerfile 中的 ENTRYPOINT 和 CMD 实现的。
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
ENTRYPOINT 指令和 CMD 指令的用法近似,都是给出需要执行的命令,并且它们都可以为空,或者说是不在 Dockerfile 里指出。
当 ENTRYPOINT 与 CMD 同时给出时,CMD 中的内容会作为 ENTRYPOINT 定义命令的参数,最终执行容器启动的还是 ENTRYPOINT 中给出的命令。
ENTRYPOINT 和 CMD 的区别在于,ENTRYPOINT 指令的优先级高于 CMD 指令。当 ENTRYPOINT 和 CMD 同时在镜像中被指定时,CMD 里的内容会作为 ENTRYPOINT 的参数,两者拼接之后,才是最终执行的命令。
ENTRYPOINT | CMD | 实际执行 |
---|---|---|
ENTRYPOINT ["/bin/ep", “arge”] | /bin/ep arge | |
ENTRYPOINT /bin/ep arge | /bin/sh -c /bin/ep arge | |
CMD ["/bin/exec", “args”] | /bin/exec args | |
CMD /bin/exec args | /bin/sh -c /bin/exec args | |
ENTRYPOINT ["/bin/ep", “arge”] | CMD ["/bin/exec", “argc”] | /bin/ep arge /bin/exec argc |
ENTRYPOINT ["/bin/ep", “arge”] | CMD /bin/exec args | /bin/ep arge /bin/sh -c /bin/exec args |
ENTRYPOINT /bin/ep arge | CMD ["/bin/exec", “argc”] | /bin/sh -c /bin/ep arge /bin/exec argc |
ENTRYPOINT /bin/ep arge | CMD /bin/exec args | /bin/sh -c /bin/ep arge /bin/sh -c /bin/exec args |
ENTRYPOINT 和 CMD 设计的目的是不同的,ENTRYPOINT 指令主要用于对容器进行一些初始化,而 CMD 指令则用于真正定义容器中主程序的启动命令。
比如 redis 的 Dockerfile 文件 : https://github.com/docker-library/redis/blob/d42494ab2d96070c8d83f37a7542fbbffd999988/5.0/Dockerfile
# ...
ENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 6379
CMD ["redis-server"]
可以很清晰的看到,CMD 指令定义的是启动 Redis 的服务程序,而 ENTRYPOINT 使用的是一个外部引入的脚本文件。
创建容器时可以改写容器主程序的启动命令,而这个覆盖只会覆盖 CMD 中定义的内容,而不会影响 ENTRYPOINT 中的内容。
4、EXPOSE
在未做特殊定义的前提下,直接连接容器网络只能访问容器明确暴露的端口。可以在容器创建时通过选项来暴露这些端口,也可以在镜像中定义端口暴露。
通过 EXPOSE 指令为镜像指定要暴露的端口:
EXPOSE <port> [<port>/<protocol>...]
当通过 EXPOSE 指令配置了镜像的端口暴露定义,可以在被其他容器通过 --link
选项连接时,直接允许来自其他容器对这些端口的访问了。
注意 : EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
5、VOLUME
在 Dockerfile 里可以通过 VOLUME 持久化数据:
VOLUME ["/data"]
在 VOLUME 指令中定义的目录,在基于新镜像创建容器时,会自动建立为数据卷,不需要再使用 -v
选项来配置了。
6、COPY 和 ADD
在制作新的镜像的时候,可能需要将一些软件配置、程序代码、执行脚本等直接导入到镜像内的文件系统里,使用 COPY 或 ADD 指令能直接从宿主机的文件系统里拷贝内容到镜像里的文件系统中。
COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY 与 ADD 指令的定义方式完全一样,需要注意的仅是当的目录中存在空格时,可以使用后两种带引号格式避免空格产生歧义。
对比 COPY 与 ADD,两者的区别主要在于 ADD 能够支持使用网络端的 URL 地址作为 src 源,并且在源文件被识别为压缩包时,自动进行解压,而 COPY 没有这两个能力。
虽然看上去 COPY 能力稍弱,但对于那些不希望源文件被解压或没有网络请求的场景,COPY 指令是个不错的选择。
五、构建镜像
在编写好 Dockerfile 之后,就可以通过命令 docker build
构建镜像了:
$ sudo docker build ./webapp
docker build
命令可以接收一个参数,需要特别注意的是,这个参数为一个目录路径(本地路径或 URL 路径),而并非 Dockerfile 文件的路径。在 docker build
命令里,这个给出的目录会作为构建的环境目录,很多的操作都是基于这个目录进行的。例如,在我们使用 COPY 或是 ADD 拷贝文件到构建的新镜像时,会以这个目录作为基础目录。
在默认情况下,docker build
也会从这个目录下寻找名为 Dockerfile 的文件,将它作为 Dockerfile 内容的来源。如果的 Dockerfile 文件路径不在这个目录下,或者有另外的文件名,可以通过 -f
选项指定 Dockerfile 文件的路径。
$ sudo docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp
其中 -t
是指定新生成镜像的名称与版本。
六、变量
构建中使用参数变量
一些在构建时,需要在构建命令中传入的变量,可以用 ARG 指令来定义一个参数变量作为占位符,构建时通过构建指令传入这个参数变量,在 Dockerfile 里使用它。例如:版本号。定义的变量需要通过 $NAME
这种形式来占位。
# ...
ARG TOMCAT_MAJOR
ARG TOMCAT_VERSION
# ...
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
# ...
构建时通过 docker build
的 --build-arg
选项来设置参数变量:
sudo docker build --build-arg TOMCAT_MAJOR=8 --build-arg TOMCAT_VERSION=8.0.53 -t tomcat:8.0 ./tomcat
环境变量
在构建中经常用到的同一个值,可以用 ENV
指定为环境变量,方便在后面做统一处理。定义的变量也是使用 $NAME
这种形式来占位。
## ......
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
## ......
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
## ......
环境变量与参数变量的区别是,环境变量不但可以影响构建,还会影响通过这个镜像构建的容器,环境变量其实就是定义容器的系统变量,所以在系统中也是可以使用这些变量的。
由于环境变量是在 Dockerfile 中定义的,需要修改时,就需要修改镜像,但是可以在创建对应容器时使用 -e
或者 --env
选项修改环境变量:
$ sudo docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7
对于环境变量与参数变量有同样的变量名,ENV
指令定义的环境变量,永远会覆盖 ARG
所定义的参数变量,不管他们定义时顺序是怎么样的。
七、合并命令
在 Dockerfile 中,在 RUN 指令里面聚合了大量代码,实际上,多条代码聚合执行与逐一执行单条代码是没有太大区别的。下面两种写法基本上是一样的:
RUN apt-get update; \apt-get install -y --no-install-recommends $fetchDeps; \rm -rf /var/lib/apt/lists/*;
RUN apt-get update
RUN apt-get install -y --no-install-recommends $fetchDeps
RUN rm -rf /var/lib/apt/lists/*
但是在实际中,其实是前一种聚合写法居多。在 docker build
构建中,当一条能够形成对文件系统改动的指令在被执行前,Docker 先会基于上条命令的结果启动一个容器,在容器中运行这条指令的内容,之后将结果打包成一个镜像层。每有一个命令执行都会进行一遍这个操作,最终形成镜像。
镜像是由多个镜像层叠加而得,而这些镜像层其实就是在 Dockerfile 中每条对文件系统改动的指令所生成的。那么聚合指令的做法不但减少了镜像层的数量,也减少了镜像构建过程中反复创建容器的次数,提高了镜像构建的速度。
八、构建缓存
Docker 在镜像构建的过程中,还支持使用缓存策略来提高镜像的构建速度。其实缓存就是使用已经有的镜像。
由于镜像是多个指令所创建的镜像层组合而得,如果知道新编译的镜像层与已有的镜像层是一样的,那么完全可以直接利用之前构建的结果,而不需要再执行这条构建指令,这就是镜像构建缓存的原理。
Docker 是如何判断镜像层与之前的镜像间一样的呢?这主要参考两个维度:
- 所基于的镜像层是否一样
- 用于生成镜像层的指令的内容是否一样
基于这个原则,在条件允许的前提下,可以将不容易发生变化的搭建过程放到 Dockerfile 的前部,充分利用构建缓存提高镜像构建的速度。另外,指令的合并也不宜过度,而是将易变和不易变的过程拆分,分别放到不同的指令里。
在另外一些时候,可能不希望 Docker 在构建镜像时使用构建缓存,可以通过 --no-cache
选项来禁用它:
$ sudo docker build --no-cache ./webapp
九、demo:制作 springboot 项目的 dockerfile
https://blog.csdn.net/qq_37502106/article/details/103547307