Docker 镜像瘦身技巧

发布者:我昐会员 网友2023-09-11评论

当我们实现一个高度可水平扩展的微服务架构时,需要考虑到一个关键的点: 当前端的访问压力突增的时候,我们的微服务群怎么快速的增加对应的容器的数量,通过达到足够数量的容器来应对的压力. 短时间扩展出大量后端服务资源,需要考虑架构和运维的方方面面,其中通过减少镜像的体积,在同样的网络带宽的情况下就能缩短镜像的拉取时间,进而缩短整个容器的部署就绪时间。

根据Best for 和Best - your with ECS这2篇文章,我们对镜像瘦身的几种技巧做一次梳理。

总而言之我们有下面的几种方式来共同作用减少容器的体积:

使用多阶段构建技术使用合适的小体积的基础镜像

这个是最容易做到并且很容易理解的一种常见策略,也就是将FROM xxx语句中使用的基础镜像在满足程序运行性能和稳定性的前提下越小越好。

举个例子,我有一个Java项目需要运行,那么我需要引入一个镜像做基础镜像,那么我们就需要考虑用那种镜像做为基础最合适.

下面我列举如下的几个选项,这些选项可以在 hub 上检索到.

Size

:11-jre-slim

76.39 MB

:11-jre

117.76 MB

:11-slim

225.52 MB

:11

318.71 MB

:17

231.66 MB

:17-slim

210.62 MB

:17-

181.71 MB

很明显,我们当我们运行的项目需要JDK11的情况下,那么:11-jre-slim是最佳选择,如果我们的项目是JDK17的情况下,那么:17-slim是最佳选择。

为什么我不建议使用:xxx-选项呢?

This image is based on the Linux , in the image. Linux is much than most base (~5MB), and thus leads to much in .

以 linux为基础的在大多数情况下,的确是有最小的体积,但是不是绝对的;java程序运行只需要jre并不一定需要全套的JDK环境,在有jre版本的镜像面前, linux为基础的镜像不一定最小,比如:11-jre-slim在上表中是最小的基础镜像

The port for is not in a by , since it is not in the code base. It is only as early of . See also this . So this image what is from the 's .

What this means is that based are only for early of . Once a a "-" , the is from the " Tags"; they are still to pull, but will no be .

不优先选的原因是如上的2个说明:

slim和jre版本的是什么含义?

首先,jre版本表示对java运行环境做裁剪,jre是java的基础可执行环境,它移除了JDK中不需要的开发套件,进而节约了体积,通常我们要有限选择jre版本的基础镜像,完全不会影响java程序的运行指标.

其次,slim版本表示对linux做裁剪,相比完整的linux它移除了容器运行中几乎不可能使用到的一些linux组件,比如curl等shell命令就由于裁剪可能不会预装进去

总结就是,最好是选择jre和slim后缀的基础镜像,它同时对linux和JDK做裁剪,能最小化镜像的体积而不影响java程序的运行性能

普通的镜像的价值在哪里?

不带jre,slim和后缀的镜像我成为普通镜像,它有完整的JDK和完整的Linux环境,在大部分情况下是我们最无脑的选择,如果你不知道怎么选,选它就准没错,当然镜像的体积就大很多,它有一个优势就是全面,比如有完整的linux包管理和shell工具,能让你做一些细节的操作,比如健康检查就可能用到curl,如果你使用slim版本的就可能由于缺失curl而导致健康检查被误认为不通过,当然最好是你在slim上安装对应的软件,这样既有slim的小体积优势也能满足你的细节需要。

减少镜像的layer的数量

镜像是由很多镜像层()组成的(最多127层),中的每条指定都会创建镜像层,但是只有RUN, COPY, ADD会使镜像的体积增加.

Only the RUN, COPY, ADD . Other , and do not the size of the build.

所以我们在使用上面讲的三个指令的使用,就需要特别注意尽量把它们合并为一条shell语句,而不是每个语句一行

例如下面的用法

...
RUN apt update -y
RUN apt install curl -y
...

我们就可以合并为

RUN apt update -y && apt install curl -y

这样在理论上减少了一个layer. 此外对于上面的2个指令,合并还能获取避免意料之外的问题

可能的问题

Using apt-get alone in a RUN and apt-get fail

sees the and as and the cache from steps. As a the apt-get is not the build uses the . the apt-get is not run, your build can get an of the curl and nginx .

所以合并指令是一个百利无一害的方式

镜像只包含程序运行所需的最小的资源

我们在使用构建镜像的时候,可能会产生一些中间文件或者由于考虑步骤意外的引入了程序运行无关的文件,这个也需要考虑

首先是排除不必要的资源

我们的程序只需要它需要的资源,一切与他无关的文件,比如某些图片,文档等,我们可以使用排除出去,原理和.一样

按照程序语言的运行特性做对应的资源裁剪

有时我们在构建的时候需要安装某种linux工具,比如为有些slim版本的linux安装curl执行内部的健康检查,这是我们执行apt/dnf包管理软件的时候,可能无意间产生了大量的临时缓存,这很容易被无意间打包到镜像中,所以我建议使用结合指令合并技术加上rm -rf /var/lib/apt/lists/*来立即在软件安装完后清理缓存,比如我安装curl时会这样写RUN apt -y && apt -y curl && rm -rf /var/lib/apt/lists/*

此外在经验不足的情况下,我们没有具体的语言具体分析,比如它是一种纯粹的编译型语言,在程序被编译出来后,它只需要linux基础环境即可运行

有些人可能会这样写FROM :1.17并以为它作为的运行环境是最合适的,其实未必,编译出的二进制可执行程序,只需linux环境即可,我们就可以先编译并FROM :-slim 即可,它能有更小的体积并完全不影响正常的运行性能.

还有就是,这类解释性语言,我们需要在运行前执行包管理程序下载依赖包,然后程序才能运行,但是实际上程序的运行只需要有程序本身和依赖包即可,包管理器和包管理器执行过程产生的缓存以及临时文件是我们不需要的,我们也可以使用分阶段构建技术(后文会讲)来在后续的阶段移除这些运行无关的文件,哪怕它们在构建早期是如此重要。

此外解释性语言在安装包时可能会附带一些调试和附加工具,它们在开发阶段很实用但是正式环境运行时不再必要,我们最好也是要注意一下只安装生产环境级别的依赖,排除一些辅助性的开发组件。

In the case of a image for we want to that we only in a way, and this us to the for the best for npm in a image: RUN npm ci --only=

参考: 10 best to Node.js web with

使用多阶段构建技术

是我们构建过程中相对来将复杂一段的技术,但是也异常的实用,比如前面讲的等解释性语言就可以充分利用本技术来瘦身。当然java,go等编译性语言也能受益于此技术

它的核心思想是:将镜像的构建分为多个阶段,下一个阶段依赖上一个阶段的输出,将上一个阶段的输出作为输入来排除掉上个阶段构建过程中无法排除的废弃文件

通常会分为2个大致的阶段 编译/依赖下载->实际的镜像构建

以官方的例子为例

的构建,第一阶段使用基础镜像做依赖的下载和编译工作,第二阶段只复制上一阶段产生的二进制文件并使用更加精简的 镜像作为实际的运行环境

# syntax=docker/dockerfile:1
FROM golang:1.16-alpine AS build
# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only
# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project
# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

构建,第一步下载依赖,第二步只将上一阶段的依赖复制过来,排除了上一阶段的npm和npm运行过程中的中间文件,同时使用更精简的slim镜像来提供实际的运行环境

FROM node:14 AS build
WORKDIR /srv
ADD package.json .
RUN npm install
FROM node:14-slim
COPY --from=build /srv .
ADD . .
EXPOSE 3000
CMD ["node", "index.js"]

通过这2个例子,相信大家可以清楚的理解多阶段构建对编译型/解释性语言都有很好的瘦身效果。

猜你喜欢

相关文章