为什么需要多阶段构建容器镜像?

知识共享许可协议 4.0

通过多阶段构建容器镜像,不但可以控制容器大小,还可以解决发布应用的依赖问题('no such file or directory')。

在实际开发发布过程中,经常会碰到容器执行失败,并报:no such file or directory此类错误。通常情况下,错误的原因都是由于容器程序与所运行的基础镜像之间的环境不同造成的。虽然在编译Go程序的时候可以选择交叉编译的方式指定目标编译平台,但是对于不能通过非静态编译(即依赖外部的静态库或者动态库)的程序,其外部依赖库本身依赖其编译系统时,Go提供的交叉编译就无能为力了。所以通过多阶段构建,将程序的编译与发布通过相同的底层镜像进行构建可以最大程度的规避此类错误。

一个简单的多阶段构建容器镜像的例子

FROM golang:1.12-alpine AS builder
# 设置Go编译参数
ARG LDFLAGS
ENV LDFLAGS ${LDFLAGS}
# 设置Go编译环境变量
ENV GO111MODULE=on
ENV GOPROXY=https://[Your.Proxy]
WORKDIR /app
COPY . .
RUN GOOS=linux go build -o main -ldflags "${LDFLAGS}"

FROM  alpine
# 安装必要的工具包
RUN  apk --update --no-cache add tzdata ca-certificates \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=builder /app/main /usr/local/bin
ENTRYPOINT [ "main" ]

排查No such file or directory

虽然No such file or directory的报错即直白又明确,但是不同的程序导致的原因则是千差万别。这里简单说下如何排查这个问题,主要分两个步骤:静态排查动态排查

静态镜像检查

既然报文件或目录不存在,首先当然是检查是否真的文件或目录不存在。通过静态的方式,即容器不在运行态。静态检查就是通过检查镜像的方式,检查文件或目录是否存在。如何深入到容器镜像内部文件系统,可以通过手动的方式进行检查,也可以使用镜像工具检查。之前推荐一个开源项目dive,通过一个简单的命令就够了。

$: dive [your.image]

这个工具非常有用,能够看出容器镜像每一层发生的变更,也就能够定位文件是否真的不存在。

动态容器检查

其实很多时候,静态检查都会发现文件是存在,之所以导致no such file or directory的错误,是因为执行程序依赖的外部库不存在导致的。既然是外部库不存在,则需要进入实际的容器进行检查。

通常在Dockerfile中构建容器时使用 ENTRYPOINT 关键字,设置容器的初始进程。如果想进入问题容器显然ENTRYPOINT设置成问题程序,就会导致无法通过docker exec进入该容器。所以需要重新构建容器镜像,就是不要设置基础镜像的ENTRYPOINT,而是将问题程序通过CMD进行设置。这样我们就可以通过sh进入容器。

进入容器后,检查问题程序的执行问题,只需要ldd命令即可,查询问题程序的外部依赖。常规情况下,到这里基本就会定位具体的问题所在了。