Dockerfile 构建镜像(十)
一、Dockerfile简介
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
1、镜像构建步骤
-
编写一个 dockerfile 文件
-
docker build 构建成为一个镜像
-
docker run 运行镜像
-
docker push 发布镜像(咱们可以发布到 DockerHub,也可以发布到阿里云上面)
2、镜像结构图
通过如下架构图可以看出通过 DockerFile 可以直接构建镜像:
3、 Dockerfile 解析过程:
3、Dockerfile 基本结构
二、Dockerfile 指令
1、Dockerfile 指令结构图
2、Dockerfile 指令表
命令 | 运行阶段 | 描述 |
---|---|---|
FROM | BUILD | 指定基础镜像,必须为第一个命令。 |
MAINTAINER | BUILD | 构建指令,用于将镜像制作者相关的信息写入到镜像中。 |
RUN | BUILD | 用于在镜像容器中执行命令。 |
COPY | BUILD | 拷贝文件,不会自动解压文件,也不能访问网络资源。 |
ADD | BUILD | 将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget。 |
LABEL | BUILD | 用于为镜像添加元数据 |
ARG | BUILD | 用于指定传递给构建运行时的变量(给dockerfile传参),相当于构建镜像时可以在外部为里面传参 |
ONBUILD | BUILD | 用于设置镜像触发器 |
CMD | RUN | 指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。 |
EXPOSE | RUN | 将容器中的端口映射成宿主机器中的某个端口。 |
ENTRYPOINT | RUN | 类似于CMD指令,配置容器启动后执行的命令(容器的初始化命令)。 |
ENV | RUN | 设置环境变量。 |
WORKDIR | BOTH | bulid以及run阶段大家工作的场所(CMD以及RUN执行的工作目录)。 |
USER | BOTH | 指定运行容器时的用户名或UID,默认是root,后续的RUN也会使用指定用户。 |
VOLUME | RUN | 运行阶段容器为了持久化存储以正常的文件或者目录的形式挂载于宿主机上(持久化存储,容器挂了数据还在,用户存储有状态的数据)。 |
3、DockerFile 注意事项
- 每个 Dockerfile 的保留字(指令),都必须是大写的
- DockerFile 脚本执行是按照顺序执行的
#
表示注释- 每一个指令都会创建提交一个新的镜像层,并提交
4、Dockerfile 命令介绍
(1)FROM
FROM:指定基础镜像,并且必须是第一条指令。如果不以任何镜像为基础,那么写法为:FROM scratch
。同时意味着接下来所写的指令将作为镜像的第一层开始。
语法:
1 | # 格式: |
(2)MAINTAINER
MAINTAINER:维护者信息
语法:
1 | 格式: |
(3)RUN
RUN:构建镜像时执行的命令。
RUN命令有两种格式,一种是 shell 格式,还有一种为 exec 格式:
shell 语法如下:
1 | RUN <command> |
exec 语法如下:
1 | RUN ["executable", "param1", "param2"] |
注:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定–no-cache参数,如:docker build --no-cache。
(4)ADD
ADD:将本地文件添加到容器中。ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能。
1 | # 格式: |
如果 <源路径>
为一个 tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 <目标路径>
去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu
中:
1 | # 示例 |
但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD
命令了。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
1 | # 示例: |
(5)COPY
COPY:指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置。功能类似 ADD,也有两种格式,一种类似于命令行,一种类似于函数调用。不会自动解压文件,也不能访问网络资源。
1 | # 格式: |
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
(opens new window)规则,如:
1 | # 示例: |
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用 COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
1 | # 示例 |
注意:
- COPY 不会自动解压文件,也不能访问网络资源。
- 如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
(6)LABEL
LABEL:用于为镜像添加元数据,LABEL
是键值对。要在 LABEL
值中包含空格,请像在命令行中一样使用引号和反斜杠。一些用法示例:
1 | # 格式: |
(7)ARG
ARG:用于指定传递给构建运行时的变量(给dockerfile传参),相当于构建镜像时可以在外部为里面传参。
1 | #格式: |
(8)ONBUILD
OUTBUILD:用于设置镜像触发器,是一个特殊的指令,它后面跟的是其它指令,比如 RUN
, COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
1 | # 格式: |
(9)CMD
CMD:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
- CMD 在docker run 时运行。
- RUN 是在 docker build。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
1 | # 格式: |
运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu
镜像默认的 CMD
是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash
。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release
命令替换了默认的 /bin/bash
命令了,输出了系统版本信息。
提到 CMD
就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd
去启动后台服务,容器内没有后台服务的概念。
一些初学者将 CMD
写为:
1 | CMD service nginx start |
然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl
命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
而使用 service nginx start
命令,则是希望 upstart 来以后台守护进程形式启动 nginx
服务。而刚才说了 CMD service nginx start
会被理解为 CMD [ "sh", "-c", "service nginx start"]
,因此主进程实际上是 sh
。那么当 service nginx start
命令结束后,sh
也就结束了,sh
作为主进程退出了,自然就会令容器退出。
正确的做法是直接执行 nginx
可执行文件,并且要求以前台形式运行。比如:
1 | CMD ["nginx", "-g", "daemon off;"] |
(10)EXPOSE
EXPOSE:用于为容器暴露端口到外部,用于实现通讯,类似于docker run
的 -p 选项。语法如下:
1 | # 格式: |
不管 EXPOSE 设置是什么,都可以通过使用 -p 标志在运行时覆盖它们。例如:
1 | 示例: |
(11)ENTRYPOINT
ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数。
ENTRYPOINT 的值可以通过 docker run --entrypoint 来覆盖掉。
ENTRYPOINT 有两种语法格式,如下所示:
1 | # exec 格式 |
示例:
1 | # exec 格式 |
注意:
- ENTRYPOINT 与 CMD 非常类似,不同的是通过 docker run 执行的命令不会覆盖 ENTRYPOINT。
- Dockerfile 中只允许有一个 ENTRYPOINT 命令,多指定时会覆盖前面的设置,而只执行最后的 ENTRYPOINT 指令。
通常情况下, ENTRYPOINT 与 CMD 一起使用,ENTRYPOINT 写默认命令,当需要参数时候使用 CMD 传参。
(12)ENV
ENV:用来在镜像构建过程中设置环境变量,后续的 RUN 可以使用它所创建的环境变量。当创建基于该镜像的 container 的时候,会自动拥有设置的环境变量。
1 | # 格式: |
(13)WORKDIR
WORKDIR:用于指定容器的一个目录, 相当于设置容器的工作目录。以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR
会帮你建立目录。
1 | # 格式: |
注:通过 WORKDIR 设置工作目录后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY
等命令都会在该目录下执行。在使用 docker run
运行容器时,可以通过 -w 参数覆盖构建时所设置的工作目录。
(14)USER
USER:指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。这个用户必须是事先建立好的,否则无法切换。使用 USER 指定用户时,可以使用用户名、UID 或 GID,或是两者的组合。
1 | # 格式: |
注:使用 USER 指定用户后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT
都将使用该用户。镜像构建完成后,通过 docker run
运行容器时,可以通过 -u
参数来覆盖所指定的用户。
(15)VOLUME
VOLUME:用于指定持久化目录(指定此目录可以被挂载出去)。
1 | # 格式 |
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile
中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
上面示例的 /data
目录就会在容器运行时自动挂载为匿名卷,任何向 /data
中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:`
1 | docker run -d -v mydata:/data xxxx |
在这行命令中,就使用了 mydata
这个命名卷挂载到了 /data
这个位置,替代了 Dockerfile
中定义的匿名卷的挂载配置。
三、实战 Dockerfile
1、实战介绍
基础镜像为 CentOS 7
,本次实战模拟在 CentOS 7
的基础上安装 vim
、ifconfig
和 java
环境。
java环境下载地址:Index of /jdk/ (yangxingzhen.com)
2、Dockerfile 文件配置
在指定路径下 vim DockerFile
,填写内容如下:
1 | # 指定基础镜像 |
3、镜像构建
执行以下命令进行镜像构建。
1 | docker build -t centosjava . |
4、镜像测试
启动容器,验证构建的镜像,是否满足要求。
1 | 启动容器 |