Docker之十二:Dockerfile
- Dockerfile 指令
- Dockerfile 配置指令
- ARG
- FROM
- LABEL
- EXPOSE
- ENV
- ENTRYPOINT
- VOLUM
- USER
- WORKDIR
- ONBUILD
- STOPSIGNAL
- HEALTHCHECK
- SHELL
- Dockerfile 操作指令
- RUN
- CMD
- ADD
- COPY
- 创建镜像
- docker build 命令选项说明
- 选择父镜像
- 使用 .dockerignore 文件
- 多步骤创建
Dockerfile 是一个用来构建镜像的文本文件。
Dockerfile 指令
Dockerfile 配置指令
指令 | 说明 |
ARG | 定义创建镜像过程中使用的变量 |
FROM | 指定所创建镜像的基础镜像 |
LABEL | 为生成的镜像添加元数据标签信息 |
EXPOSE | 声明镜像内服务监听的端口 |
ENV | 指定环境变量 |
ENTRYPOINT | 指定镜像的默认入口命令 |
VOLUME | 创建一个数据卷挂载点 |
USER | 指定运行容器时的用户名或 UID |
WORKDIR | 配置工作目录 |
ONBUILD | 创建子镜像时指定自动执行的操作指令 |
STOPSIGNAL | 指定退出的信号值 |
HEALTHCHECK | 配置启动容器如何进行健康检查 |
SHELL | 指定默认 shell 类型 |
ARG
定义创建镜像过程中使用的变量,但ARG定义的变量只在 Dockerfile 内有效,并在 docker build过程中发挥作用,但镜像编译成功后变量将不再存在(这点于 ENV 有区别,ENV指定的变量将在镜像中保留,也就是说 ENV 的作用域是全局的)。
# 格式
ARG <name>[=<default value>]
# name 表示变量名或参数名,default value 表示默认值
# docker build 时,可以通过 --build-arg<name>=<value> 来为变量重新赋值
# Docker 内置了一些镜像创建变量,用户可以直接使用而无需声明(不区分大小写):HTTP_PROXY、HTTPS_PROXY、FTP_PROXY、NO_PROXY
FROM
指定创建镜像使用的基础镜像。Dockerfile 中的第一条指令必须时 RROM 指令。同一个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令。
# 格式
FROM <image>
# 或
FROM <image>:<tag>
# 例:
ARG VERSION=9.3
FROM debain:${VERSION}
LABEL
为生成的镜像添加元数据标签信息,这些数据可以用来辅助过滤出特定镜像。
# 格式
LABEL <key>=<value> <key>=<value> <key>=<value> ...
# 例:
LABEL version="1.0.0-rc3"
LABEL author="fgd" date="2020-12-10"
LABEL description="This text illustrates \
that label-values can span multiple lines."
镜像创建完成后,生成容器,可以使用 docker inspect 查看镜像的 “Lables“ 信息。
EXPOSE
声明镜像内服务监听的端口,其作用也只是声明而已,可以帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口;或 -p HOST_PORT:CONTAINER_PORT 具体指定所映射的本地端口。
# 格式
EXPOSE <port1> [<port2>/<protocol>...]
# 如:EXPOSE 22 80 8843
ENV
指定环境变量,在进行生成过程中会被后续的 RUB 指令使用,在镜像启动的容器中也会存在。
# 格式
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
# 指令指定的环境变量在运行时可以被覆盖掉,如:docker run --env <key>=<value> image
# 例
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"
# 当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时,会为变量都赋值后再更新
ENV key1=value2
ENV key1=value1 key2=${key1}
# 最终的结果为 key1=value1 key2=value2
ENTRYPOINT
指定镜像的默认入口命令,该入口命令会再启动容器时作为根命令执行,所有传入值作为该命令的参数。Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
# 格式1:exec调用执行
ENTRYPOINT ["<executeable>","<param1>","<param2>",...]
# 格式2:shell中执行,CMD指令指定值作为根命令参数
ENTRYPOINT command param1 param2
运行时(docker run),可以被 --entrypoint 选项覆盖掉,如:docker run --entrypoint 。
VOLUM
创建一个数据挂在点。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
# 格式:
VOLUME ["<路径1>", "<路径2>"...]
# 或
VOLUME <路径>
在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。
USER
指定运行容器时的用户名或 UID(用户和用户组),后续的 RUN 等指令也会使用指定的用户身份。用户和用户组必须提前已经存在。
# 格式:
USER <用户名>[:<用户组>]
当服务不需要管理员权限时,可以通过该命令指定运行用户,并且可以在Dockerfile中创建所需要的用户:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
# 要临时获取管理员权限可以使用 gosu 命令
WORKDIR
为后续的 RUN、 CMD、ENTRYPOINT 指令指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。
docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在.。
# 格式:
WORKDIR <工作目录路径>
可以使用多个 WORKDIR 指令,后续的命令如果参数时相对路径,则会基于之前命令指定路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
此时最终路径是:/a/b/c,因此为了避免出错,推荐 WORKDIR 指令中使用决定路径。
ONBUILD
指定当基于所生成镜像创建子镜像是,自动执行的操作指令。
# 格式:
ONBUILD [INSTRUCTION]
例如,使用如下的 Dockerfile 创建父镜像(ParentImage),指定 ONBULID指令:
# Dockerfile for ParentImage
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
使用 docker build 命令创建子镜像 ChildImage 时(FROM ParentImage),会首先执行 ParentImage 中配置 ONBUILD 指令:
# Dockerfile for ChildImage
FROM ParentImage
等价于在 ChildImage 的Dockerfile 中添加了如下指令:
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
...
由于 ONBUILD 指令时隐式执行的,推荐在使用它的镜像标签中进行标注,例如:ruby:2.1-onbuild 。
ONBUILD 指令在创建专门用于自动编译、检查等操作的基础镜像时,十分有用。
STOPSIGNAL
指定所创建镜像的穷奇接收退出信号的值。
# 格式
STOPSIGNAL signal
# signal 代表信号名或者信号所代表的值
HEALTHCHECK
配置所启动的容器如何进行健康检查,用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
# 格式1:设置检查容器健康状况的命令,根据所执行命令返回值是否为 0 来判断
HEALTHCHECK [OPTIONS] CMD <command>
# OPTIONS支持的参数:
# -interval=DURATION(default:30s):过多久检查一次
# -timeout=DURATION(default:30s):每次检查等待结果的超时
# -retries=N(default:3):如果失败,重复几次才最终确定失败
# 格式2:进制基础镜像中的健康检查,如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK NONE
SHELL
指定其他命令使用 shell 时默认 shell 类型。
# 格式:
SHELL ["executable", "parameters"]
# 默认值为 ["/bin/sh", "-c"]
Dockerfile 操作指令
指令 | 说明 |
RUN | 运行指定命令 |
CMD | 启动容器时指定默认执行的命令 |
ADD | 添加内容到镜像 |
COPY | 复制内容到镜像 |
RUN
运行指令命令,用于执行其后跟的命令行命令。
# shell 格式 :
RUN <command>
# <command> 等同于在终端操作的 shell 命令,如:RUN apt-get update
# exec 格式:
RUN ["executable", "param1", "param2"]
# executable 为可执行文件,param 为参数,如:RUN ["/bin/bash", "-c", "echo hello"]
CMD
用于运行程序,指定启动容器(docker run)时默认执行的命令。为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。Dockerfile 中只能存在一个CMD命令,如果指定了多条命令,仅最后一条有效。
# 格式1:CMD <shell命令>
CMD command param1 param2
# 格式2:
CMD ["executable", "param1", "param2"]
# executable 为可执行文件或命令, param 为参数,相当于执行 executable param1 param2,Docker 推荐的使用方式
# 格式2:
CMD ["param1", "param2"]
# 为 ENTRYPOINT 指令指定的程序提供默认参数
ADD
添加内容到镜像。在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>;但在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
# 添加内容到
ADD <src> <dest>
# <src> 可以是Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
# <dest> 可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径
# 如:ADD *.c /code/
COPY
复制内容到镜像,可以从上下文目录中复制文件或者目录到容器里指定路径。
# 格式1:
COPY [--chown=<user>:<group>] <src1>... <dest>
# 格式2:
COPY [--chown=<user>:<group>] ["<src1>",... "<dest>"]
# [--chown=<user>:<group>] 是可选参数,可以改变复制到容器内文件的拥有者和属组
# <src1> 可以支持通配符,如:COPY myfile* /mydir/
创建镜像
编写完成 Dockerfile 后,需要通过 docker build 命令来创建进项。
# 格式
docker build [OPTIONS] PATH | URL | -
# 或
docker image build [OPTIONS] PATH | URL | -
该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有数据作为上下文(Context)发送给 Docker 服务端。Docker 服务端在校验 Dockerfile 格式通过后,逐条执行其中定义的指令,碰到 ADD、COPY 和 RUN 指令会生成一层新的镜像。最终如果创建镜像成功,会返回最终镜像的ID。
如果上下文过大,会导致发送大量数据给服务端,延缓创建过程。因此除非是生成镜像所必需的文件,不然不要放到上下文路径下。如果使用非上下文路径下的 Dockerfile,可以通过 -f 选项来指定其路径。
要指定生成镜像的标签信息,可以通过-t选项。该选项可以重复使用多次为镜像一次添加多个名称。
# 例如:
$ docker build -t builder/first_image:1.0.0 /tmp/docker_builder/
# builder/first_image:1.0.0 为指定的镜像标签
# /tmp/docker_builder/ 为指定的上下文路径
docker build 命令选项说明
选择父镜像
父镜像可以分为两类,一类是基础镜像(baseimage),另外一种是普通镜像(基于基础镜像)。
基础镜像的 Dockerfile 文件中往往不存在 FROM 指令,或者基于 seratch 镜像(FROM seratch),其在整个镜像树中处于根位置。
一个基础镜像的 Dockerfile 例子:
# 用户将提前编译好的二进制可执行文件 binary 复制到镜像中,运行容器时执行 binary 命令
FROM scratch
ADD binary
CMD ["/binary"]
普通镜像也可以作为父镜像来使用,包括常见的 busybox、debain、Ubuntu 等。
Docker不同类型镜像之间的继承关系:
使用 .dockerignore 文件
可以使用 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略匹配路径或文件,在创建镜像的时候不将无关数据发送到服务端。
# .dockerignore 文件中可以定义忽略模式
*/temp*
*/*/tmmp*
tmp?
~*
Dockerfile
!README.md
.dockerignore 文件中模式语法支持 Golang 风格的路径正则格式。
”*“ 表示任意一个多字符。
”?” 表示单个字符。
“!“ 表示不匹配,即忽略指定的路径或文件
多步骤创建
Docker 支持多步骤创建(Multi-stage build)特性,可以精简最终生成的镜像大小。
对于需要编译的应用(如C、Java 或 Go 语言等)来说,通常情况下至少需要准备两个环境的 Docker 镜像:
- 编译环境镜像:包括完整的编译引擎、依赖库等,往往比较庞大。作用时编译应用为二进制文件。
- 运行环境镜像:利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。
使用多步骤创建,可以保证最终生成的运行环境保持精简的情况下,使用单一的 Dockerfile,降低维护复杂度。
以 Go 语言应用为例。创建干净目录,进入到目录中,创建 main.go 文件,内容是:
// main.go will output "Hello Docker"
package main
import {
"fmt"
}
func main() {
fmt.Println{"Hello Docker"}
}
创建 Dockerfile,使用 golang:1.9 镜像 编译应用二进制文件为 app, 使用精简镜像 alpine:latest 作为运行环境。Dockerfile 的完整内容为:
# define stage name as builder
FROM golang:1.9 as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root
# copy file from the builder stage
COPY --from=builder /go/src/test/app .
CMD ["./app"]
创建镜像:
fgd@ubuntu:/home/workspace/go$ docker build -t test-multistage:lastest .
查看镜像:最终镜像只有7.94MB
运行应用:
$ docker run --rm test-multistage:lastest