说一说Docker multi-stage builds
at 2年前 ca Docker pv 733 by touch
前言
这个特性很早之前,在 2017 年初的时候就可以使用了。如果你没有用到,很有可能你不需要编译语言,就比如 C/C++/Golang/Java 之类的语言。
现在网上有非常多的教程告诉我们,打包 Docker 镜像的时候,我们需要把镜像缩减到最小,因此我们可以看到最佳实践是类似于这样的:
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ && rm -rf /var/lib/apt/lists/*
又或者这样的:
RUN npm install --production \ && rm -rf $HOME/.npm
还有这样的:
RUN pip install -r requirements --no-cache-dir
这些技巧的目的都在于把改动的内容缩减到最小,因为没必要把缓存之类的内容也放到镜像中去,这样就可以把镜像尽可能缩小。
而今天介绍的 Multi-Stage builds 就是达到这个目的的另一个重要手段。
很早之前
对于编译型的语言,我们可能需要编译完成后,将依赖与编译好的二进制文件(artifacts,即制品)拷贝到一个新的小镜像中来进行打包了,比如我们会把镜像的打包过程放到 CI 中去,大概的内容包括:
测试
安装测试依赖
静态检查
单元测试
编译
安装编译依赖
编译
打包镜像
安装依赖
将编译制品拷贝到空镜像
将镜像推送到镜像库中
我们可以看到,编译与打包的步骤需要分开,因为我们需要:
解耦,简化整体的过程;
两者如果是基于不同 CI 运行镜像的话,我们还需要用到不同的镜像;
需要用到 CI 提供的并行执行能力;
于是我们的问题出现了:如何将编译制品传送至打包步骤?用缓存是比较简单的,但是需要保证缓存只能被当前的编译步骤使用,不然会导致编译的版本错误。
Gitlab 提供的 dependencies
也能做到这一点,自动将你制定步骤的 artifacts 下载下来,只不过这样的步骤会显得多余:有时候你只是想用来打包镜像而已,而如果 artifacts 上传至 Gitlab 服务器,还有那缓慢速度的限制,拖慢整体打包步骤。
所以,我们的救星来了,Multi-stage builds。
现在
现在这个步骤就很简单了,它允许你把所有的步骤放到一个 Dockerfile 中去完成,拿官网上的例子来说:
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"]
它将我之前说的 CI 编译与打包步骤合并了,可以注意到的是它有两个 FROM
关键字,这就是这个多阶段的关键所在了,它允许多个 FROM
来达到多阶段的目的,并且,最后一个 FROM
才是你真正的打包步骤,利用这个特性,你可以将最后打包完成的镜像做到最小。
这样还有一个好处,你可以更方便地迁移到其它 CI 平台了,毕竟两个步骤合并成一个了。
另外,这样的改变,在不注意的情况下,会让你的编译过程变慢,因为没有缓存。
解决方案也是有的,就是官网上说的是 Stop at a specific build stage,我们在编译的时候,需要多加一行镜像编译命令,就拿上面的例子来说:
docker build -t . example-registry.com/app:latest docker push example-registry.com/app:latest
我们可以利用 --target
以及 --cache-from
来实现:
# 将最新已有的 builder 镜像拉下来 docker pull example-registry.com/app:builder || true # --cache-from 就是关键了,它就可以复用缓存了 # 另外还有个 --target 即告诉 docker # 只要编译到 builder 这个就停止,这样可以把缓存保留下来 docker build \ --cache-from=example-registry.com/app:builder \ --target builder \ -t example-registry.com/app:builder \ . # 将最新已有的镜像拉下来 docker pull example-registry.com/app:latest || truedocker build \ --cache-from=example-registry.com/app:builder \ --cache-from=example-registry.com/app:latest \ -t example-registry.com/app:latest \ . # 将最新的镜像以及 builder 镜像推送至远程 docker push example-registry.com/app:latest docker push example-registry.com/app:builder
版权声明
本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。