【Docker】之 Dockerfile 指令详解

目录


  • Dockerfile 基本结构
  • Dockerfile 指令
    • 指定基础镜像 FROM
    • 维护者信息 MAINTAINER
    • 元数据标签 LABEL
    • 设置环境变量 ENV
    • 镜像构建参数 ARG
    • 指定工作目录 WORKDIR
    • 挂载匿名卷 VOLUME
    • 端口暴露 EXPOSE
    • 复制文件 COPY
    • 复制并解压文件 ADD
    • 镜像构建运行命令 RUN
    • 容器启动运行命令 CMD
    • 容器启动运行命令 ENTRYPOINT
    • 延迟构建命令 ONBUILD
    • 监控运行状态 HEALTHCHECK
  • Dockerfile 编写建议

Dockerfile 基本结构


Dockerfile 由一行行命令语句组成,并且支持以 # 开头的注释行。Dockerfile 的内容分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。例如:

# This dockerfile uses the Ubuntu image
# VERSION 1.0
# Author: wangtingyun
# Command format: Instruction [arguments / command] …

# 第一行必须指定基于的容器镜像
FROM ubuntu

# 维护者信息
MAINTAINER wangtingyun [email protected]

# 镜像的操作指令
RUN echo “deb http://archive.ubuntu.com/ubuntu/ raring main universe” >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo “\ndaemon off;>> /etc/nginx/nginx.conf

# 容器启动时执行指令
CMD /usr/sbin/nginx

其中:

  • 一开始必须指明所基于的镜像名称,接下来一般会说明维护者的信息;
  • 后面则是镜像操作指令,例如 RUN 指令,RUN 指令将对镜像执行跟随的命令,而每运行一条 RUN 指令,镜像添加新的一层,并提交;
  • 最后是 CMD 指令,来指定运行容器时的操作命令。

Dockerfile 指令


FROM

功能: 指定一个镜像作为构建自定义镜像的基础镜像,构建的镜像在这个基础镜像之上进行修改定制。

指令格式:

FROM <image>:<tag>

这个指令是 Dockerfile 中的必备指令,同时也必须是第一条指令,如果在同一个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令。


MAINTAINER

功能: 指定维护者信息。

格式为:

MAINTAINER <name>

注意: MAINTAINER 已经被抛弃了,建议使用 LABEL 指令。


LABEL

功能: 为镜像添加元数据标签,一个 LABEL 就是一个键值对。

格式如下:

LABEL <key>=<balue> <key>=<balue> ...

示例如下:

LABEL maintainer="[email protected]"
LABEL version="1.0"

可以给镜像添加多个 LABEL需要注意的是:每条 LABEL 指令都会生成一个新的层。所以最好是把添加的多个 LABEL 合并为一条命令:

# 写在一行,多个键值对使用空格隔开
LABEL maintainer="[email protected]" version="1.0"

# 使用换行符
LABEL maintainer="[email protected]" \
      version="1.0"

注意: 如果新添加的 LABEL 和已有的 LABEL 同名,则新值会覆盖掉旧值。


ENV

功能: 设置或定义环境变量,定义的环境变量可以在后续的指令中通过 $ 进行使用。

指令格式:

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>

示例如下:

# 定义 node 版本的环境变量
ENV NODE_VERSION 7.2.0

# 使用 NODE_VERSION 环境变量
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"

ARG

功能: 设置镜像构建参数。与 ENV 指令作用一致,不过作用域不一样:ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中才有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg = 来覆盖。

指令格式:

ARG <key>=<value>

WORKDIR

功能: 指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在(WORKDIR 指定的工作目录,必须是提前创建好的)。

构建镜像过程中的,每一个 RUN 命令都是新建的一层,只有通过 WORKDIR 创建的目录才会一直存在。

指令格式:

WORKDIR <path>

注意: 可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终路径为:/a/b/c


VOLUME

功能: 定义挂载匿名数据卷目录。在启动容器时如果忘记挂载数据卷,会自动挂载到匿名卷。

挂载卷的作用:

  • 避免重要的数据,因容器重启而丢失,这是非常致命的;
  • 避免容器不断变大;

指令格式:

VOLUME <path>
VOLUME ["", "", ...]

重点: 在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。


EXPOSE

功能: 告诉 Docker 服务,容器需要暴露的端口号。

作用:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
  • 在使用随机端口映射时,也就是 docker run -P大写的 P)时,会自动随机映射 EXPOSE 的端口(-p 参数指定具体的映射端口);

指令格式:

EXPOSE <port1> [<port2> ...]

COPY

功能:上下文目录中复制文件或者目录到容器里指定路径。

指令格式:

COPY [--chown=<user>:<group>] <源路径1>...  <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",...  "<目标路径>"]
  • [--chown=:]:可选参数,用于改变复制到容器内文件的拥有者和所属组;
  • <源路径>:源文件或目录(以 Dockerfile 的上下文环境为相对路径),可以使用通配符,但通配符要满足 Gofilepath.Match 规则;
  • <目标路径>:容器内的路径,如果路径不存在的话,会自动创建;

示例:

# 复制单个文件
COPY data01.txt /home/data/

# 复制多个文件,根据通配符匹配
COPY data*.txt /home/data/

# 复制文件夹
COPY temp/data/ /home/data/

注意: 目标路径最后一定是以 / 结尾的,否则就是视目标路径为文件,相当于拷贝文件并重命名为目标路径最后的名称。


ADD

功能:上下文目录中复制文件或者目录到容器里指定路径,功能和 COPY 指令类似。

指令格式:

ADD <src> <dest>

优缺点:

  • 优点: 在执行 <源文件>tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压<目标路径>
  • 缺点: 在不解压的前提下,无法复制 tar 压缩文件,会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

所以,具体是否使用 ADD 指令,可以根据是否需要自动解压来决定。


RUN

作用: 为构建的镜像指定要运行的命令行命令,而这些命令是在 docker build 的时候执行的。

指令格式有两种:

1、shell 格式

RUN <command>

2、exec 格式

RUN ["executable", "param1", "param2", ...]
  • shell 格式将在终端中运行命令,command 则为终端操作的 shell 命令;
  • exec 格式使用 exec 命令执行,可以指定其他终端,例如:RUN ["/bin/bash", "-c", "echo hello"]

每条 RUN 指令将在当前镜像的基础上执行指定命令,并提交为新的镜像,而 Dockerfile 的指令每执行一次都会在 docker 上新建一层,过多无意义的层,会造成镜像膨胀过大,所以建议将多个命令合并到同一个 RUN 指令上,例如:

# 多个 RUN 指令合并到同一行
RUN yum -y install wget \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && tar -xvf redis.tar.gz

通过使用 && 符号连接命令,这样执行后,只会创建 1 层镜像。


CMD

作用: 为启动的容器指定要运行的命令,类似于 RUN 指令,但 CMD 运行程序的时间是在 docker run 时执行的,命令运行结束,容器也就结束。

格式有三种:

# 1、shell 命令格式
CMD <shell command>

# 2、exec 命令格式:推荐 
CMD ["<可执行文件或命令>","","",...] 

# 3、该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
CMD ["","",...]  

推荐使用第二种格式,执行过程比较明确,而第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh

注意:

  • 每个 Dockerfile 只能有一条 CMD 命令,如果指定了多条 CMD 命令,只有最后一条会被执行;
  • 如果 docker run 命令行参数中指定了要运行的程序命令,则会覆盖CMD 指令指定的程序命令。

ENTRYPOINT

作用:CMD 指令类似,也是指定容器启动时执行的命令,但和 CMD 指令不同的是:

  • 即使 docker run 的命令行参数指定了要运行的程序命令,ENTRYPOINT 指令的程序命令也不会被覆盖,并且这些命令行参数会被当作参数传送给 ENTRYPOINT 指令指定的程序;
  • 但是,如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。

指令格式:

ENTRYPOINT ["", "", "", ...]

注意: 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个 ENTRYPOINT 时,只有最后一个生效。

使用方式:

ENTRYPOINT 指令一般搭配 CMD 指令一起使用:通过 CMD 的第三种指令格式给 ENTRYPOINT 动态传参。

示例: 构建 nginx:test 镜像

FROM nginx

ENTRYPOINT ["nginx", "-c"]    # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参 

1、当 docker run 不传参运行时:

docker run nginx:test

容器内会默认运行以下命令(命令参数来自 CMD 指定提供的参数),启动主进程:

nginx -c /etc/nginx/nginx.conf

2、当传参运行时:

docker run nginx:text -c /etc/nginx/new.conf

容器会运行以下命令(命令行参数覆盖 CMD 指令提供的参数),启动主进程:

nginx -c /etc/nginx/new.conf

ONBUILD

功能: 配置当所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令。

指令格式:

ONBUILD [INSTRUCTION]

示例:

使用如下 Dockerfile 内容创建镜像 myImage

...
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
...

当新的 Dockerfile 中基于镜像 myImage即使用 FROM myImage)时,会自动执行 ONBUILD 指令的内容,等价于在后面添加了两条指令:

FROM myImage

# 自动运行以下两条指令
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src

HEALTHCHECK

功能: 指定某个程序或者指令来监控 docker 容器服务的运行状态。

指令格式:

# 设置检查容器健康状况的命令
HEALTHCHECK [选项] CMD <命令>

# 如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK NONE

# 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法
HEALTHCHECK [选项] CMD <命令>

Dockerfile 编写建议


1、编写 .dockerignore 文件

.dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少 Docker 镜像的大小。

2、容器只运行单个应用

如果在 Docker 容器中运行多个进程会有比较多的麻烦:

  • 非常长的构建时间;
  • 非常大的镜像文件;
  • 多个应用的日志难以处理(不能直接使用stdout,否则多个应用的日志会混合到一起);
  • 横向扩展时非常浪费资源(不同的应用需要运行的容器数并不相同);
  • 僵尸进程问题(需要选择合适的init进程);

所以,建议每个应用构建单独的 Docker 镜像,然后使用 Docker Compose 运行多个 Docker 容器。

3、将多个 RUN 指令合并为一个

由于 Docker 镜像是分层的:

  • Dockerfile 中的每个指令都会创建一个新的镜像层;
  • 镜像层会被缓存和复用,当 Dockerfile 的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效,而某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效;
  • 镜像层是不可变的,如果我们再某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件(只是这个文件在 Docker 容器中不可见了);

所以,RUN 指令的多个命令可以通过换行符 \&& 来合并成一行指令,例如下面的 node.jsnpm 模块的安装命令:

FROM ubuntu

ADD . /app

RUN apt-get update \  
    && apt-get install -y nodejs \
    && cd /app \
    && npm install

CMD npm start

注意: 们只能将变化频率一样的指令合并在一起,将 node.js 安装与 npm 模块安装放在一起的话,则每次修改源代码,都需要重新安装 node.js,这显然不合适。因此,正确的写法是这样的:

FROM ubuntu

RUN apt-get update && apt-get install -y nodejs  
ADD . /app  
RUN cd /app && npm install

CMD npm start

4、基础镜像的标签不要用 latest

当镜像没有指定标签时,将默认使用 latest 标签。因此, FROM ubuntu 指令等同于 FROM ubuntu:latest

但是,当镜像更新时,latest 标签会指向不同的镜像,这时构建镜像有可能失败。如果你的确需要使用最新版的基础镜像,可以使用 latest 标签,否则的话,最好指定确定的镜像标签。

5、每个 RUN 指令后删除多余文件

假设我们更新了 apt-get 源、下载、解压并安装了一些软件包,它们都保存在 /var/lib/apt/lists/ 目录中。但是,运行应用时,Docker 镜像中并不需要这些文件。我们最好将它们删除,因为它会使 Docker 镜像变大:

RUN apt-get update \  
    && apt-get install -y nodejs \
    # 删除多余文件
    && rm -rf /var/lib/apt/lists/*

6、选择合适的基础镜像 (alpine 版本最好)

比如,我们只是构建运行 node 程序,没有必要选择 ubuntu 这种通用的基础镜像:

FROM ubuntu

选择 node 镜像作为基础镜像更好,并且选择 alpine 版本的更好:

FROM node:7-alpine
  • alpine 是一个极小化的 Linux 发行版,非常适合作为基础镜像;
  • apkAlpine 的包管理工具,它与 apt-get 有些不同,但是非常容易上手。另外,它还有一些非常有用的特性,比如 no-cache--virtual 选项,它们都可以帮助我们减少镜像的大小;

7、设置 WORKDIR

WORKDIR 指令可以设置默认目录,也就是运行 RUN / CMD / ENTRYPOINT 指令的地方。

8、使用 ENTRYPOINT

ENTRYPOINT 指令并不是必须的,因为它会增加复杂度。ENTRYPOINT 是一个脚本,它会默认执行,并且将指定的命令作为其参数,它通常用于构建可执行的 Docker 镜像。

Dockerfile 中指定 entrypoint.sh 脚本:

ENTRYPOINT ["./entrypoint.sh"] 

然后在 docker run 中执行该脚本:

docker run -it demo /bin/bash

注意: entrypoint 脚本,应该使用 exec 执行命令,否则无法顺利地关闭容器,因为 SIGTERM 信号会被 bash 脚本进程吞没。exec 命令启动的进程可以取代脚本进程,因此所有的信号都会正常工作。

9、优先使用 COPY 而不是 ADD

COPY 指令非常简单,仅用于将文件拷贝到镜像中,而 ADD 相对来讲复杂一些,可以用于下载远程文件以及解压压缩包。所以除非要解压缩包,否则建议使用 COPY

10、合理调整 COPY 与 RUN 的顺序

我们应该把变化最少的部分放在 Dockerfile 的前面,这样可以充分利用镜像缓存。

11、设置默认的环境变量、映射端口和数据卷

运行 Docker 容器时很可能需要一些环境变量。在 Dockerfile 设置默认的环境变量、端口映射和数据卷是一种很好的方式:

# 设置环境变量
ENV MEDIA_DIR=/media \  
    APP_PORT=3000
# 挂载数据卷
VOLUME $MEDIA_DIR 
# 暴露映射端口
EXPOSE $APP_PORT

ENV 指令指定的环境变量在容器中可以使用,如果只是需要指定构建镜像时的变量,可以使用 ARG 指令。

12、 使用 LABEL 设置镜像元数据

使用 LABEL 指令,可以为镜像设置元数据,例如镜像创建者或者镜像说明。旧版的 Dockerfile 语法使用 MAINTAINER 指令指定镜像创建者,但是它已经被弃用了:

FROM node:7-alpine  
LABEL maintainer "[email protected]" 
...

13、添加HEALTHCHECK

运行容器时,可以指定 --restart always 选项。这样的话,容器崩溃时,Docker 守护进程会重启容器,对于需要长时间运行的容器,这个选项非常有用。

但是,如果容器的确在运行,但是不可用(陷入死循环,配置错误)怎么办?使用 HEALTHCHECK 指令可以让 Docker 周期性的检查容器的健康状况。

我们只需要指定一个命令,如果一切正常的话返回 0,否则返回 1:

HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1

完整示例如下(以构建 Node.js 应用为例):

FROM node:7-alpine  
LABEL maintainer "[email protected]"

ENV PROJECT_DIR=/app  
WORKDIR $PROJECT_DIR

COPY package.json $PROJECT_DIR  
RUN npm install  
COPY . $PROJECT_DIR

ENV MEDIA_DIR=/media \  
    NODE_ENV=production \
    APP_PORT=3000

VOLUME $MEDIA_DIR  
EXPOSE $APP_PORT  
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1

ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

你可能感兴趣的:(Docker,docker,Dockerfile)