docker 多阶段构建

November 27, 2018

docker多阶段构建出现主要是为了解决一些在容器中进行编译工作,然后运行时容器只需要编译时容器的结果,不需要整个编译环境被依赖,减少镜像的大小。 多阶段构建是从docker17.3之后开始的,以前一个Dockerfile中只能有一个FROM指令,从多阶段构建出现之后就可以在一个Dockerfile中进行多个FROM的引用。

多阶段构建出现以前

在多阶段构建出现以前,需要单独写一个Dockerfile出来进行构建,然后再写一个Dockerfile来做运行时的镜像,再通过写一个脚步来把两个镜像的结果拼接起来。

  • vim Dockerfile.build
FROM golang:1.7.3
WORKDIR /go/src/test/multi-stage-demo
COPY . .
#RUN cd /test/multi-stage-demo
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
  • vim Dockerfile.run
FROM scratch
# 多个文件copy
COPY main \
     config.json \
     /root/
# 暴露端口    
EXPOSE 80
# 运行
ENTRYPOINT [ "/root/main","-conf=/root/config.json" ]
  • vim build.sh
#! /bin/bash
echo Building main:build
# 开始构建构建镜像
docker build -t main:build -f Dockerfile.build .
# 创建构建容器,但是不运行,不能用run,run是包含了create和start两个命令
docker container create --name extract main:build
# 复制容器中的两个文件
docker container cp extract:/go/src/test/multi-stage-demo/main ./main
docker container cp extract:/go/src/test/multi-stage-demo/config.json ./config-run.json
# 删除创建的容器
docker container rm -f extract
# 删除构建镜像
docker rmi main:build
echo Building main:latest
# 开始构建运行时镜像
docker build --no-cache -t main:latest .
# 删除拷贝到本地的文件
rm ./main ./config-run.json
echo Building Success.

通过上面的三个步骤就可以进行自动化的构建和打包运行时,但是这样比较麻烦,需要写Dockerfile.buildDockerfile.run两个Dockerfile文件,还需要写一个构建脚本build.sh,最后通过运行脚本来打包镜像,这样比较麻烦,每个项目都需要维护多个文件。

多阶段构建出现以后

使用多阶段构建如上的步骤就非常简单

  • vim Dockerfile
# 第一步,构建
FROM golang:1.7.3 as build
WORKDIR /go/src/test/multi-stage-demo
COPY . .
#RUN cd /test/multi-stage-demo
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# 第二部,运行
FROM scratch

COPY --from=build /go/src/test/multi-stage-demo/main \
                 /go/src/test/multi-stage-demo/config.json \
                 /go/src/test/multi-stage-demo/docker-entrypoint.sh \
                /root/
EXPOSE 80

ENTRYPOINT [ "/root/main","-conf=/root/config.json" ]

上面主要是第二步比较陌生,COPY命令中的--from=build是指从构建阶段中复制文件。而不是当前的上下文中,所以复制的路径需要指定为第一个阶段构建结果存储的路径。--from=0也可以这样写,这样就不需要给第一步一个别名,直接这样写就行了,FROM golang:1.7.3。其他的都是和正常的Dockerfile没有区别的。

通过docker build -t main .就可以进行如上的打包。

golang 项目中多阶段构建依赖问题

Golang项目如果没有使用vender进行管理的话,使用多阶段构建就会出现问题,前提是有依赖其他第三方包,因为在Copy的时候只会把当前项目下的文件复制进构建容器中,而复制不到项目之外的文件,打包的时候就会因为找不到依赖包而打包不成功,比如一个项目使用了uber的日志包"go.uber.org/zap",但是打包的时候会因为找不到依赖包构建不通过。不建议在构建时再去拉取这些依赖包,这样可能会出现开发和生产上使用的依赖包代码不一致的问题。 针对上述问题目前我想到的两个办法时:

  • 1、修改构建的上下文,改为src,把Golang的本地的src都复制进容器中去构建,这样能通过,但是只能用于个人,公司项目一般都是有单独的服务器进行构建。这样可能会比较大。
  • 2、使用dep管理,就是Golang的包管理工具,把依赖包都集成到项目的vender中来,这样打包的时候就能找到依赖包。我认为这样时比较好的办法,而且对开发也方便,便于依赖包的统一管理。

多阶段构建适用的场景

一般只有需要依赖有单独的编译环境的项目使用多阶段构建才比较适合,比如前端的项目,需要使用node进行编译,这样就可以写一个编译容器出来进行单独的编译。

GolangDocker集成工具 drone.

参考


LRF 记录学习、生活的点滴