【问题标题】:Swap out a layer of a container image换出一层容器镜像
【发布时间】:2020-09-29 15:45:02
【问题描述】:

我想知道是否可以将容器图像的一层换成另一层。这是我的场景:

我有一个执行以下操作的 docker 文件:

  1. 它会拉取 .net core 3.1 debian slim (buster) 映像
  2. 构建我的应用程序的源代码并将其作为层添加到 .net 核心映像之上

当 .net core 3.1 运行时映像的新版本出现时,我想制作一个具有该新版本但在其上具有相同应用层的新映像。

我不想找到用于构建应用程序并重新构建的代码的确切版本。

这个想法是复制升级机器和运行时,但不对应用程序进行任何更改(更少测试升级)。

我可以使用 docker 命令换出一层图像吗?

【问题讨论】:

  • 这是非常规的,需要解决一大堆模棱两可的技术问题才能正确做到这一点。我认为没有现有的docker 命令可以执行此操作,也没有为此目的设计的任何工具。此外,我不建议您这样做。 但是,我会回答你的问题 - 技术上,这是可能的。我正在开发一个概念验证工具,它能够为你交换基本图像(这是你所描述的)。我会尽快将其发布在答案中,但由于其复杂性,可能需要几天时间。
  • @concision - 我的公司目前在虚拟机上运行我们的定制软件。自动构建创建自定义软件,然后将其部署到该虚拟机。当虚拟机需要补丁或升级时,我们不重新编译代码,我们只是升级虚拟机(甚至可能制作一个新的虚拟机并将exe安装到它上面。)我正在寻找一种重新创建的方法这个场景使用容器。 (我可以升级操作系统,而无需重新构建我的代码。)这通常如何使用容器完成?
  • 容器应该是短暂的——它们应该在卷中保存它们的持久状态或不在容器状态(即文件系统)内的另一种持久存储方式。如果所有持久数据都在容器的内部状态之外,那么重建基础 Docker 映像并在持久数据上创建具有相同卷的新更新容器就是您“升级”操作系统的方式。虚拟机的类比在这里不太适用,Docker 工作在一个完全不同的架构上,它有一个联合层文件系统,其中顶层依赖于底层。
  • 此外,虽然我不能强调您应该从源代码重建,但通过直接编辑图像在技术上是可行的。但是,如果您尝试“升级”正在运行的容器的操作系统,则需要停止容器。由于这是一个有趣的非正统概念,我花一些时间尝试通过编写 CLI 工具来实现这一点。但是,为了实现这一点,您需要分别拥有原始基础镜像、新基础镜像和您希望“升级”的应用程序镜像。你知道/有那些你想“升级”的形象吗?
  • @Vaccano 这可能是一个愚蠢的问题,但是,您不能在运行 docker 的机器上保存二进制文件,并将volume 附加到容器上吗?在这种情况下,交换基础镜像将通过两个命令完成,docker container rm ... 和 docker container run(带有所需的参数)。

标签: docker containers


【解决方案1】:

这个答案分为两个不同的部分:

  • 第一部分:从源代码构建:解释为什么不应该这样做。
  • 第 II 部分:已实施的概念验证 CLI 工具:我专门针对此问题实施的 CLI 工具,带有演示。

如果不考虑潜在的不良副作用,可以跳过第一部分。


第一部分:从源代码构建

,可能没有docker 命令来解决这个问题——而且很可能永远不会,因为一大堆技术问题。即使按照设计,这也不是微不足道的。图像和图层在构建后是不可变的。强烈建议从原始源重新构建应用程序映像,但通过修改 FROM 命令使用新的基础映像。

许多一致性问题使这个想法不明智,下面列出了其中一些:

  • 某些Dockerfile 命令实际上不会创建层,它们会更新最后一层的内部清单和图像的清单。声明 Removing intermediate container XXXXXXXXXXXX 的命令实际上是在更新上述清单,并且创建新层。这需要在从旧基础镜像切换到新基础镜像时正确更新唯一相关的更改;例如协调来自ENV/LABEL/MAINTAINER/EXPOSE/CMD/ENTRYPOINT 命令的更改。

  • ENV 从前一个基本映像中继承的变量更改应用程序映像配置的命令可能无法正确更新。比如在应用镜像中,可能有如下命令ENV命令:

    ENV PATH="/path:${PATH}"
    

    如果应用程序映像的旧基础映像层被换出,并且新的基础映像包含不同的${PATH} 变量,那么在没有开发人员手动决定的情况下如何协调差异是不明确的。

  • 如果apt/apt-get/apk/yum用于安装Linux包,这些包作为后续层安装在应用镜像中。如果底层操作系统或可执行文件在新的基础镜像层发生变化,则不保证安装的包与新的基础镜像兼容。

第二部分:概念验证实施的 CLI 工具

通过对已构建的 Docker 应用程序映像的映像存档进行直接操作,技术上可以“升级”映像的基础。但是,我不能重复这一点 - 您不应该甚至尝试编辑现有图像的底层说真的——停下来,寻求帮助。我有 90% 的把握这是战争罪。

不过,为了彻底解决这个问题,我在我的 GitHub 项目 concision/docker-base-image-swapper 上开发了一个用 Java 编写的概念验证 CLI 工具,旨在交换 基础图像(但不是任意层)。我为解决各种一致性问题而做出的设计选择是对应该采取什么行动的“最佳猜测”。

包括一个演示,用于将已构建的 Java 应用程序映像从 JDK8 交换到 JDK11,在 demo/demo.sh 脚本中实现。所有核心代码都在隔离的 Docker 包含中运行,因此只有 Bash 和 Docker 是主机上运行此演示所必需的依赖项。演示应用程序映像仅在 JDK 8 上构建一次,但运行两次 - 一次在原始 JDK 8 映像上,另一次在交换的基础 JDK 11 映像上。

如果您在使用该工具时遇到一些技术难题,我可能会解决该问题。这个项目很快就被黑掉了,代码质量很差;此外,它可能会遭受各种无法解释的边缘情况。我可能会在接下来的一两个月内用 Rust 彻底重写它,重点是可维护性和处理所有边缘情况。

警告:我仍然不建议尝试编辑图像;使用该工具的风险自负。

概念

在这个过程中有三个相关的图像,它们都是已经构建的(即不需要原始来源):

  • 应用映像:当前基于旧基础映像的应用映像,但需要更换为较新的基础映像。
  • 旧的基础镜像:应用程序镜像使用原始Dockerfile 中的FROM 命令所基于的旧基础镜像。
  • 新的基础镜像:更新的基础镜像应该替换仅从FROM old-base-image 层继承的应用程序镜像层。

通过了解应用程序镜像的哪些层和配置是从旧的基础镜像继承的,它们可以被新基础镜像的层和配置替换。可以使用docker save 命令将所有图像层和清单作为 tar 存档获取。通过所有相关三张图像的存档,工具可以分析它们之间的差异


替代品警告

注意不要从旧应用程序映像中简单地做一个COPY --from=...,作为原始应用程序的映像配置(通过诸如CMDENTRYPOINTENVEXPOSELABEL、@987654352 等命令@、VOLUMEWORKDIR) 将无法正确复制。

【讨论】:

    【解决方案2】:

    有可能吗?是的。但很少这样做,因为它很容易出错。例如,如果之前的构建会创建一个库,但该库已经存在于原始基础映像中,则该库将不会包含在第一个构建中。如果基础映像删除了该库,则生成的合并映像将缺少该库,因为它不在新的基础层中,也不在旧的应用层中。

    我可以想到几种方法来做到这一点。

    选项 1:如果您知道一个图像中的特定文件,则使用 COPY --from 语法在图像之间复制这些文件。这是最不容易出错的方法,但要求您知道要包含的每个文件。生成的 Dockerfile 如下所示:

    FROM new_base
    
    # if every file is in /usr/local/bin:
    COPY --from=old_image /usr/local/bin/ /usr/local/bin/
    

    选项 2:您可以导出图像并通过组合两者之间的图层来创建自己的新图像。为此,有docker savedocker load。看起来像:

    docker save old_image >old_image.tar
    docker save new_base >new_base.tar
    mkdir old_image new_base new_image
    tar -xvf old_image.tar -C old_image
    tar -xvf new_base.tar -C new_base
    cp old_image/*.json new_image/
    # manually: copy each layer directory you want to save from old_image, you can view the nested tar files to find the proper ones
    # manually: copy each layer directory from new_base into new_image
    # manually: modify the new_image/manifest.json to have the new tag for your image, and adjust the layer references
    tar -cvf new_image.tar -C new_image .
    docker load <new_image.tar
    

    选项 3:这可以通过 API 调用直接在注册表中完成。您将拉出旧清单,使用新图层对其进行调整,然后推送任何新图层,然后推送新清单。这需要相当多的编码(请参阅regclient/regclient,了解我如何在 Go 中实现其中一些 API)。

    选项 4:我知道我已经看到了一个在特定场景中执行此操作的工具,但它的名称让我无法理解。我认为这需要您使用他们的基础镜像,这些基础镜像经过精心策划以降低版本之间不兼容的风险,并限制了可以交换的基础镜像,所以我认为它不是通用工具。

    请注意,选项 2 和 3 都需要手动步骤,或者如果您想自动执行,则需要您编写一些代码。由于这很容易出错(如上所述),我认为您不会找到任何人维护和支持实现它的工具。绝大多数使用 CI 工具从 Dockerfile 重建。

    【讨论】:

      【解决方案3】:

      我使用 Python 开发了一个小型实用程序脚本,可让您将 tarball 附加到容器注册表中的现有容器映像(无需提取现有映像数据),可在 https://github.com/malthe/appendlayer 获得。

      以下说明了它的工作原理:

      [ base layer ]                    => image1
      [ base layer ] [ appended layer ] => image2
      

      该脚本支持任何实现OCI Distribution Spec 的注册表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-03
        • 2020-07-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-08
        • 1970-01-01
        相关资源
        最近更新 更多