使用 Dockerfile 定制镜像

  • 使用 Dockerfile 定制镜像
  • 用 Nginx 做个示例
  • FROM 指定基础镜像
  • RUN 执行命令
  • shell格式
  • exec格式
  • 构建镜像
  • 指令详解
  • COPY 复制文件
  • ADD 更高级的复制文件
  • CMD 容器启动命令
  • ENTRYPOINT 入口点
  • ENV 设置环境变量
  • ARG 构建参数
  • VOLUME 定义匿名卷
  • EXPOSE 声明端口
  • WORKDIR 指定工作目录
  • USER 指定当前用户
  • HEALTHCHECK 健康检查
  • ONBUILD 为他人做嫁衣裳


使用 Dockerfile 定制镜像

  • 镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。
  • 镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么镜像无法重复、构建透明性和体积的问题就都会解决。这个脚本就是 Dockerfile。
  • Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

用 Nginx 做个示例

[root@ ~]# mkdir /usr/local/test

[root@ ~]# cd /usr/local/test

[root@ test]# vim Dockerfile

[root@ test]# docker build -t test:1 .

Dockerfile的内容如下:

FROM nginx

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

再启动nginx就会发现,首页不是官方默认的内容,而是显示Hello, Docker!

dockerfile 本地build dockerfile 本地镜像找不到_docker

FROM 指定基础镜像

  • FROM指令表示指定一个基础镜像,这是每个Dockerfile文件都必须有的,并且必须是第一条指令。

RUN 执行命令

  • RUN 指令是用来执行命令行命令的,有两种格式:

shell格式

按命令行的写法:

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

exec格式

RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。

Dockerfile 中每一个指令都会建立一层,因此在编写 Dockerfile 的时候要尽量合并命令。而且如果存在需要编译安装的软件,在命令最后,一定清理为了安装所下载和其余产生的不必要的文件,不然在执行下一个命令后,就无法修改上一层的内容,镜像就可能变得越来越臃肿。

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

构建镜像

[root@ test]# docker build -t test:1 .
  • 这里需要注意命令最后的.,这里表示 Dockerfile 所在的文件夹,并将这个目录作为操作目录,docker build会把这个目录下的所有文件都上传到 Docker 引擎以帮助构建镜像。
  • 如果有在构建时不希望上传到 Docker 引擎的文件,可以以.gitignore一样的语法写一个.dockerignore

指令详解

COPY 复制文件

COPY [--chown=<user>:<group>] <源路径>... <目标路径>

# 源路径可以使用满足 Go 的 filepath.Match 规则的通配符
COPY hom* /mydir/
COPY hom?.txt /mydir/
  • <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
  • 此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
  • 在使用该指令的时候还可以加上 --chown=<user>:<group>选项来改变文件的所属用户及所属组。

ADD 更高级的复制文件

  • ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
  • ADD 指令的<源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
  • 在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是需要自动解压缩的场合。

CMD 容器启动命令

  • CMD指令有两种格式。
  • shell 格式:CMD <命令>
  • exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]
  • 参数列表格式:CMD [“参数1”, “参数2”…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
  • 在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
  • Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念,所以无法以后台守护进程形式启动服务。

ENTRYPOINT 入口点

这个指令貌似是在执行docker run命令的时候,把所带的参数带到容器的构建脚本的CMD指令后面,没有很懂,后面弄明白了再补充。

ENV 设置环境变量

相当于为后面的命令设置一个常量,有两种格式

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

可以在以下指令中使用:
ADDCOPYENVEXPOSELABELUSERWORKDIRVOLUMESTOPSIGNALONBUILD

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

ARG 构建参数

格式:ARG <参数名>[=<默认值>] 顾名思义,这个参数是在构建的时候使用的,构建结束后不会保存在容器中,在构建的时候可以用--build-arg <参数名>=<值>来覆盖。

VOLUME 定义匿名卷

格式:

  • VOLUME ["<路径1>", "<路径2>"...]
  • VOLUME <路径>

这个参数和docker run -d -v mydata:/data xxxx中的-v是同样的效果,把容器中的数据保存到本地,这样在销毁容器的时候就不会丢失重要数据。

EXPOSE 声明端口

格式为 EXPOSE <端口1> [<端口2>...]

这个只是声明,属于一种说明,并不会做实际的端口映射,是为了方便这个脚本的使用者了解这个镜像服务的守护端口。

WORKDIR 指定工作目录

格式为 WORKDIR <工作目录路径>

RUN cd /app
RUN echo "hello" > world.txt

简单的说,上面的两个RUN指令其实是在不同存储层,所以第二个RUN指令并不是继承于第一个指令,如果你想实现连续的路径文件操作,就可以定义WORKDIR

USER 指定当前用户

格式:USER <用户名>[:<用户组>]

WORKDIR的效果差不多,定义以后的操作,就是基于定义的用户做操作。

HEALTHCHECK 健康检查

格式:

  • HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令。
  • HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令。

HEALTHCHECK 支持下列选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒。
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒。
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。

实例:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1

可以看到STATUS显示了healthy,可以用docker inspect来查看容器的健康状态log。

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   18 seconds ago      Up 16 seconds (healthy)   80/tcp, 443/tcp     web

ONBUILD 为他人做嫁衣裳

格式:ONBUILD <其它指令>

这个指令是自己本身被构建的时候并不会被指令,而是在其它镜像要以自己为基础镜像的时候才会执行。

举个例子:

下面的脚本在执行的时候ONBUILD的三行指令并不会执行。

FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

但一个新的镜像要以上一个镜像为基础镜像时,ONBUILD的三行指令就会触发。

FROM my-node

适合在创建多个类似应用的时候使用。