Dockerfile文件构建镜像:
- Dockerfile的基本结构
- 所支持的内部指令
- 使用它创建镜像的基本过程
- 合理的构建镜像
Dockerfile基本结构:
Dockerfile是一个文本格式的配置文件,可以使用Dockerfile来快速创建自定义的镜像。
Dockerfile:一行行命令语句+以#开头的注释行。
Dockefile主体内容:
- 基础镜像信息
- 维护者信息
- 镜像操作命令
- 容器启动时执行指令
简单示例:
首行通过注释来指定解析器指令,后续通过注释说明镜像的相关信息。
主体部分首先用FROM指令来指明所基于的镜像名称。接下来是使用LABEL指令说明维护者信息。接下来是镜像的操作指令。没运行一条RUN指令,镜像添加新的一层,最后是CMD指令,指定运行容器时的操作命令。
可查看更多官方镜像的Dockerfile写法。
指令说明:
Dockerfile中的指令一般格式:INSTRUCTION arguments.包括”配置指令(配置镜像信息)“、”操作指令(具体执行操作)“
分类 | 指令 | 说明 |
配置指令 | ARG FROM LABEL EXPOSE ENV ENTRYPOINT VOLUME USER WORKDIR ONBUILD STOPSIGNAL HEALTHCHECK SHELL |
定义创建镜像过程中使用的变量 指定所创建镜像的基础镜像 为生成的镜像添加元数据标签信息 声明镜像内服务监听的端口 指定环境变量 指定镜像的默认入口命令 创建一个数据卷挂载点 指定运行容器时的用户名或UID 配置工作目录 创建子镜像时指定自动执行的操作指令 指定退出的信号值 配置所启动容器如何进行健康检查 指定默认shell类型 |
操作指令 | RUN CMD ADD COPY |
运行指定命令。 启动容器时指定默认执行的命令。 添加镜像 复制内容到镜像 |
上述指令的具体说明:
1.配置镜像:
1.1 ARG:定义创建镜像过程中使用的变量。
格式:ARG <name>[=<default value>]
执行docker build时,通过-build--arg[=]来为变量复制。当镜像编译成功后,ARG指定的变量将不再存在。(ENV指定的变量将在镜像中保留)。
docker内置了一些镜像创建变量(直接使用不需要声明):HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY
1.2 FROM:指定所创建镜像的基础镜像。
格式:FROM <image> [AS <name> ]
或 FROM <imgae>:<tag> [AS <name>]
或 FROM <image>@<digest> [AS <name>]
任何Dockerfile中第一条指令必须是FROM指令。如果在同一个Dockerfile创建多个镜像时,可以使用多个FROM指令(每个镜像一次)。
示例:
ARG VERSION = 3
FROM nginx:${VERSION}
1.3 LABEL:可以为生成的镜像添加元数据标签信息。用来辅助过滤出特定镜像。
格式:LABEL <key>=<value> .....
示例:
LABEL version = "1.0.0"
LABEL author = "yinlei" data = "2020-01-16"
LABEL description = "This text is the test"
1.4 EXPOSE:声明镜像内服务监听的端口。
格式:EXPOSE <port> [<port>/<protocol>...]
EXPOSE指令只是声明作用,并不会自动完成端口映射。
如果要映射端口出来,在启动容器的时候应该使用-p参数。
示例:‘
EXPOSE 22 80 443
1.5 ENV: 指定环境变量,在镜像生成过程中hi被后续的RUN指令使用,在镜像启动的容器中也会存在。
格式: ENV <key> <value.
或 ENV <key>=<value> ...
指定的环境变量在运行时候可以被覆盖掉。如docker run --env=xxxx built_image
示例:
ENV APP_VERSION = 1.0.0
ENV APP_HOME=/usr/local/app
ENV PATH $PATH:/usr/local/bin
当一条ENV指令中同时为多i个环境变量复制并且值也是从环境变量读取时,会为变量都赋值后再更新:
ENV key1 = value2
ENV key1 = value1 key2 = ${key1}
最终结果是key1=value1 key2 =value2
1.6 ENTRYPOINT:指定镜像的默认入口命令,该入口命令会再在启动容器时作为根命令执行,所i有传入值作为该命令的参数。
2种格式:
- ENTRYPOINT ["executable","param1","param2"]: exec调用执行
- ENTRYPOINT command param1 param2: shell中执行
此时,CMD指令指定值将作为根命令的参数
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。
在运行时,可以被--entrypoint参数覆盖掉。
1.7 VOLUME:创建一个数据卷挂载点。
格式: VOLUME ["/data"]
运行容器时候可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保持的数据等。
1.8 USER:指定运行容器时的用户名或UID。后续的RUN等指令也会使用指定的用户身份。
格式: USER daemon
当服务不再需要管理员权限时,可以通过该命令执行运行用户,并在dockerfile中创建所需要的用户:
RUYN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
要临时获取管理员权限可以使用gosu命令。
1.9 WORKDIR:为后续的RUN CMD ENTRYPOINT指定配置工作目录。
格式:WORKDIR /path/to/workdir
可以使用多个WORKDIR命令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
例如:
WORKDIR /a
WORKDIR /b
WORKDIR /c
最终路径:/a/b/c
推荐只使用绝对路径
1.10 ONBUILD:指定基于所生成镜像创建子镜像时自动执行的操作指令。
格式:ONBUILD [INSTRUCTION]
示例:使用dockerfile构建父镜像ParentImage,指定ONBUILD指令
# Docker for parentImage
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
使用docker build 创建子镜像childImage时,会首先执行ParentImage中配置的ONBUILD指令:
FROM parentImage
等价于在ChildImage的dockerfile中添加了:
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
由于
ONBUILD是隐式执行的,推荐在使用它的镜像标签中进行标注:如ruby:2.1-onbuild
ONBUILD在创建专门用于自动编译、检查等操作的基础镜像的时候非常有用。
1.11 STOPSIGNAL:指定所创建镜像启动的容器接收退出的信号值。
STOPSIGNAL signal
1.12 HEALTHCHECK:配置所启动容器如何进行健康检查。(dockjer 1.12+支持)
2种格式:
- HEALTHCHECK [OPTIONS] CMD command:根据所执行命令返回值是否为0来判断
- HEALTHCHECK NONE: 禁止基础镜像中的健康检查
OPTIONS支持的参数:
- -interval = DURATION (default: 30s) 过多久检查一次
- -timeout = DURATION(default: 30s)每次检查等待结果的超时时间
- -retries = N (default: 3) 如果失败了,重试几次才最终确定失败
1.13 SHELL: 指定其他命令使用shell时的默认shell类型:
SHELL ["executable","parameters"]
默认值:["/bin/sh","-c"]
windows: shell路径使用了 \ 做为分隔符,一般在dockerfile开头加#eccape=' 来指定转义符。
2.操作指令:
2.1 RUN : 运行指定命令。
格式: RUN <command>
或 RUN ["executable","parameter1",""parameter2"]
RUN ["executable","parameter1",""parameter2"]这种方式的指令会被解析为json数组,所以需用双引号。且使用exec执行,不会启动shell环境。
RUN <command> 这种方式默认将在shell终端中运行命令。即/bin/sh -c;
指定使用其他终端类型可以使用第2个方式:
例如 RUN["/bin/bash","-c","echo yinlei"]
每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。
命令较长使用 \ 来换行。
2.2 CMD: 用来指定启动容器时默认执行的命令。
支持3种格式:
- CMD ["executable","parameter1",""parameter2"]:相当于executable param1 param2
- CMD cmmand param1 param2 :在默认的shell中执行,提供给需要交互的应用。
- CMD ["param1","param2"]: 提供给ENTRYPOINT的默认参数
每个dockerfile只能有一条CMD命令。
指定多条命令,只有最后一条生效。
用户启动容器时候手动指定了运行的命令参数,则会覆盖掉dockerfile中指定的命令。
2.3 ADD:添加内容到镜像。
格式:ADD <src> <dest>
复制指定的<src>路径下的内容到容器中的<dest>路径下。
<src>可以是dockefile所在目录下的一个相对路径,可以是一个URL,可以是一个tar文件(自动解压为目录).
<dest>可以是镜像内绝对路径,或相对于工作目录WORKDIR的相对路径
路径支持正则。
例如:ADD *.c / code/
2.4 COPY: 复制内容到镜像。
格式: COPY <src> <dest>
复制本地主机的<src>(dockerfile所在目录的相对路径,文件或目录)下内容到镜像中的<dest>。目标路径不存在时会自动创建。
路径支持正则。
COPY与ADD指令功能类似,当使用本地目录为源目录时,推荐COPY
创建镜像:
编写完dockefile后,通过docker [image] build命令来创建镜像。
格式:docker build [OPTIONS] PATH | URL | -
读取指定路径下(包括子目录)的dockefile.并将该路径下所有数据作为上下文context发送给docker服务端。
dockerf服务端在校验dockefile格式通过后,逐条执行其中定义的命令,碰到ADD COPY RUN指令会生成一层新的镜像。最终如果创建镜像成功会返回镜像的ID。
如果上下文过大,会导致发送大量数据给服务端,延缓创建过程。
除非是生成镜像所必须的文件,不然不要放回到上下文路径下。如果使用非上下文路径下的dockerfile,可以通过-f选项来指定其路径。
要指定生成镜像的标签信息,可通-t选项。可以重复使用-t为镜像一次添加多个名称。
OPTIONS支持的选项:
选项 | 说明 |
-add-host list | 添加自定义的主机名到IP的映射 |
-build-arg list | 添加创建时的变量 |
-cache-from strings | 使用指定镜像作为缓存源 |
-cgroup-parent string | 继承的上层cgroup |
-compress | 使用gzip来压缩创建上下文数据 |
-cpu-period int | 分配的CFS调度器时长 |
-cpu-quota int | CFS调度器总份额 |
-c, -cpu-shares int | CPU权重 |
-cpuset-cpus string | 多CPU允许使用的CPU |
-cpuset-mems string | 多CPU允许使用的内存 |
-disable-content-trust | 不进行镜像校验,默认为真 |
-f, -file string | dockerfile名称 |
-force-rm | 总是删除中间过程的容器 |
-iidfile string | 将镜像id写入到文件 |
-isolation string | 容器的隔离机制 |
-label list | 配置镜像的元数据 |
-m,-memory bytes | 限制内存和缓存的总量 |
-memory-swap bytes | k限制内存和缓存的总量 |
-network string | 指定RUN命令时候的网络模式 |
-no-cache | 创建镜像时不使用缓存 |
-platform string | 指定平台类型 |
-pull | 总是尝试获取镜像的最新版本 |
-q,-quiet | 不打印创建过程中的日志信息 |
-rm | 创建成功后自动删除中间过程容器,默认为真 |
-security-opt strings | 指定安全相关的选项 |
-shm-size bytest | /dev/shm的大小 |
-squash | 将新创建的多层挤压放入到一层中 |
-stream | 持续获取创建的上下文 |
-t,-tag list | 指定镜像的标签列表 |
-target string | 指定创建的目标阶段 |
-ulimit ulimit | 指定ulimit的配置 |
选择父镜像:
大部分情况下,生成的新镜像都需要通过FROM指令来指定父镜像。
父镜像是生成镜像的基础,会直接影响到所生成镜像的带下和功能。
用户可以选择2种镜像作为父镜像:
- 基础镜像
- 普通镜像(往往由第三方创建,基于基础镜像)
基础镜像比较特殊,其dockfile中往往不存在FROM指令,或者基于scratch镜像(FROM scratch),意味着其在整个镜像树中处于根的位置。
普通镜像也可以作为父镜像来使用,包括常见的busybox 、debian 、 ubuntu等
Docker不同类型镜像之间的继承关系:
使用.dockerignore文件:
每一行添加一条匹配模式让Docker忽略匹配路径或文件,在创建镜像时不将无关的数据发送到服务器。使用方法和git的.gitignore类似。
多步骤创建:docker17.05+
Docker支持多步骤镜像创建特性,精简最终生成的镜像大小i。
对于需要编译的应用,通常至少需要准备2个环境的docker镜像:
- 编译环境镜像:包括完整的编译引擎、依赖库等,作用是编译应用为2进制文件。
- 运行环境镜像:利用编译好的2进制文件,运行应用,由于不需要编译环境,体积较小。
优点:使用单一的dockefile,降低维护复杂度。
示例:
以go为例:
创建干净目录,并创建main,go
package main
import("fmt")
func main(){
fmt.Println("hello world");
}
创建dockerfile,使用golang:1.9镜像编译应用2进制文件为app,使用精简的镜像alpine:latest作为运行环境。
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 -cacertificates
WORDIR /root/
COPY --from=builder /go/src/test/app .
CMD ["./app"]
执行如下命令创建镜像并运行应用:
docker build -t yeasy/test-multistage:latest
docker run --rm yeasy/test-multistage:latest
查看成功的镜像大小:
docker images|grep test-multistage
合理的构建镜像:
尝试从下面几个角度进行思考完善镜像:
- 精简镜像用途:尽量让每个镜像的用途都比较单一集中,避免构造大而复杂、多功能的镜像
- 选用合适的基础镜像:容器的核心是应用。选择过大的父镜像会造成最终生成应用镜像的臃肿,推荐选用瘦身过的应用镜像或选择较为小巧的系统镜像
- 提供注释和维护者信息
- 正确使用版本号:通过版本号可以避免环境不一致导致的问题
- 减少镜像层数:若希望所生成的镜像的层数尽量少,则要尽量合并RUN、ADD、COPY指令。多个RUN指令可以合并为1条RUN指令
- 恰当使用多步骤创建:通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但需要维护多个Dockefile.
- 使用.dockerignore文件:标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容。
- 及时删除临时文件和缓存文件:特别是在执行apt-get指令后,/var/cache/apt下会缓存一些安装包
- 提升生成速度:合理的使用cache或.dockerignore
- 调整合理的指令顺序:开启cache的情况下,内容不变的指令尽量放在前面,这样可以尽量复用
- 减少外部源的告饶:需要从外部引入数据时,需要指定持久的地址,并带版本信息等。