Docker深入之Dockerfile
Docker进阶篇之利用dockerfile构建属于自己的镜像,此篇需要Docker基础,可以查看Docker入门篇
What
Dockerfile是一个文本文档,其中包含用户可以在命令行上调用以组合图像的所有命令。使用docker build 用户可以创建自动执行的构建,该构建可以连续执行多个命令行指令。
摘自官方文档
简单来说,Dockerfile是一个文本文件,也可以理解为配置文件,自己写命令,然后以build命令运行脚本,执行所有命令已达到构建自己镜像的目的。
How
基本规范
- 所有Dockerfile必须以
FROM
指令开头,以指定基础镜像,如果不需要基础镜像可以指定镜像为scratch
,scratch
镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 - 所有指令不区分大小写,但是约定必须大写,以便将指令与参数区分开来。
- 约定每两个不同指令间以空行隔开。
- 一个指令只能写在一行内,不允许换行。
指令详解
FROM指定基础镜像
简介:用于指定本Dockerfile的基础镜像。
FROM nginx
表示指定基础镜像为nginx
RUN运行命令
简介:用于在容器中执行命令,由于命令行是很强大的,所有这个也是最常用的命令
RUN echo 'Hello Docker!'
表示在容器中输出Hello Docker!
COPY复制文件
格式: COPY <源路径>… <目标路径>
简介:用于复制构建上下文路径的文件到容器中的目标路径,源路径可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则
COPY package.json /usr/src/app/
复制上下文路径下的package.json 到 容器中/usr/src/app/
路径下。
备注: 这个指令涉及到上下文概念,详情查看 上下文
ADD 更高级的复制文件
简介:ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
使用格式与COPY 相同,ADD命令源路径可以是URL Docker引擎会自动下载文件并将文件权限设置为600,如果需要修改权限还需要使用RUN 命令修改权限;
如果源文件是一个压缩包,ADD命令还会自动解压缩,如果不需要解压缩,则不可以使用ADD命令。所以这个命令并不合理,如果需要下载或者解压缩,使用RUN 命令操作更加方便。
官方也不推荐使用此命令。
CMD 容器启动命令
简介:CMD 指令的格式和 RUN 相似,也是两种格式:
- shell 格式: CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]
- 参数列表格式:CMD [“参数1”, “参数2”…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
注意:Docker是进程,而不是容器,CMD也就是指定容器在启动进程时执行的命令(覆盖原命令)。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:CMD echo $HOME
在实际执行时会转换为CMD [ "sh", "-c", "echo $HOME" ]
这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。
ENTRYPOINT 入口点
简介:ENTRYPOINT 的格式和 RUN 指令格式一样。
一般有两种使用情景
为CMD添加额外可变参数:
1 | ENTRYPOINT ["/bin/echo", "Hello"] # 当容器通过 docker run -it [image] 启动时,输出为:Hello ; 而如果通过 docker run -it [image] Ttt 启动,则输出为:Hello Ttt |
将Dockerfile修改为:
1 | ENTRYPOINT ["/bin/echo", "Hello"] |
当容器通过 docker run -it [image] 启动时,输出为:Hello world
而如果通过 docker run -it [image] Ttt 启动,输出依旧为:Hello Ttt
ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
应用运行前的准备工作
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。
此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。
这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是
1 | FROM alpine:3.4 |
ENV 设置环境变量
简介:这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
格式:ENV <key> <value>
或者 ENV <key1>=<value1> <key2>=<value2>...
使用格式: $key
ARG 构建参数
格式:ARG <参数名>[=<默认值>]
简介:构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。
VOLUME 定义匿名卷
格式为:VOLUME ["<路径1>", "<路径2>"...]
或者 VOLUME <路径>
简介:定义成匿名卷后,数据会存储在宿主机上,多个容器可以共用,且不会在容器存储层写入大量数据。
备注:容器启动时可以使用docker run -d -v mydata:/data xxxx
覆盖原配置
EXPOSE 暴露端口
格式为 EXPOSE <端口1> [<端口2>...]
简介:EXPOSE 指令是声明运行时容器提供服务端口,注意,这只是声明,并不会暴露端口,有两个作用(帮助使用者理解容器守护端口、-P 命令是随机映射端口)
WORKDIR 指定工作目录
简介:使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。
格式:WORKDIR <工作目录路径>
。
备注:
1 | RUN cd /app |
如果在dockerfile中这样写的话。第二行的工作目录依旧是默认目录,而不是上一个命令执行的/app目录,因为dockerfile是每一个指令由一个进程执行,两行命令的执行环境都是不同的。
USER 指定当前用户
格式: USER 用户名
简介:USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层,以后的所有指令都是在这个用户下操作。
当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
1 | RUN groupadd -r redis && useradd -r -g redis redis |
备注:如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。
1 | # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 |
HEALTHCHECK 健康检查
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
简介: 防止容器进入死锁状态或者死循环状态后,容器仍在运行的问题。
HEALTHCHECK 支持下列选项:--interval=<间隔>
:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>
:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>
:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
注意:和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
ONBUILD 作为基础镜像构建时
格式: ONBUILD <其它指令>
简介: 当镜像被作为基础镜像使用时,执行相关指令。
ONBUILD RUN [ "npm", "install" ]
表示作为基础镜像时执行npm install 命令
上下文
在docker build 命令最后有一个.
这个.
也就是当前目录,也就是在指定上下文目录。什么是上下文呢?
这个要设计到docker 的工作原理,docker分为服务端和客户端,执行命令的就是客户端,客户端会根据命令发送相应的请求到服务端,由服务端去执行。所以docker build 其实是在服务端进行构建的,那docker又是怎么访问到我们的文件的呢,很简单,docker客户端会将指定的上下文路径直接打包发送给服务端。而上下文路径,也就是打包的文件夹路径
注意:如果在Dockerfile中使用COPY ../demo.txt
这个指令是不能执行成功的,因为…是上级目录,已经超出上下文目录了。相同的,使用COPY /demo.txt
也是不行的。
Why
我们为什么要使用Dockerfile?
比如我现在的java项目要部署到docker上,那么我现在需要一个Dockerfile来构建我的镜像。如下:
1 | FROM anapsix/alpine-java:8_server-jre_unlimited |
现在只需要build之后,将镜像push到docker私服上,然后再任何地方运行docker run imagesname 就可以启动我的项目了
或者不push,在本地build之后也可以直接运行了。