我们如何通过引入具有多阶段构建过程的Dockerfiles
来减小Golang
镜像的大小?
让我们从一个通用的Dockerfile
开始,它负责处理基本的事务,如依赖项、构建二进制文件、声明暴露的端口等,以便为Go
中的一个非常基础的REST API
提供服务。
假如在reduce-docker-size
项目下有一个dockerfile
文件。
reduce-docker-size/dockerfile
bash
FROM golang:1.16-alpine
ENV GO111MODULE=on
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
ENV HTTP_PORT=8080
EXPOSE 8080
ENTRYPOINT ["/app/reduce-docker-size"]
那将无缝地构建您项目的二进制文件,并创建Docker
镜像。
这样做真的足够好吗? 我会说不,因为生成的镜像大小超过300MB
(确切地说是322MB
),因为它包含了所有的Golang
工具,这对我们来说是不必要的,因为我们指示编译器禁用cgo(CGO_ENABLED=0)
并静态链接任何将为我们提供自包含可执行文件的C
绑定(其大小仅为6.05MB
!),无需任何外部框架或运行时依赖。
CGO_ENABLED=0 是至关重要的,如果我们不构建自包含的可执行文件,多阶段构建过程将无法工作。
我们可以做得更好的是,采用所谓的多阶段构建。多阶段构建允许多个不同的构建过程,这些构建可以完全从不同的基础镜像构建,选择性地将文件从一个阶段传递到下一个阶段,从而剥离最终镜像中所有不必要的文件。例如,我们可以将前一个阶段称为BUILD
,然后引入第二个阶段,我们称之为BINARIES
,该阶段使用alpine:latest
作为基础镜像,并从BUILD
阶段复制我们构建的应用程序的二进制文件。
bash
# BUILD
FROM golang:1.16-alpine as BUILD
ENV GO111MODULE=on
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
ENV HTTP_PORT=8080
EXPOSE 8080
# BINARIES
FROM alpine:latest
COPY --from=BUILD /app/reduce-docker-size /app/reduce-docker-size
ENTRYPOINT ["/app/reduce-docker-size"]
由于配备了golang
工具包的已被清理。现在镜像大小已降至11.7MB
。
这个好到足够了吗? 我会说是的,但是为了实验的缘故,我们还是尽量挑战一下极限。我们继续沿着多阶段构建的道路前进,但这次在我们的第二阶段,我们将不再使用alpine:latest
,而是转向一个非常特殊的名为scratch
的镜像,这是一个完全空白的镜像,实际上什么都没有。
bash
# BUILD
FROM golang:1.16-alpine as BUILD
ENV GO111MODULE=on
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
ENV HTTP_PORT=8080
EXPOSE 8080
# MINIATURE
FROM scratch
COPY --from=BUILD /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=BUILD /app/reduce-docker-size /app/reduce-docker-size
ENTRYPOINT ["/app/reduce-docker-size"]
新创建的镜像现在已经降至6.34MB!
因为我们预先告知的scratch镜像实际上是空的,所以找不到任何根SSL证书。以下指令将在最终镜像中复制证书,绝对不应被省略:
go
COPY --- from=BUILD /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
请问使用scratch
作为最终阶段的基础镜像值得吗?我会说既值得又不值得。如果你排除那些在alpine:latest
和scratch
构建的最终镜像之间的5.36MB
差异可能会产生巨大的影响,在其余的情况下,你最终会在生产中得到一个完全没有任何工具的容器,我完全不推荐这样做。这些特殊情况很少见,所以在为了仅仅5.36MB
(实际上是alpine:latest
的大小)而给自己找麻烦之前,要三思。