本篇上接文章《Docker Dockerfile指令说明》。
讲述了dockerfile文件的制作,然后来制作镜像文件就需要用到docker build命令创建。
1. docker build
docker build [OPTIONS] PATH | URL | -
--add-host list # 添加自定义的主机名到IP地址的映射
--build-arg list # 设置镜像创建时的变量
--cache-from strings # 使用指定镜像作为缓存源
--cgroup-parent string # 继承的上层cgroup
--compress # 使用gzip来压缩创建上下文数据
--cpu-period int # 限制 CPU CFS 周期
-c,--cpu-quota int # 限制 CPU CFS 配额
--cpu-shares int # 设置 CPU 使用权重
--cpuset-cpus string # 多个CPU指定使用的 CPU
--cpuset-mems string # 多个CPU指定使用的内存 id
--disable-content-trust # 忽略镜像检验,默认开启
-f,-file string # 指定要使用的 Dockerfile 路径 (默认是 'PATH/Dockerfile')
--force-rm # 设置镜像过程中删除中间容器
--iidfile string # 将镜像ID写入到文件
--isolation string # 使用容器隔离技术
--label list # 设置镜像使用的元数据
-m, --memory bytes # 限制使用内存量
--memory-swap # 设置 Swap 的最大值为内存和缓存的总量,'-1' 表示不限 swap
--network string # 指定run命令的网络模式
--no-cache # 创建镜像的过程中不适用缓存
--pull # 尝试更新镜像的最新版本
-q,--quiet # 安静模式,不打印创建过程的日志信息
--rm # 设置镜像成功后删除中间容器
--shm-size # 设置 /dev/shm 的大小,默认值是 64M
--security-opt strings # 安全选项
--ulimit Ulimit # Ulimit 配置
-t, --tag list # 镜像的名字及标签,通常 name:tag 或者 name 格式,可以在一次构建中为一个镜像设置多个标签
--target string # 指定创建的目标阶段
该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下所有数据作为上下文(Context)发送给Docker服务端。Docker服务端在校验Dockerfile格式通过后,逐条执行其中定义的指令,碰到ADD、COPY和RUN指令会生成一层新的镜像。最终如果创建镜像成功,会返回最终镜像的ID。
如果上下文过大,会导致发送大量数据给服务端,延缓创建过程。因此除非是生成镜像所必需的文件,不然不要放到上下文路径下。如果使用非上下文路径下的Dockerfile,可以通过-f选项来指定其路径。
要指定生成镜像的标签信息,可以通过-t选项。该选项可以重复使用多次为镜像一次添加多个名称。
例如,上下文路径为/tmp/docker_builder/,并且希望生成镜像标签为builder/first image:1.0.0,
可以使用下面的命令:
docker build -t builder/first_image:1.0.0 /tmp/docker_builder/
使用当前目录的 Dockerfile 创建镜像,标签为 dongzao/example:v1
docker build -t dongzao/example:v1
使用 URL http://www.zabbx.cn/www/file 的 Dockerfile 创建镜像
docker build http://www.zabbx.cn/www/file
通过 -f Dockerfile 文件的位置
docker build -f /path/to/a/Dockerfile .
例如:
docker build -f /var/nginx_build/Dockerfile
2. 选择父镜像
大部分情况下,生成新的镜像都需要通过FROM指令来指定父镜像。父镜像是生成镜像的基础,会直接影响到所生成镜像的大小和功能。
用户可以选择两种镜像作为父镜像:
- 一种是所谓的基础镜像(baseimage)
- 一种是普通的镜像(往往由第三方创建,基于基础镜像)
基础镜像比较特殊,其Dockerfile中往往不存在FROM指令,或者基于scratch镜像(FROM scratch),这意味着其在整个镜像树中处于根的位置。
下面的Dockerfile定义了一个简单的基础镜像,将用户提前编译好的二进制可执行文件binary复制到镜像中,运行容器时执行binary命令:
FROM scratch
ADD binary/
CMD["/binary"]
普通镜像也可以作为父镜像来使用,包括常见的busybox、debian、ubuntu等。
3. .dockerignore文件
可以通过.dockerignore文件(每一行添加一条匹配模式)来让Docker忽略匹配路径或文件,在创建镜像时候不将无关数据发送到服务端。
当我们在 docker build 的过程中,首先会将指定的上下文目录打包传递给 docker引擎,而这个上下文目录中可能并不是所有的文件我们都会在 Dockerfile 中使用到,那么这个时候就可以在 .dockerignore 文件中指定在传递给 docker引擎 时需要忽略掉的文件或文件夹。
例如下面的例子中包括了6行忽略的模式(第一行为注释):
#.dockerignore文件中可以定义忽略模式
#排除 test 目录下的所有文件
test/*
#排除 md 目录下的 xttblog.md 文件
md/xttblog.md
#排除 xttblog 目录下的所有 .md 的文件
xttblog/*.md
#排除以 xttblog 为前缀的文件和文件夹
xttblog?
#排除所有目录下的 .sql 文件夹
**/*.sql
- dockerignore文件中模式语法支持Golang风格的路径正则格式:
- “*”表示任意多个字符;
- “?”代表单个字符;
- “!”表示不匹配(即不忽略指定的路径或文件)
4. 多步骤创建
自17.05版本开始,Docker 支持多步骤镜像创建(Multi-stage build)特性,可以精简最终生成的镜像大小。
对于需要编译的应用(如C、Go或Java语言等)来说,通常情况下至少需要准备两个环境的Docker镜像:
- 编译环境镜像:包括完整的编译引擎、依赖库等,往往比较庞大。作用是编译应用为二进制文件;
- 运行环境镜像:利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。
使用多步骤创建,可以在保证最终生成的运行环境镜像保持精简的情况下,使用单一的Dockerfile,降低维护复杂度。
以Go语言应用为例。创建干净目录,进入到目录中,创建main.go文件,内容为:
//main.go will output "Hello,Docker"
package main
import(
“fmt"
func main(){
fmt.Print1n("Hello,Docker")
创建Dockerfile,使用golang:1.9镜像编译应用二进制文件为app,使用精简的镜像alpine:1atest作为运行环境。Dockerfile 完整内容为:
FROM golang:1.9 as builder #define stage name 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--fromabuilder/go/src/test/app.# copy file from the builder stage
CMD["./app"]
执行如下命令创建镜像,并运行应用:
docker build-t yeasy/test-multistage:latest.
Sending build context to Docker daemon 3.072kB
step 1/10:FROM golang:1.9
Successfully built 5fdocb93dda0
Successfully tagged yeasy/test-multistage:latest
docker run--rm yeasy/test-multistage:latest
Hel1o,Docker
查看生成的最终镜像,大小只有6.55MB:
docker imageslgrep test-multistage
yeasy/test-multistage latest 5fdocb93dda0 1 minutes ago 6.55MB
5. 实践经验
所谓最佳实践,就是从需求出发,来定制适合自己、高效方便的镜像。
首先,要尽量吃透每个指令的含义和执行效果,多编写一些简单的例子进行测试,弄清楚了再撰写正式的Dockerfile。此外,Docker Hub官方仓库中提供了大量的优秀镜像和对应的Dockefile,可以通过阅读它们来学习如何撰写高效的Dockerfile。
- 精简镜像用途:尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像;
- 选用合适的基础镜像:容器的核心是应用。选择过大的父镜像(如Ubuntu系统镜像)会造成最终生成应用镜像的臃肿,推荐选用瘦身过的应用镜像(如node:slim),或者较为小巧的系统镜像(如alpine、busybox或debian);
- 提供注释和维护者信息:Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用;
- 正确使用版本号:使用明确的版本号信息,如1.0,2.0,而非依赖于默认的latest。通过版本号可以避免环境不一致导致的问题;
- 减少镜像层数:如果希望所生成镜像的层数尽量少,则要尽量合并RUN、ADD和COPY指令。通常情况下,多个RUN指令可以合并为一条RUN指令;
- 恰当使用多步骤创建:通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个Dockerfile。
- 使用.dockerignore文件:使用它可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程。
- 及时删除临时文件和缓存文件:特别是在执行apt-get指令后,/var/cache/apt下面会缓存了一些安装包;
- 提高生成速度:如合理使用cache,减少内容目录下的文件,或使用.dockerignore文件指定等;
- 调整合理的指令顺序:在开启cache的情况下,内容不变的指令尽量放在前面,这样可以尽量复用;
- 减少外部源的干扰:如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。
- 不要相信任意基础图像:始终使用漏洞扫描程序,一些开发人员将从别人创建的DockerHub中获取基本图像-因为乍一看它具有他们需要的软件包-然后将任意选择的容器推入生产环境。这样做有很多错误:您可能使用的代码版本错误,带有漏洞,其中有错误,或者更糟的是,它可能是故意捆绑了恶意软件-您只是不知道。
- 每个容器仅使用一个进程:与保持基本映像较小有关的是,每个容器只能进行一个处理。容器与其托管的应用程序具有相同的生命周期,这意味着每个容器应只包含一个父进程。通常,容器和应用程序应同时启动。同样,当应用停止时,容器也应停止。如果一个容器中有多个进程,则可能会出现应用程序状态混合的情况,这可能导致Kubernetes无法确定容器是否正常。
评论区