【问题标题】:Should each Docker image contain a JDK?每个 Docker 镜像都应该包含一个 JDK 吗?
【发布时间】:2019-03-21 19:03:37
【问题描述】:

所以,我对 Docker 很陌生。让我解释一下这个问题的背景。

  1. 我有 10 - 20 个 Spring Boot 微服务应用程序,每个应用程序都运行在我本地机器的不同端口上。

  2. 但是要迁移到 Docker,根据我的学习,每个服务必须在不同的 Docker 容器中,以便快速部署或复制。

  3. 对于每个 Docker 容器,我们需要创建一个新的 Docker 镜像。

  4. 每个 Docker 映像都必须包含一个 JRE,才能运行 Spring Boot 应用程序。最大约为 200 MB。这意味着每个 docker 映像最大为 350 MB。 另一方面,在我的本地 PC 上,我只有一个 200 MB 的 JRE,每个应用程序只占用几 MB 的空间。

  5. 基于此,我的本地系统需要 600 MB,但所有 Docker 映像需要 7 GB。

这种方法正确吗? DockerHub 中的“OpenJDK”是否应该添加到每个镜像中?

为什么目标PC可能已经安装了JDK,但是镜像还是很大?

【问题讨论】:

  • 您似乎在谈论 JDK 和 JRE - 理想情况下,您会避免使用 JDK 构建映像,因为您只在构建时需要它,并且只在生产映像中使用 JRE。请注意,您在 Dockerfile 中有多个 FROMs,因此您可以使用 JDK 构建,然后仅使用 JRE 打包。
  • 确实如此。看看multistage builds。这允许您在一个映像中使用 JDK 进行构建,然后将构建的工件复制到更轻的运行时映像中。

标签: java docker spring-boot microservices


【解决方案1】:

基于此,我的本地系统需要 600 MB,但需要 7 GB 适用于所有 Docker 映像。

这种方法正确吗?是否应该将来自 DockerHub 的“OpenJDK”添加到 每张图片?

没错。虽然您可能想知道 JRE 是否还不够。

即使目标 PC 可能已经 有JDK吗?

您比较无法比较的东西本地环境(除了生产机器外)VS 集成/生产环境。

在集成/生产环境中,您的应用程序的负载可能很高,通常建议应用程序之间进行隔离。因此,在这里,您希望通过机器(裸机、VM 或容器)托管最少数量的应用程序(用户界面/服务),以防止应用程序之间的副作用:共享库不兼容、软件升级副作用、资源匮乏、应用程序之间的连锁故障。 ..

在本地环境中,您的应用程序的负载非常低,应用程序之间的隔离通常不是一个严重的问题。所以在这里你可以在本地机器上托管多个应用程序(ui/services),你还可以共享操作系统提供的一些公共库/依赖项。 虽然您可以这样做,但在本地混合和共享所有内容真的是一个好习惯吗? 我不认为是因为:
1) 本地机器不是垃圾箱:你整天都在工作。越干净,您的开发就越高效。例如:本地托管的应用程序之间的 JDK/JRE 可能不同,应用程序使用的某些文件夹可能具有相同的位置,数据库版本可能不同,应用程序可以安装不同的 java 服务器(Tomcat、Netty、Weblogic)和或具有不同的版本...
感谢容器,这不是问题:所有内容都根据您的要求安装和删除。

2) 环境(从本地到生产环境)应尽可能接近,以简化整个集成-部署链并及早发现问题,而不仅仅是在生产中。

附带说明,要在本地实现这一目标,您需要为开发人员提供真实机器。


一切都是有代价的,但实际上并不昂贵

除了隔离(硬件和软件资源)之外,容器还带来了其他优势,例如快速部署/取消部署、可扩展性和故障转移友好(例如:Kubernetes 依赖于容器)。
隔离、快速、可扩展性和健壮性友好是有代价的:在容器(操作系统、库、JVM……)之间不共享任何物理资源。

这意味着即使您在应用程序中使用确切的操作系统、库、JVM,每个应用程序都必须将它们包含在它们的映像中。
这个很贵吗 ? 并非如此:官方图像通常依赖于 Alpine(有限制的轻型 Linux 操作系统,但如果需要可定制),以及在成本方面代表 350 MB 的图像(您引用的值是现实中的值)是什么?
事实上,这真的很便宜。 在集成/生产中,您的所有服务很可能不会托管在同一台机器上,因此将容器的 350 MB 与用于集成/生产的传统 VM 中使用的资源进行比较,这些 VM 包含安装了多个附加程序的完整操作系统。您了解容器的资源消耗不是问题。这甚至被认为是超越本地环境的优势。

【讨论】:

    【解决方案2】:

    其他答案很好地涵盖了 Docker 分层,所以我只想为您的问题添加详细信息

    这种方法正确吗? DockerHub 中的“OpenJDK”是否应该添加到每个镜像中?

    是的。如果它不在图像中,它就不会在容器中。您可以通过重用尽可能多的图层来节省磁盘空间。所以试着把你的 Dockerfile 从“最不可能改变”写成“最有可能改变”。因此,当您构建映像时,您看到“使用缓存”的次数越多越好。

    为什么目标PC可能已经安装了JDK,但是镜像还是很大?

    Docker 希望尽可能少地与主机发生关系。 Docker 甚至不想与主机打交道。它做的第一件事就是创建一个虚拟机来隐藏。Docker 镜像假设主机唯一会提供的是空内存、磁盘和 CPU。所以每个 Docker 镜像还必须包含它自己的操作系统/内核。 (这就是您最初的 FROM 正在做的事情,选择要使用的基本操作系统映像)所以您的最终映像大小实际上是操作系统 + 工具 + 应用程序。图像大小有点误导,因为它是所有层的总和,可以跨图像重复使用。

    (隐含)每个应用程序/微服务都应该在自己的容器中吗?

    理想情况下,是的。通过将您的应用程序转换为独立模块,可以更轻松地替换/负载平衡该模块。

    在实践中,也许不是(对你来说)。 Spring Boot 不是一个轻量级的框架。实际上,它是一个将代码模块化的框架(在模块控制系统中有效地运行模块控制系统)。现在您想容纳 10-20 个?这可能无法在单个服务器上运行。 Docker 将强制 Spring boot 将自己加载到内存中每个应用程序;并且对象现在不能跨模块重用,因此也需要多实例化!如果您仅限于一台生产服务器,则不能选择水平扩展。 (每个 Spring Boot 需要约 1GB 的 HEAP(RAM),里程数取决于您的代码库)。对于 10-20 个应用程序,重构以使应用程序更适合 Docker 部署可能不可行/在预算内。更不用说,如果您不能在本地运行最小的设置进行测试(RAM 不足),开发工作将获得更多“乐趣”。

    Docker 不是一把金锤。试一试,自己评估利弊,然后确定利弊对您和您的团队是否值得。

    【讨论】:

    • 我喜欢你的回答,但同时也发人深省。您建议每个微服务作为 Spring Boot 应用程序运行的替代方案是什么。这允许非常松散的耦合,并且没有像旧的更大的弹簧应用程序那样的部署步骤。微服务可以相互交谈。那么在这种情况下,最后在运行 docker 镜像的机器上,它们不是都使用相同的 JRE 并消除每个容器对 1GB 堆的需要吗?
    • @SamwellTarly 容器将共享(大部分)基本映像,但它们的运行时内存(R+W 层和 RAM)是每个容器隔离的。所以每个容器的 JVM 都需要将它正在使用的资源加载到内存中(而 Spring Boot 使用了很多资源)。 Docker 实际上基于12 Factor App 设计理念,它假设您的微服务都设计为在单独的虚拟机/机器上运行。不过,一种折衷方案是首先在 1 个 Docker 容器上构建它,然后在重构时创建更多容器以实现更轻的部署。
    • @SamwellTarly 最终映像越小,最终 RAM 占用越轻,启动容器的速度就越快(如果您想利用 Docker 容器扩展,这将是一件大事/load-balancing。即使您只使用 1 个容器,它也解决了“在我的机器上工作”问题(大部分)。对于更有针对性的答案,您最好再问一个关于如何解决任何问题的问题正在尝试通过切换到 Docker 来解决。
    • 是的,我知道包含 RAM 使用的容器必须是最小的。然而,亚马逊的云教程本身使用每个微服务作为 Spring Boot 应用程序。基本 JVM 将要求 2GB 的 RAM 映射。然而,每个微服务在我的本地 PC 上使用的 RAM(10MB)非常少。如果它需要更多 RAM,集群管理器不会处理吗?您能否指出您的来源,其中指出 Spring Boot 很重并且在云平台中需要大量 RAM?
    • @SamwellTarly 如果 Ram 不是问题,那么显然这不是问题。如果您有有限的服务器资源限制,则集群管理器不能分配比集群中更多的资源。当然,Java+Containers 的第一个主要问题(如果您不是 11+)是 Java 将从集群中过度分配堆。我不能指出关于 Spring 沉重的硬数字,因为任何关于它的博客都做了表面测试,只是证明“Spring 在纸上很轻”,但我在实践中看到 Spring 可以增加巨大的启动和运行 -时间开销。 (最高 X5)
    【解决方案3】:

    Lagom's answer 很棒,但我想补充一点,Docker 容器的大小应该尽可能小,以便于传输和存储。

    因此,有很多基于Alpine Linux 分布的容器,它们真的很小。如果可能,请尝试使用它们。

    此外,不要将所有可以想象到的工具都添加到您的容器中,例如你通常可以不用 wget...

    【讨论】:

    • 当然,不仅仅是wget - 我已经看到了生产 Docker 镜像,其中包含各种愚蠢的东西,包括完整的 GCC 发行版(在 PHP 应用程序中)。
    • @SebastianLenartowicz 好笑!为什么?我见过的必须有用于测试的东西才能构建一个 python 包。大多数人倾向于不使用多层图像,这样可以防止这个特殊问题。
    • 明白。如此强大的设计需要最大程度的继承。
    • @ChristianSauer 因为 Docker 镜像是由对其目的不完全了解的人构建的。他们认为他们需要一个完整的 Unix-y 系统,这样他们就可以在它运行时对其进行修改和管理(我知道,我知道)。
    • @SamwellTarly 警告!这取决于!过多的继承会使你的整个项目变得笨拙。例如。如果您部署了多个微服务,那么拥有各种 jave 版本可能会有所帮助 - 例如因为一个包有一个错误,它阻止它在你喜欢的所有其他服务的版本上工作。冲帐!开发时间也是一个考虑因素 - 如果您需要安装 deps,让 alpine 映像工作可能会很痛苦。
    【解决方案4】:

    你的理解不正确。

    Docker 镜像是由层组成的;见下图:

    当你在你的镜像中安装一个 JRE 时,假设它的校验和在下一张图中是91e54dfb1179,它确实会占用你的磁盘。

    但是,如果你所有的容器都基于同一个镜像,并添加不同的东西,比如你的不同微服务应用到瘦 R/W 层,所有容器将共享91e54dfb1179,所以它不会是 n*m 关系。

    你需要注意尽可能为所有Java应用程序使用相同的基础镜像,并在瘦R/W层添加不同的东西。

    【讨论】:

    • 很好的答案,但我还有一个疑问。假设 docker 镜像构建在不同的系统中?假设每个微服务都是由不同地理位置的单独团队构建的?这种与 id 的现有 jre 共享将不成立,对吧?
    • @SamwellTarly 在适当的时候使用一个好的通用基础镜像 - 这个基础镜像应该包含重要的通用部分。
    • @SamwellTarly 您需要将基本映像与大多数常见事物对齐,至少将您最关心的 jre 与一个自定义基本映像对齐。并且,建议使用 dockerhub 或私有 docker registery 来分享。然后每个服务团队都可以基于这个基础镜像添加东西。
    • 你应该考虑使用OpenJDK作为你的基础镜像。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-07
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    • 2021-01-02
    • 1970-01-01
    相关资源
    最近更新 更多