Use multi-stage builds

预计阅读时间:6分钟

多阶段构建对于努力优化 Dockerfile 并使其易于阅读和维护的任何人都非常有用.

致谢:特别感谢Alex Ellis允许使用他的博文Builder pattern vs. Multi-stage builds in Docker作为以下示例的基础.

Before multi-stage builds

构建图像最具挑战性的事情之一就是缩小图像大小. Dockerfile 中的每条指令都会为映像添加一层,您需要记住在继续下一层之前清理您不需要的任何工件. 要编写一个真正高效的 Dockerfile,您通常需要使用 shell 技巧和其他逻辑来保持层尽可能小,并确保每一层都具有前一层所需的工件,而没有其他任何东西.

实际上,有一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容)和一个精简的用于生产的 Dockerfile 是很常见的,它只包含您的应用程序以及运行它所需的内容. 这被称为"建造者模式". 维护两个 Dockerfile 并不理想.

这是一个Dockerfile.buildDockerfile的示例,它们遵循上面的构建器模式:

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 .

请注意,此示例还使用 Bash &&运算符人为地将两个RUN命令压缩在一起,以避免在图像中创建额外的层. 这是容易失败且难以维护的. 例如,很容易插入另一个命令而忘记使用\字符继续该行.

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工件.

多阶段构建大大简化了这种情况!

Use multi-stage builds

对于多阶段构建,您可以在 Dockerfile 中使用多个FROM语句. 每个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 和任何中间工件都被留下,不会保存在最终图像中.

Name your build stages

默认情况下,这些阶段没有命名,您可以通过它们的整数来引用它们,第一个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"]  

Stop at a specific build stage

构建镜像时,不一定需要构建整个 Dockerfile,包括每个阶段. 您可以指定目标构建阶段. 以下命令假定您使用的是以前的Dockerfile ,但在名为builder的阶段停止:

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

A few scenarios where this might be very powerful are:

  • 调试特定的构建阶段
  • 使用启用了所有调试符号或工具的debug阶段,以及精益production阶段
  • 使用testing阶段,您的应用程序会在其中填充测试数据,但使用使用真实数据的不同阶段构建生产

Use an external image as a “stage”

使用多阶段构建时,您不仅限于从之前在 Dockerfile 中创建的阶段进行复制. 您可以使用COPY --from指令从单独的映像中复制,可以使用本地映像名称、本地或 Docker 注册表上可用的标记或标记 ID. 如有必要,Docker 客户端会拉取映像并从那里复制工件. 语法是:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

Use a previous stage as a new stage

在使用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

Version compatibility

Docker Engine 17.05 中引入了多阶段构建语法.

images, containers, 最佳实践, multi-stage, multistage

by  icopy.site