欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Docker image 多阶构建(multstage build)

程序员文章站 2024-03-12 22:21:02
...

参考:https://docs.docker.com/develop/develop-images/multistage-build/

背景

过大的image占用更多的磁盘空间,push或者pull操作时占用更多的网络带宽,花费更长的时间。大规模的部署过程可能需要数个小时,大部分时间浪费在image的push与pull操作中,甚至因为短时间内流量的爆发而引起网络问题,最终导致部署失败。因此,我们应该尽量构建更小尺寸的image。

Iamge的构建过程写在Dockerfile文件中,文件中的每一条指令都会叠加一个新的层。相应地,缩小image的方法有两个,一个是尽量减少Dockerfile中指令的条数,指令条数越少,image中的层数就越少。另一个是精简每层的内容,使之只包含必要的东西。

为此在写Dockerfile时往往会利用SHELL脚本技巧,将多条命令连接成一条命令,并在其中加入一些逻辑。另外,可以将同一个image的Dockerfile分成两份,一个Dockerfile供开发者使用,里边包含开发套件、各种工具、运行时环境等一切构建、运行所需要的东西。另一个Dockerfile供部署人员使用,里边只包含开发者构建好的成果物及运行时环境,删除了开发套件、工具等开发人员才需要的东西,称之为建造者模式(builder pattern)。

构建者模式(builder pattern)

一个建造者模式的例子,首先有两份image构建文件,名称分别为Dockerfile.buildDockerfile。

Dockerfile.build内容如下:

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

上边文件中使用的基础镜像是golang:1.7.3,它包含goland语言完整的编译、调试、及运行环境,尺寸较大。这个文件供开发者使用,每次修改完app.go中的代码以后,用这个文件build镜像,然后交互式运行,进去运行、调试代码。

Dockerfile文件内容如下:

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]  

使用的基础镜像为alpine:latest,它只包含运行golang可执行程序的运行时环境,尺寸较小。另外,它需要使用Dockerfile.build中编译好的golang可执行文件app,整个过程可通过一个shell脚本build.sh实现,脚本内容如下:

#!/bin/sh
echo Building alexellis2/href-counter:build

# 通过Dockerfile.build文件构建image,image名称为alexellis2/href-counter:build
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \  
    -t alexellis2/href-counter:build . -f Dockerfile.build

# 为上一步中构建好的镜像alexellis2/href-counter:build创建container,名称为extract
docker container create --name extract alexellis2/href-counter:build  
# 将extract中的app文件复制到当前目录
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
# 删除extract容器  
docker container rm -f extract

echo Building alexellis2/href-counter:latest
# 用Dockerfile文件构建镜像,它会使用刚才复制到当前目录的app文件
docker build --no-cache -t alexellis2/href-counter:latest .
# 删除当前目录中的app文件
rm ./app

这样,通过build.sh脚本构建出的image尺寸会较小,然后可将此image push到镜像创建*部署人员使用。

可以看到为了缩小image的尺寸,上述构建过程有点小复杂。涉及三个文件Dockerfile.build、Dockerfile、build.sh,增加了管理的难度。为了减少Dockerfile中指令的数量,使用了shell中的&&命令连接符,降低了可读性。

多阶构建(multstage build)

我们当然希望最好只需要一个Dockerfile文件,并且文件中的指令简单明了,就能达成上述三个文件才能完成的目标。于是从Docker17.0.5版本开始,引入了"多阶构建(multstage build)"的功能。以上述构建过程为例,只需要一个Dockerfile文件,并且不需要额外的脚本,Dockerfile文件内容如下:

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  

在同一个Dockerfile文件中,包含两条FROM指令,分别引用两个不同的基础image,本质上包含了两个image的构建过程,代表了上述方法中两个不同的阶段,所以是多阶构建。另外,后者可以直接引用前者的中间产物,在本例中,后者直接COPY前者生成的/go/src/github.com/alexellis/href-counter/app文件。

然后运行如下命令:

docker build.
# 或者
docker build -t alexellis2/href-counter:latest .

最终生成的镜像是以alpine:latest为基础的镜像,小尺寸的供部署人员使用。第一个阶段以golang:1.7.3为基础镜像的构建过程不会生成最终结果,它相当于整个构建过程中的准备阶段。

如果只想要第一阶段构建的image以供开发人员调试,怎么办呢?通过给每个阶段加上名字,构建时指定阶段名就可以得到相应的结果,如下:

FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  

第一阶段被命名为builder,执行如下命令:

$ docker build --target builder -t alexellis2/href-counter:latest .

注意其中的参数--target builder,此命令完成第一阶段的构建后就结束,最终结果是第一阶段构建的image。如果不加--target builder,两个构建过程都会执行,并且最终结果是第二阶段的构建结果。相比于建造者模式,多阶构建简洁、灵活得多,并且不需要SHELL脚本技巧。