【问题标题】:Optimizing image storage in Docker repository using Jib for Spring Boot使用 Jib for Spring Boot 优化 Docker 存储库中的图像存储
【发布时间】:2020-06-29 01:33:35
【问题描述】:

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

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

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

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

为此,想法如下:

  • 我们创建一个仅包含 /opt/libs 中的依赖项的基础镜像,我们将其命名为 spring-base:1.0.0 并推送到我们的私有 Docker 注册表。

  • 我们将该图像用作仅包含我们的代码的应用程序图像的父级/基础。 Dockerfile 看起来与此类似(未经测试,只是为了呈现概念):

    FROM our-registry/spring-base:1.0.0
    COPY ./build/classes/kotlin/main/* /opt/classes
    COPY ./build/resources/main/* /opt/resources
    ENTRYPOINT ["java", "-cp", "/opt/libs/*:/opt/resources:/opt/classes", "com.example.MainKt"]
    

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

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

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

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

【问题讨论】:

    标签: spring-boot docker jib


    【解决方案1】:
    推荐的答案 Google Cloud

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

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

    这会在我们每次构建时生成一个大 (250 MB) 的新映像,即使实际上更改的代码很少。这是因为 fat jar 包含共享依赖项(很少更改)和我们的代码。

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

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

    我们创建一个仅包含 /opt/libs 中的依赖项的基础映像,我们将其命名为 spring-base:1.0.0 并推送到我们的私有 Docker 注册表。

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

    期望这些图像要小得多

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

    具有依赖关系的大基础镜像只存储一次

    是的,但是当您以FROM gcr.io/distroless/java:11 开头时,这也可能是正确的。只要您可以为共享库创建自己的单独层并保持层稳定(即可重现),将您的共享库推入另一个“基础映像”是没有意义的。 Jib 非常擅长可重复地构建这样的层。保存在注册表中的位粒度是层而不是图像,因此实际上没有必要“标记”库层位于某个“基本图像”中(只要您为库创建自己的层)。注册表只能看到层,“图像”的概念是通过声明“该图像由 A 层、B 层和 C 层以及该元数据组成”而形成的。图像甚至没有基本图像的概念。它并没有说“这个图像是通过将 A 层放在这个基础图像之上的”。只要 B 层是共享库层,就比拥有 fat jar 层有更好的优化。

    节省大量存储空间。

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

    我们集成它并使用./gradlew jibDockerBuild,它似乎确实为依赖项、资源和类创建了层,但仍然只有一个大图像。

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

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

    【讨论】:

    • 对,我缺少的是 Docker 通过层而不是图像来保存存储的知识,谢谢。我将尝试在官方文档中阅读更多相关信息,但如果您有更详细解释所有内容的链接,请分享。
    • 您所说的大部分是正确的,但需要进一步澄清。顺便说一句,“id”可能是一个令人困惑的术语。图层只是一个存档文件;在几乎所有的实现中,它只是一个你可以使用旧的tar 程序创建的压缩包。没有其他的。层的一个常见“id”是直接从 tarball 计算的 SHA256 哈希。所以,是的,“id”仅由 tarball 内容确定。如果 tarball 字节更改,“id”会更改。如果您可以重复创建相同的 tarball,一般来说 Docker 和注册表不必在其存储中复制相同的 tarball。
    • 一般来说已经足够了解了,但为了完整起见,我只想说还有另一种类型的“id”:从 compressed 计算的 SHA256 哈希压缩包。有时这可能很重要,因为如果您使用不同的 GZIP 压缩级别,即使 tarball(层)的内容相同,您最终也可能会拥有“不同”的层(即不同的 .tar.gz 文件)。
    • 现在,注册表实际上可以存储任何 blob(无论是 tarball、一些随机内容、一些随机文本还是任何东西)。当“声明图像”时,您只需说“这些 blob(blob A、blob B、blob C、blob D)构成我的图像”;您只需声明 blob 的“id”列表。 blob(或层)之间没有父子关系,也没有任何类型的关系。是的,你可以声明你的镜像是由 Distroless 的第 2 层、这个镜像的第 5 层、那个镜像的第 7 层等组成的。当然,这样的镜像是完全没有意义的。
    • 由于图像只是blob“id”的列表,从技术上讲,您甚至可以指向一个甚至不是tarball而是随机字节的blob。当然,在这种情况下,镜像完全损坏,任何 Docker 容器运行时都将无法运行它,因为它无法解压缩 blob。
    猜你喜欢
    • 2021-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多