前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何将镜像体积海量缩减

如何将镜像体积海量缩减

作者头像
程序猿Damon
发布2023-09-04 13:04:22
2270
发布2023-09-04 13:04:22
举报

镜像的传统构建

我们随便找个Golang代码项目作为案例,来开始构建一个镜像。下面我们以我的一个实战项目开始讲解:https://gitee.com/damon_one/uranus

第一步:我们把项目代码克隆到本地:

代码语言:javascript
复制
git clone https://gitee.com/damon_one/uranus

第二步,书写其编译的Dockerfile:

代码语言:javascript
复制
FROM golang:1.20
WORKDIR /opt/app
COPY . .
go build -o hz-zeus ./zeus
CMD ["/opt/app/hz-zeus"]

这个 Dockerfile 描述的构建过程非常简单,我们首选 Golang:1.20 版本的镜像作为编译环境,将源码拷贝到镜像中,然后运行 go build 编译源码生成二进制可执行文件,最后配置启动命令。

第三步,构建镜像:

代码语言:javascript
复制
docker build -t hz-zeus -f Dockerfile .

这样编译构建的镜像会很大,这里就不展示最后的镜像信息了。

Dockerfile 优化

从上面的 Dockerfile 可以看出,我们在容器内运行了 go build -o hz-zeus ./zeus,这条命令将会编译生成二进制的可执行文件,由于编译的过程中需要 Golang 编译工具的支持,所以我们必须要使用 Golang 镜像作为基础镜像,这是导致镜像体积过大的直接原因。

既然依赖基础镜像比较大,那么我们是否可以替换为轻量级的镜像呢?发现可以将 Golang:1.20 基础镜像替换为 golang:1.20-alpine 版本。

但,这样的构建之后,发现镜像还是很大。毕竟是在镜像内镜像编译二进制文件后构建镜像。那是否可以在外部进行构建后再同步到镜像内部呢?

代码语言:javascript
复制
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hz-zeus ./zeus
$ ls -lh
...

最简单的办法就是在本地先编译出可执行文件,再将它复制到一个更小体积的 ubuntu 镜像内。具体做法是,首先在本地使用交叉编译生成 Linux 平台的二进制可执行文件。接下来,使用 Dockerfile 文件构建镜像。

代码语言:javascript
复制
FROM ubuntu:latest
WORKDIR /opt/app
COPY hz-zeus ./

CMD ["/opt/app/hz-zeus"]

因为不再需要在容器里进行编译,所以我们直接引入了不包含 Golang 编译工具的 ubuntu 镜像作为基础运行环境,接下来使用 docker build 命令构建镜像。

这种构建方式生成的镜像在体积上比最初的缩小了几乎 90% 。镜像的最终大小就相当于 ubuntu:latest 的大小加上 Golang 二进制可执行文件的大小。不过,这种方式将应用的编译过程拆分到了宿主机上,这会让 Dockerfile 失去描述应用编译和打包的作用,不是一个好的实践。

多阶段构建

多阶段构建的本质其实就是将镜像构建过程拆分成编译过程和运行过程。第一个阶段对应编译的过程,负责生成可执行文件;第二个阶段对应运行过程,也就是拷贝第一阶段的二进制可执行文件,并为程序提供运行环境,最终镜像也就是第二阶段生成的镜像。

代码语言:javascript
复制
FROM golang:1.20 as builder
WORKDIR /opt/app
COPY . .
RUN go build -o hz-zeus ./zeus


FROM ubuntu:latest
WORKDIR /opt/app
COPY --from=builder /opt/app/hz-zeus ./hz-zeus

CMD ["/opt/app/hz-zeus"]

这段内容里有两个 FROM 语句,所以这是一个包含两个阶段的构建过程。

第二阶段,它的作用是将第一阶段生成的二进制可执行文件复制到当前阶段,把 ubuntu:latest 作为运行环境,并设置 CMD 启动命令。

最后,我们执行docker build后会发现镜像大小与上面的先编译后copy到镜像种的操作生成的镜像一样大小。

到这里,对镜像大小的优化已经基本上完成了,镜像大小也在可接受的范围内。在实际的项目中,我也推荐你使用 ubuntu:latest 作为第二阶段的程序运行镜像。

如何复用构建缓存

在第一阶段的构建过程中,我们先是用 COPY . . 的方式拷贝了源码,又进行了编译,这会产生一个缺点,那就是如果只是源码变了,但依赖并没有变,Docker 将无法复用依赖的镜像层缓存。在实际构建过程中,你会发现 Docker 每次都会重新下载 Golang 依赖。

这就引出了另外一个构建镜像的小技巧:尽量使用 Docker 构建缓存。

要使用 Golang 依赖的缓存,最简单的办法是:先复制依赖文件,再下载依赖,最后再复制源码进行编译。基于这种思路,我们可以将第一阶段的构建修改如下:

代码语言:javascript
复制
FROM golang:1.20 as builder
WORKDIR /opt/app
COPY go.* ./
RUN go mod download
COPY . .
RUN go build -o hz-zeus ./zeus

这样,在每次代码变更而依赖不变的情况下,Docker 都会复用之前产生的构建缓存,这可以加速镜像构建过程。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 交个朋友之猿天地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Dockerfile 优化
  • 多阶段构建
  • 如何复用构建缓存
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档