使用 Jib 为 Spring 引导优化 Docker 存储库中的图像存储

Optimizing image storage in Docker repository using Jib for Spring Boot

使用 Jib 构建 Docker 图像是否有助于优化远程 Docker 存储库存储?

我们在 Docker 和 Gradle 中使用 Spring 启动。目前,我们正在创建标准的 fat Boot jar,其中包含所有依赖项,然后我们用它创建一个图像,如下所示:

FROM gcr.io/distroless/java:11
COPY ./build/libs/*.jar app.jar
CMD ["app.jar"]

这会导致我们每次构建时都会生成一个大的 (250 MB) 新映像,即使实际上更改的代码很少。这是因为 fat jar 包含共享依赖项(不经常更改)和我们的代码。这是我们私有存储库中存储 space 的低效使用,我们想改变它。

为此,思路如下:

期望这些镜像要小很多,而且有依赖关系的大基础镜像只存储一次,节省大量存储。

我们的一位同事调查了 Jib 并坚持认为它确实是这样做的,但在阅读了整个文档和常见问题解答并试用了一下之后,我不太确定。我们集成它并使用 ./gradlew jibDockerBuild,它似乎确实为依赖项、资源和 类 创建了层,但仍然只有一个大图像。 Jib 似乎专注于加快构建时间(通过利用 Docker 层缓存)和可重现的构建,但我认为当我们将该图像上传到我们的存储库时,相对于我们当前的解决方案没有任何改变 - 我们仍然会存储'static' 多次依赖,但现在我们将在每个新图像中有多个层,而不是只有一个层。

有更多 Docker 和 Jib 经验的人能否解释 Jib 是否为我们提供了我们正在寻找的存储 space 优化?

编辑: 在等待答案时,我尝试了所有这些并使用了 https://github.com/wagoodman/divedocker system dfdocker images 检查尺寸并查看图像和图层,Jib 似乎完全符合我们的需要。

Does using Jib to build Docker images help optimize remote Docker repository storage?

是的。事实上,由于强大的图像层再现性,它在很大程度上有助于这一点。当仅使用 Dockerfile 时,您通常会完全失去大多数层的再现性,因为文件时间戳被纳入检查层是否相同的因素。例如,即使你的 .class 的字节根本没有改变,如果再次生成文件,你将失去再现性。这对 jar 来说更糟;不仅它的时间戳可以改变,而且 jar 元数据(例如,META-INF/MANIFEST.MF)包含编译时信息,包括时间戳、构建工具信息、JVM 版本等。在不同机器上构建的 jar 将被认为是不同的Docker世界。

This results in a big (250 MB) new image each time we build, even if very little code is actually changed. This is due to the fact that the fat jar contains both the shared dependencies (which change infrequently) and our code.

部分正确,大小很大 (250MB),但不是因为 fat jar。构建镜像的大小将始终为 250MB,即使它不是 fat jar,即使您为共享库指定了不同的层。最终图像的大小 (250MB) 将始终包括基础图像的大小 (gcr.io/distroless/java:11) 和共享库的大小,无论图像是通过哪种工具构建的。

但是,Docker 引擎不会复制它们在存储中已知的图层。同样,远程注册表也不会复制存储库中已经存在的层。此外,注册表甚至经常在不同的存储库中存储一层的一个副本。因此,当您仅更新您的代码(因此您的 jar)时,只有包含该 jar 的层将占用新存储 space。 Docker 和 Jib 将仅通过网络将新层发送到远程注册表。也就是说,将不会发送 gcr.io/distroless/java:11 的基础图像层。

We create a base image which contains only the dependencies in /opt/libs, let's call it spring-base:1.0.0 and push to our private Docker registry.

创建一个单独的图像只包含共享库并不是闻所未闻的事情,我看到有人尝试这样做。但是,我不认为您打算在概念上将这个特殊的基础图像视为一个独立的独立图像,旨在在您的组织中的不同类型的图像之间共享。所以我认为在这种情况下这样做是非常规的,如果这个技巧只是关于节省存储 space(和网络带宽)的想法,那么这个技巧很可能是不必要的。请继续阅读。

The expectation is that these images are much smaller

没有。正如我所解释的,无论如何您都将创建一个大小相同的 250MB 图像。它包括基本图像的大小,其中包括您的共享库。当 运行 docker images 时,您的本地 Docker 引擎将显示图像大小为 250MB。但正如我所说,这并不意味着您的 Docker 引擎会在您构建新图像时占用额外的 250MB space。

the big base image with dependencies is stored only once

是的,但是当您从 FROM gcr.io/distroless/java:11 开始时也是如此。将您的共享库推到另一个 "base image" 是没有意义的,只要您可以为共享库创建一个自己的单独层并保持该层稳定(即可重现)。而 Jib 非常擅长可重复地构建这样的层。保存在注册表中的位的粒度是层而不是图像,所以真的没有必要 "mark" 库层在某些 "base image" 中(只要你为库创建自己的层) .注册表只看到层,"image" 的概念是通过声明 "this image is comprised of layer A, layer B, and layer C along with this metadata." 图像甚至没有基本图像的概念来形成的;它没有说 "this image is by putting layer A on top of this base image." 只要 B 层是共享库层,你就比拥有一个胖 jar 层有更好的优化。

saving a lot of storage.

因此,这不是真的。毕竟,Docker 引擎和注册表不会无缘无故地多次存储同一层。

We integrated it and use ./gradlew jibDockerBuild and it does seem to create layers for the dependencies, resources, and classes, but there is still just one big image.

是的。图像大小为 250MB。当您使用 Dockerfile 或任何其他图像构建工具时,这仍然是正确的。但是,在使用 Jib 时,如果您仅更改应用程序 .java 文件,则 Jib 在重建时将仅通过网络将小型应用程序层(不包含共享库或资源)发送到远程注册表;它不会发送整个 250MB 的层,因为 Jib 保持了很强的可重复性。同样,如果您只更新共享库,Jib 将只发送库层,从而节省时间、带宽和存储空间。

但是请注意,由于 Docker 引擎 API 的能力有限,Jib 无法检查某些层是否已存储在 Docker 引擎中,使用 jibDockerBuild 时,Jib 必须加载整个 250MB 的图层。这通常不是问题,因为加载是在本地完成的,无需通过网络。但由于这个 API 限制,令人惊讶的是,Jib 将图像直接推送到远程注册表通常比本地 Docker 引擎更快; Jib 只需要发送已经改变的层。然而,正如我多次强调的那样,即使 Jib(或任何其他图像构建工具)将整个 250MB 的图层加载到 Docker 引擎中,引擎也只会保存必要的内容(即它创建的新图层)从未见过——或者它相信如此)。它不会复制基础图像或共享库层;只有新的不同层才会占用存储空间。使用 Dockerfile,你通常最终会生成 "new layers",即使它们实际上不是新的,因为可重复性差。