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

在多阶段构建之前

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

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

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

Dockerfile.build

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 命令人为压缩在一起,以避免在镜像中创建额外的层。这很容易发生故障,也很难维护。例如,很容易插入另一个命令而忘记使用 \ 字符继续行。

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

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

使用多阶段构建

对于多阶段构建,可以在 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)阶段,在这个阶段你的应用会被测试数据填充,但是在构建产品时,使用一个使用真实数据的不同阶段。

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

当使用多阶段构建时,您不受限于从 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 官网

译者 : 技术译民

出品 : 技术译站

链接 : 英文原文

使用 Docker 开发 - 使用多阶段构建镜像的更多相关文章

  1. Docker容器学习梳理 - Dockerfile构建镜像

    在Docker的运用中,从下载镜像,启动容器,在容器中输入命令来运行程序,这些命令都是手工一条条往里输入的,无法重复利用,而且效率很低.所以就需要一 种文件或脚本,我们把想执行的操作以命令的方式写入其 ...

  2. docker学习之使用 DockerFile 构建镜像并搭建 swarm+compose 集群

    题目要求 (1)将springboot应用程序打成jar包:Hot.jar (2)利用dockerfile将Hot.jar构建成镜像 (3)构建 Swarm 集群 (4)在 Swarm 集群中使用 c ...

  3. docker multi-stage 多阶段构建

    多阶段构建 一.需求 二.普通构建 1.编写Dockerfile 2.构建镜像 三.多阶段(multi-stage)构建 1.编写Dockerfile 2.构建镜像 四.比较2个镜像的体积大小 我们在 ...

  4. docker构建镜像

    Docker 提供了两种构建镜像的方法: docker commit 命令Dockerfile 构建文件 示例: Dockerfile FROM golang:1.7.5 #基础镜像 RUN apt- ...

  5. (干货)构建镜像之docker commit

    Docker提供了两种构建镜像的方法:docker commit命令喝Dockerfile构建文件. docker commit   不推荐 (1).这是手工构建镜像的方式,容易出错,效率低且可重复性 ...

  6. Dockerfile 多阶段构建实践

    写在前面 在Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单.这篇小作文我们来学习一下如何编写实现多阶段构建的Dockerfile 关于doc ...

  7. 使用Dockerfile来构建镜像

    Dockerfile原理 创建Dockerfile Dockerfile实例 Dockerfile指令 注释 FROM MAINTAINER RUN ADD WORKDIR ENV USER COPY ...

  8. Docker中使用多阶段Dockerfile构建容器镜像image(镜像优化)

    使用多阶段构建 预计阅读时间: 6分钟 多阶段构建是守护程序和客户端上需要Docker 17.05或更高版本的新功能.多阶段构建对于那些努力优化Dockerfiles同时使其易于阅读和维护的人来说非常 ...

  9. 多阶段构建Golang程序Docker镜像

    Docker简介 Docker是基于Linux容器技术(LXC),使用Go语言实现的开源项目,诞生于2013年,遵循Apache2.0协议.Docker自开源后,受到广泛的关注和讨论. Docker在 ...

随机推荐

  1. Java实现 蓝桥杯 算法训练 Multithreading

    问题描述 现有如下一个算法: repeat ni times yi := y y := yi+1 end repeat 令n[1]为你需要算加法的第一个数字,n[2]为第二个,-n[N]为第N个数字( ...

  2. java实现数字黑洞

    任意一个 5 位数,比如:34256,把它的各位数字打乱,重新排列,可以得到 一个最大的数:65432,一个最小的数 23456. 求这两个数字的差,得:41976,把这个数字再次重复上述过程(如果不 ...

  3. 分布式ID总结

    分布式ID 生成的ID使用场景 几乎所有的业务系统,都有生成一个记录标识的需求,例如:message_id, order_id.这个记录标识往往就是数据库中的唯一主键,数据库上会建立聚集索引(clus ...

  4. 简谈Java语言的继承

    Java语言的继承 这里简谈Java语言的三大特性之二——继承. Java语言的三大特性是循序渐进的.是有顺序性的,应该按照封装-->继承-->多态这样的顺序依次学习 继承的定义 百度百科 ...

  5. Spring杂谈 | 从桥接方法到JVM方法调用

    前言 之所以写这么一篇文章是因为在Spring中,经常会出现下面这种代码 // 判断是否是桥接方法,如果是的话就返回这个方法 BridgeMethodResolver.findBridgedMetho ...

  6. mysql域名解析引起的远程访问过慢?

    MYSQL远程连接速度慢的解决方法 PHP远程连接MYSQL速度慢,有时远程连接到MYSQL用时4-20秒不等,本地连接MYSQL正常,出现这种问题的主要原因是, 默认安装的MYSQL开启了DNS的反 ...

  7. 从0到70%:Chrome上位揭秘!

    最近的数据显示,Chrome在2020年4月的市场份额达到了70%左右,把微软的Edge和Firefox远远甩在身后,毫无疑问,Chrome赢得了第二次游览器之战,成为新一代王者. Chrome的第一 ...

  8. SQLserver 的分页存储过程

      -- 1.建立修改学生数据的存储过程 -- 2.建立根据班级Id和学生姓名模糊查询的分页存储过程,要求正确输出总记录数,总页数-- (输入班学生姓名 计算总记录数 计算总页数) -- @name ...

  9. Optional 容器类

    什么是Optional容器类 Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的 ...

  10. Kafka源码解析(二)---Log分析

    上一篇文章讲了LogSegment和Log的初始化,这篇来讲讲Log的主要操作有哪些. 一般来说Log 的常见操作分为 4 大部分. 高水位管理操作 日志段管理 关键位移值管理 读写操作 其中关键位移 ...