Dockerfile多阶段构建(一个 Dockerfile 导入多个镜像)

Docker 17.05版本以后,支持了多阶段构建,允许一个Dockerfile 中出现多个 FROM 指令。多阶段构建的作用是什么呢?

Docker的镜像内容中,并非只是一个文件,而是有依赖关系的层级结构,后面以前一层为基础,可以理解成类似 git 每一次 commit 都是一个层。 Dockerfile 中的大多数指令都会生成一个层。

多个 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程序就行了,不需要编译时的编译工具,最后生成的大体积镜像就是一种浪费。 可以再加一个构建步骤,创建第二个 Dockerfile:

最后将编译接口拷贝到镜像中就行了,那么Dockerfile的基础镜像并不需要包含Go编译环境:

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

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

# 指定容器运行时入口程序 server
ENTRYPOINT ["/server"]
提示: scratch 是内置关键词,并不是一个真实存在的镜像。  FROM scratch 会使用一个完全干净的文件系统,不包含任何文件。 因为Go语言编译后不需要运行时,也就不需要安装任何的运行库。FR OM scratch可以使得最后生成的镜像最小化,其中只包含了  server 程序。

而使用 docker 的多阶段构建特性,一个 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"]

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了,直接将官方编译好的程序文件拿过来就行了。

你可能感兴趣的:(Cloud,Native,软件开发,docker,容器,linux)