Dockerfile多阶段构建道理和运用场景

Docker 17.05版本今后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是许可一个Dockerfile 中涌现多个 FROM 指令。如许做有什么意义呢?

老版本Docker中为何不支撑多个 FROM 指令

在17.05版本之前的Docker,只许可Dockerfile中涌现一个FROM指令,这得从镜像的实质提及。

《Docker观点简介》 中我们提到,你可以简朴明白Docker的镜像是一个压缩文件,个中包括了你须要的顺序和一个文件体系。实在如许说是不严谨的,Docker镜像并不是只是一个文件,而是由一堆文件构成,最主要的文件是 层。

Dockerfile 中,大多数指令会天生一个层,比方下方的两个例子:

# 示例一,foo 镜像的Dockerfile
# 基本镜像中已存在若干个层了
FROM ubuntu:16.04

# RUN指令会增添一层,在这一层中,装置了 git 软件
RUN apt-get update \
  && apt-get install -y --no-install-recommends git \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*
# 示例二,bar 镜像的Dockerfile
FROM foo

# RUN指令会增添一层,在这一层中,装置了 nginx
RUN apt-get update \
  && apt-get install -y --no-install-recommends nginx \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

假定基本镜像ubuntu:16.04已存在5层,运用第一个Dockerfile打包成镜像 foo,则foo有6层,又运用第二个Dockerfile打包成镜像bar,则bar中有7层。

假如ubuntu:16.04 等其他镜像不算,假如体系中只存在 foo 和 bar 两个镜像,那末体系中一共保留了若干层呢?

是7层,并不是13层,这是由于,foo和bar同享了6层。层的同享机制可以勤俭大批的磁盘空间和传输带宽,比方你当地已有了foo镜像,又从镜像堆栈中拉取bar镜像时,只拉取当地所没有的末了一层就可以了,不须要把全部bar镜像连根拉一遍。然则层同享是如何完成的呢?

本来,Docker镜像的每一层只纪录文件变动,在容器启动时,Docker会将镜像的各个层举行盘算,末了天生一个文件体系,这个被称为 团结挂载。对此感兴趣的话可以进入相识一下 AUFS。

Docker的各个层是有相关性的,在团结挂载的过程当中,体系须要晓得在什么样的基本上再增添新的文件。那末这就请求一个Docker镜像只能有一个肇端层,只能有一个根。所以,Dockerfile中,就只许可一个FROM指令。由于多个FROM 指令会形成多根,则是没法完成的。但为何 Docker 17.05 版本今后许可 Dockerfile支撑多个 FROM 指令了呢,难道已支撑了多根?

多个 FROM 指令的意义

多个 FROM 指令并不是为了天生多根的层关联,末了天生的镜像,仍以末了一条 FROM 为准,之前的 FROM 会被扬弃,那末之前的FROM 又有什么意义呢?

每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然末了天生的镜像只能是末了一个阶段的效果,然则,可以将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

最大的运用场景是将编译环境和运转环境星散,比方,之前我们须要构建一个Go言语顺序,那末就须要用到go敕令等编译环境,我们的Dockerfile多是如许的:

# Go言语环境基本镜像
FROM golang:1.10.3

# 将源码拷贝到镜像中
COPY server.go /build/

# 指定事情目次
WORKDIR /build

# 编译镜像时,运转 go build 编译天生 server 顺序
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server

# 指定容器运转时进口顺序 server
ENTRYPOINT ["/build/server"]

基本镜像golang:1.10.3是异常巨大的,由于个中包括了一切的Go言语编译东西和库,而运转时刻我们仅仅须要编译后的server顺序就好了,不须要编译时的编译东西,末了天生的大体积镜像就是一种糟蹋。

运用脉冲云的处理办法是将顺序编译和镜像打包离开,运用脉冲云的编译构建效劳,挑选增添构Go言语构建东西,然后在构建步骤中编译。

末了将编译接口拷贝到镜像中就好了,那末Dockerfile的基本镜像并不须要包括Go编译环境:

# 不须要Go言语编译环境
FROM scratch

# 将编译效果拷贝到容器中
COPY server /server

# 指定容器运转时进口顺序 server
ENTRYPOINT ["/server"]

提醒:
scratch 是内置关键词,并不是一个实在存在的镜像。
FROM scratch 会运用一个完整清洁的文件体系,不包括任何文件。 由于Go言语编译后不须要运转时,也就不须要装置任何的运转库。FR
OM scratch可以使得末了天生的镜像最小化,个中只包括了
server 顺序。

在 Docker 17.05版本今后,就有了新的处理方案,直接一个Dockerfile就可以处理:

# 编译阶段
FROM golang:1.10.3

COPY server.go /build/

WORKDIR /build

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server

# 运转阶段
 FROM scratch

# 从编译阶段的中拷贝编译效果到当前镜像中
COPY --from=0 /build/server /

ENTRYPOINT ["/server"]

这个 Dockerfile 的微妙的地方就在于 COPY 指令的--from=0 参数,从前边的阶段中拷贝文件到当前阶段中,多个FROM语句时,0代表第一个阶段。除了运用数字,我们还可以给阶段定名,比方:

# 编译阶段 定名为 builder
FROM golang:1.10.3 as builder

# ... 省略

# 运转阶段
FROM scratch

# 从编译阶段的中拷贝编译效果到当前镜像中
COPY --from=builder /build/server /

更加壮大的是,COPY --from 不只可以从前置阶段中拷贝,还可以直接从一个已存在的镜像中拷贝。比方,

   FROM ubuntu:16.04
    
   COPY --from=quay.io/coreos/etcd:v3.3.9 /usr/local/bin/etcd /usr/local/bin/

我们直接将etcd镜像中的顺序拷贝到了我们的镜像中,如许,在天生我们的顺序镜像时,就不须要源码编译etcd了,直接将官方编译好的顺序文件拿过来就好了。

有些顺序要么没有apt源,要么apt源中的版本太老,要么痛快只提供源码须要本身编译,运用这些顺序时,我们可以轻易地运用已存在的Docker镜像作为我们的基本镜像。然则我们的软件有时刻能够须要依靠多个这类文件,我们并不能同时将 nginx 和 etcd 的镜像同时作为我们的基本镜像(不支撑多根),这类情况下,运用 COPY --from 就异常轻易实用了。

    原文作者:赵一凡
    原文地址: https://segmentfault.com/a/1190000016137548
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞