Dockerfile 使用

一、Dockerfile 介绍

Dockerfile 是 docker 中用于定义镜像自动化构建流程的配置文件,在 Dockerfile 中,包含了构建镜像过程中需要执行的命令和其他操作。通过 Dockerfile 可以更加清晰、明确的给定 docker 镜像的制作过程,而由于其仅是简单、小体积的文件,在网络等其他介质中传递的速度极快,能够更快的帮助我们实现容器迁移和集群部署。

简单来说 Dockerfile 就是构建容器的过程。

Dockerfile 的定义就是针对一个名为 Dockerfile 的文件,没有扩展名,但本质就是一个文本文件,可以通过常见的文本编辑器或者 IDE 创建和编辑它。

Dockerfile 的内容很简单,主要以两种形式呈现,一种是注释行(以 # 开头),另一种是指令行,指令行拥有一套独立的指令语法,其用于给出镜像构建过程中所要执行的过程。Dockerfile 里的指令行,就是由指令与其相应的参数所组成。

二、Dockerfile 比容器的优势

  1. Dockerfile 的体积远小于镜像包,更容易进行快速迁移和部署。
  2. 环境构建流程记录了 Dockerfile 中,能够直观的看到镜像构建的顺序和逻辑。
  3. 使用 Dockerfile 来构建镜像能够更轻松的实现自动部署等自动化流程。
  4. 在修改环境搭建细节时,修改 Dockerfile 文件要比从新提交镜像来的轻松、简单。

三、Dockerfile 的结构

总体上来说,可以将 Dockerfile 理解为一个由上往下执行指令的脚本文件。当调用构建命令让 Docker 通过 Dockerfile 构建镜像时,Docker 会逐一按顺序解析 Dockerfile 中的指令,并根据它们不同的含义执行不同的操作。

Dockerfile 的指令简单分为五大类。

  1. 基础指令:用于定义新镜像的基础和性质。
  2. 控制指令:是指导镜像构建的核心部分,用于描述镜像在构建过程中需要执行的命令。
  3. 引入指令:用于将外部文件直接引入到构建镜像内部。
  4. 执行指令:能够为基于镜像所创建的容器,指定在启动时需要执行的脚本或命令。
  5. 配置指令:对镜像以及基于镜像所创建的容器,可以通过配置指令对其网络、用户等内容进行配置。

这五类命令并非都会出现在一个 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 param2

CMD ["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 的参数,两者拼接之后,才是最终执行的命令。

ENTRYPOINTCMD实际执行
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 argeCMD ["/bin/exec", “argc”]/bin/sh -c /bin/ep arge /bin/exec argc
ENTRYPOINT /bin/ep argeCMD /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 是如何判断镜像层与之前的镜像间一样的呢?这主要参考两个维度:

  1. 所基于的镜像层是否一样
  2. 用于生成镜像层的指令的内容是否一样

基于这个原则,在条件允许的前提下,可以将不容易发生变化的搭建过程放到 Dockerfile 的前部,充分利用构建缓存提高镜像构建的速度。另外,指令的合并也不宜过度,而是将易变和不易变的过程拆分,分别放到不同的指令里。

在另外一些时候,可能不希望 Docker 在构建镜像时使用构建缓存,可以通过 --no-cache 选项来禁用它:

$ sudo docker build --no-cache ./webapp

九、demo:制作 springboot 项目的 dockerfile

https://blog.csdn.net/qq_37502106/article/details/103547307

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值