106. Dockerfile通过多阶段构建减小Golang镜像的大小

我们如何通过引入具有多阶段构建过程的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:latestscratch构建的最终镜像之间的5.36MB差异可能会产生巨大的影响,在其余的情况下,你最终会在生产中得到一个完全没有任何工具的容器,我完全不推荐这样做。这些特殊情况很少见,所以在为了仅仅5.36MB(实际上是alpine:latest的大小)而给自己找麻烦之前,要三思。

相关推荐
normaling4 分钟前
五,Stream流和方法引用
后端
Postkarte不想说话12 分钟前
从零开始写一个FreeSWITCH模块
后端
Victor35612 分钟前
Dubbo(38)如何优化Dubbo的线程池配置?
后端
都叫我大帅哥17 分钟前
代码世界的「万能转接头」:适配器模式的跨界艺术
java·后端·设计模式
码觉客17 分钟前
高并发系统的限流方案研究,其实限流实现也不复杂
后端
Cache技术分享18 分钟前
40. Java 使用 `switch` 语句进行分支选择
前端·后端
创新技术阁18 分钟前
FastAPI核心技巧大公开:深入探索路由与视图的奥秘
后端·python
hankeyyh42 分钟前
讲清楚Go字符串和utf8编码
后端·go
五行星辰1 小时前
Gson修仙指南:谷歌大法的佛系JSON渡劫手册
java·后端