使用 Docker 开发 - 使用多阶段构建镜像

多阶段构建是一个新特性,须要 Docker 17.05 或更高版本的守护进程和客户端。对于那些努力优化 Dockerfiles 并使其易于阅读和维护的人来讲,多阶段构建很是有用。html

在多阶段构建以前

构建镜像时最具挑战性的事情之一就是缩小镜像大小。Dockerfile 中的每一条指令都会在镜像中添加一个层,在进入下一层以前,您须要记住清除全部不须要的工件。要编写一个真正高效的 Dockerfile,传统上须要使用 shell 技巧和其余逻辑来保持层尽量小,并确保每一层都有它须要的来自前一层的工件,而没有其余东西。linux

实际上,有一个 Dockerfile 用于开发环境(包含构建应用程序所需的全部内容),同时有一个精简的 Dockerfile 用于生产环境(仅包含应用程序和运行应用程序所需的内容)是很是常见的。这被称为“建造者模式”。维护两个 Dockerfiles 并不理想。nginx

这里有一个例子 Dockerfile.build 文件以及符合上述建造者模式的 Dockerfilegit

Dockerfile.buildgithub

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 .

请注意,此示例还使用 Bash 操做符 && 将两个 RUN 命使人为压缩在一块儿,以免在镜像中建立额外的层。这很容易发生故障,也很难维护。例如,很容易插入另外一个命令而忘记使用 \ 字符继续行。golang

Dockerfiledocker

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

build.shshell

#!/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 工件。app

多阶段构建极大地简化了这种状况!工具

使用多阶段构建

对于多阶段构建,能够在 Dockerfile 中使用多个 FROM 语句。每一个 FROM 指令均可以使用不一样的基镜像,而且它们都开始了构建的新阶段。您能够选择性地将工件从一个阶段复制到另外一个阶段,舍弃在最终镜像中您不想要的全部内容。为了说明这是如何工做的,让咱们使用多阶段构建调整前一节中的 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。您也不须要单独的构建脚本。只要运行 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 也不会破坏。

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"]

在特定的构建阶段中止

在构建映像时,没必要构建包括每一个阶段的整个 Dockerfile。你能够指定目标构建阶段。如下命令假设你正在使用以前的 Dockerfile,可是在名为 builder 的阶段中止:

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

这可能很是强有力的几个场景是:

  • 调试一个特定的构建阶段
  • 使用一个启用了全部调试符号或工具的 调试(debug) 阶段和一个精益的 生产(production) 阶段
  • 使用一个测试(testing)阶段,在这个阶段你的应用会被测试数据填充,可是在构建产品时,使用一个使用真实数据的不一样阶段。

docker-multi-stage-medium

使用外部镜像做为“阶段”

当使用多阶段构建时,您不受限于从 Dockerfile 中先前建立的阶段进行复制。您可使用 COPY --from 指令从单独的镜像中进行复制,可使用本地镜像名称、本地或 Docker 注册表上可用的标签或标签 ID。Docker 客户端会在必要时拉取镜像并从中复制工件。语法是:

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

把之前的阶段做为新的阶段

在使用 FROM 指令时,您能够引用前一阶段的内容。例如:

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 官网
译者 : 技术译民
出品 : 技术译站
连接 : 英文原文

相关文章
相关标签/搜索