使用多阶段构建
预计阅读时间:6分钟
多阶段构建对于在优化Dockerfile的同时使其易于阅读和维护的任何人都非常有用。
致谢:特别感谢Alex Ellis允许使用他的博客文章 Builder模式与Docker 中的多阶段构建作为以下示例的基础。
在进行多阶段构建之前
关于构建图像,最具挑战性的事情之一是保持图像尺寸变小。Dockerfile中的每条指令都会在映像中添加一层,您需要记住在移至下一层之前清除不需要的任何工件。为了编写一个真正有效的Dockerfile,传统上,您需要使用shell技巧和其他逻辑来使各层尽可能小,并确保每一层都具有上一层所需的工件,而没有其他任何东西。
实际上,通常有一个Dockerfile用于开发(包含构建应用程序所需的一切),而一个精简的Dockerfile用于生产时,仅包含您的应用程序以及运行它所需的内容。这被称为“构建者模式”。维护两个Dockerfile是不理想的。
这是一个示例,Dockerfile.build
并且Dockerfile
遵循上述构建器模式:
Dockerfile.build
:
# syntax=docker/dockerfile:1
FROM golang:1.16
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 .
请注意,此示例还RUN
使用Bash&&
运算符人为地将两个命令压缩在一起,以避免在图像中创建额外的图层。这是容易失败的并且难以维护。例如,插入另一个命令很容易,而忘记使用该\
字符继续该行。
Dockerfile
:
# syntax=docker/dockerfile:1
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 container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app
运行build.sh
脚本时,它需要构建第一个图像,从中创建一个容器以复制工件,然后构建第二个图像。这两个映像都占用了系统空间,并且app
本地磁盘上也仍然有工件。
多阶段构建极大地简化了这种情况!
使用多阶段构建
通过多阶段构建,您可以FROM
在Dockerfile中使用多个语句。每个FROM
指令可以使用不同的基础,并且每个指令都可以开始构建的新阶段。您可以有选择地将工件从一个阶段复制到另一个阶段,从而在最终图像中留下您不需要的所有内容。为了展示它是如何工作的,让我们改编上一部分中的Dockerfile以使用多阶段构建。
Dockerfile
:
# syntax=docker/dockerfile:1
FROM golang:1.16
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。您也不需要单独的构建脚本。快跑吧docker build
。
$ docker build -t alexellis2/href-counter:latest .
最终结果是与以前相同的微小生产图像,并显着降低了复杂性。您不需要创建任何中间映像,也不需要将任何工件提取到本地系统。
它是如何工作的?第二FROM
条指令以alpine:latest
图像为基础开始新的构建阶段。该COPY --from=0
行仅将之前阶段的构建工件复制到新阶段。Go SDK和任何中间工件都被保留了下来,没有保存在最终图像中。
命名您的构建阶段
默认情况下,这些阶段未命名,您可以通过它们的整数来引用它们,对于第一FROM
条指令,它们以0开头。但是,您可以通过AS <NAME>
在FROM
指令上添加一个来命名您的阶段。本示例通过命名阶段并在COPY
指令中使用名称来改进前一个示例。这意味着,即使稍后对Dockerfile中的指令进行了重新排序,它们COPY
也不会中断。
# syntax=docker/dockerfile:1
FROM golang:1.16 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"]
在特定的构建阶段停止
构建映像时,不一定需要构建包括每个阶段的整个Dockerfile。您可以指定目标构建阶段。以下命令假定您正在使用上一个命令,Dockerfile
但在名为的阶段停止builder
:
$ docker build --target builder -t alexellis2/href-counter:latest .
一些可能非常强大的方案是:
- 调试特定的构建阶段
- 使用
debug
启用了所有调试符号或工具的production
阶段以及精益阶段 - 使用一个
testing
阶段,在该阶段中,您的应用程序将填充测试数据,但使用另一个阶段使用实际数据构建生产环境
将外部图像用作“舞台”
使用多阶段构建时,您不仅限于从之前在Dockerfile中创建的阶段进行复制。您可以使用COPY --from
指令从单独的映像进行复制,方法是使用本地映像名称,本地或Docker注册表上可用的标签或标签ID。Docker客户端在必要时提取映像并从那里复制工件。语法为:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
将上一个阶段用作新阶段
您可以在使用FROM
伪指令时引用前一个阶段结束的地方。例如:
# syntax=docker/dockerfile:1
FROM alpine:latest AS builder
RUN apk --no-cache add build-base
FROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp
FROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
版本兼容性
Docker Engine 17.05中引入了多阶段构建语法。
图像,容器,最佳实践,多阶段,多阶段