原文链接 https://blog.alexellis.io/mutli-stage-docker-builds/
相关参考 https://docs.docker.com/develop/develop-images/multistage-build/
关键词 - COPY --from=
是么是建造器模式? 对于像Golang这样的静态编译语言,人们倾向从Golang“ SDK”镜像中获取其Dockerfile,添加源代码,进行构建,然后将其推送到Docker Hub。不幸的是,所得图像的大小非常大-至少为670mb。
A workaround which is informally called the builder pattern involves using two Docker images - one to perform a build and another to ship the results of the first build without the penalty of the build-chain and tooling in the first image.
有个变通之法(非正式版),叫建造器模式,涉及到两个Docker镜像, 一个用于执行构建,另一个用于调用第一个构建的结果,后者完成后不会影响第一个镜像中内容。
建造器模式的示例: 从Golang基本映像派生整个运行时/ SDK(Dockerfile.build) 添加源代码 产生一个静态链接的二进制文件 将静态二进制文件从映像复制到主机(docker create,docker cp) 源自SCRATCH或其他一些轻量级图像,例如alpine(Dockerfile) 重新添加二进制文件 将微小的映像推送到Docker Hub
一个例子:
Dockerfile.build
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 .
Dockerfile
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
build.sh
#!/bin/sh echo Building 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 docker create --name extract alexellis2/href-counter:build docker cp extract:/go/src/github.com/alexellis/href-counter/app ./app docker rm -f extract echo Building alexellis2/href-counter:latest docker build --no-cache -t alexellis2/href-counter:latest .
如何理解 多阶段构建 ?
Multi-stage builds give the benefits of the builder pattern without the hassle of maintaining three separate files: 多阶段构建提供了构建器模式的优点,而无需维护多个单独的文件:
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"]
This is huge for developers and maintainers, especially when you support multiple Dockerfiles for different architectures such as the Raspberry Pi. 对于开发人员和维护人员而言这是很大工作量,尤其是为树莓派等不同的架构支持多个Dockerfile时。
The general syntax involves adding FROM additional times within your Dockerfile - whichever is the last FROM statement is the final base image. To copy artifacts and outputs from intermediate images use COPY --from=<base_image_number> 不论哪个FROM是最后基础镜像的声明,通常会涉及额外次数去使用FROM 去中间层的镜像复制内容,可以使用 COPY --from=<base_image_number> The second PR mentioned improves on this syntax and when merged would mean you can do something more like: 第二个公共版本中,提到改进了这种语法,也就是“合并”后意味着您可以做更多的事情,如:
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"]
注: 1、FROM golang:1.7.3 as builder 给镜像取个别名 builder 2、WORKDIR 当前工作目录 /go/src/github.com/alexellis/href-counter/app 3、RUN 构建镜像时运行命令 4、COPY 将脚本app.go拷贝到当前工作目录 5、FROM alpine:latest 基于 alpine 一个小巧的 linux 6、RUN apk --no-cache add ca-certificates 7、FROM alpine:latest 基于 alpine 一个小巧的 linux 8、WORKDIR /root/ 切换至root目录 9、COPY --from=builder /go/src/github.com/alexellis/href-counter/app . 将上一层镜像复制制定目录复制到当前工作目录 10、实例时运行 ./app
那么我门们该如何做呐?
基于mater分支构建docker
You can create a development build of Docker at any time by cloning the docker/docker repository and typing in make tgz. The resulting build will create binaries for you in the bundles folder. 您可以随时克隆docker / docker仓库,并输入make tgz来创建Docker的开发版本。 生成的版本将在 打包文件夹中为您创建二进制文件。
构建步骤如下:
$ git clone https://github.com/docker/docker $ cd docker $ make tgz
让我用上述的COPY -- from 新特性,做个范例,看看有什么样的效果
Launch Docker within the container you built above:
在之前你创建的容器中启动Docker
These steps prepare the new Docker version for use:
以下步骤准备用新版Docker来供使用
$ docker run -v `pwd`/bundles:/go/src/github.com/docker/docker/bundles --privileged -ti docker-dev:master bash
The Docker development build creates an image called docker-dev
. You can actually run Docker inside this image, which is what we'll do below:
Docker开发版本会创建一个名为docker-dev的镜像, 您实际上可以在此镜像中运行Docker,这是我们将在下面执行的操作:
$ export PATH=$PATH:`pwd`/bundles/latest/dynbinary-daemon:`pwd`/bundles/latest/binary-client/
$ dockerd &
Now still within the container, clone my repository and initiate a build using the multi-step Dockefile:
现在仍在此容器中,克隆我的仓库,并使用多步骤Dockefile启动构建:
$ git clone https://github.com/alexellis/href-counter $ cd href-counter $ docker build -t href-counter . -f Dockerfile.multi
the -f
flag allows you to specify the name of a different Dockerfile.
-f 的标签语法 是可以让你为其他的Dockerfile指定名字。
Now run the Docker image:
$ docker run -e url=https://www.alexellis.io/ multi {"internal":9,"external":5} $ docker run -e url=https://www.docker.com multi {"internal":97,"external":38}
Compare the differences in size between the resulting image and what we would have had if we used FROM golang
:
对比 使用的是FROM golang
时和我们自己构建的容器大小区别:
REPOSITORY TAG IMAGE ID CREATED SIZE multi latest bcbbf69a9b59 6 minutes ago 10.3MB golang 1.7.3 ef15416724f6 4 months ago 672MB
总结
The builder pattern was effective as a work-around and would have created a binary of a similar size, but it was hard to maintain and very hard to use with Docker's automated build system. 建造器模式,建出一个相似大小的二进制的文件,可以非常有效的解决问题,但是,它也难以维护,很难与Docker的自动化系统一起使用。
If you need a small image - you should follow the builder pattern for now using the example above.