软件越来越多地以容器镜像的形式分发。容器镜像包括支持容器中特色软件所需的许多软件组件。 因此,容器镜像的分发涉及许多软件组件的分发,这些组件通常包括 GPL 许可的组件。 我们不能期望每个分发容器镜像的公司都成为开源合规专家,因此我们需要将合规性构建到容器技术中。
我们应该将源代码可用性设计到容器工具和流程中,以促进高效且可移植的开源许可证合规性
- 高效 - 在创建容器镜像时一次性解决合规性问题。 避免依赖于以后的操作,尤其是在现有容器分发工具之外的操作。
- 可移植 - 当映像移动到另一个注册表时,应能够直接地将合规性随之移动。
这可以通过一种注册表原生的源代码可用性方法来完成。 这不需要更新所有容器注册表以包括源代码特定功能。 可以利用注册表已经具有的功能。 对于通过容器注册表分发的软件,我们可以使用这些相同的注册表来促进合规性。
容器技术
容器技术有助于管理部署和运行复杂软件系统所面临的挑战。
“容器”一词指的是运行时体验 - 一个程序及其依赖项可以在一个环境中执行,该环境提供与其他执行程序的一定隔离; 这就是在容器中运行。
用于配置此类容器的文件集称为“容器镜像”。 虽然特定容器镜像的目的是运行一个应用程序,但容器镜像包括更多内容:该应用程序及其依赖项 - 运行该应用程序所需的文件,除了操作系统内核。 容器镜像是一种可以存储和传输的文件形式,从而可以可靠地一次又一次地部署应用程序,并且可以独立于系统中可能在其他容器中运行和更改的许多其他程序而运行。
以容器镜像的形式分发软件正在增长,容器镜像从称为“容器注册表”的服务分发。 一旦投入于围绕容器组织计算,以容器镜像打包和分发软件就变得有价值。 那些使用容器来运行其软件的人发现,能够以预构建的容器镜像的形式获取软件非常有用,而不是自己完成所有的容器组装。 即使容器镜像是定制的,从预构建的镜像开始通常也很有价值。
容器镜像是不同的
过去,通常是单独获取每个软件组件。 相比之下,容器镜像包括一个异构的软件集合 - 通常是数百个软件组件。 软件交付的单位正在从由软件的**构建**方式驱动转变为由软件的**使用**方式驱动。(参见 容器镜像中有哪些内容:应对法律挑战。)
软件包维护者和软件包管理工具在过去二十年中在源代码可用性方面发挥了被低估的作用。 软件包的集中性质、软件包维护者的角色以及已构建用于支持软件包管理系统的工具导致人们期望某人(软件包维护者)将负责确保源代码可用。 构建二进制文件的工具还会将相应的源代码收集到一个可以与二进制文件一起交付的存档中。 结果是,大多数人不需要考虑源代码的可用性。 源代码与可执行软件的交付在同一个单元中可用,并通过相同的分发机制提供; 对于以 RPM 形式交付的软件,相应的源代码在源 RPM 中可用。
相比之下,没有约定提供与容器镜像对应的源代码。
容器镜像中的许多软件组件通常包括 GPL 许可的软件。 当公司开始以容器镜像的形式提供其软件时,可能没有太多 FOSS 软件分发经验的公司可能会开始分发 GPL 许可的软件。 让我们让每个人(包括可能不熟悉 FOSS 的公司)都能以一致的方式提供源代码变得简单。
识别相应的源代码
对于容器镜像,识别相应的源代码比软件包更具挑战性。 对于软件包,源代码可供构建软件包的人员使用(尽管如果软件包构建为包含依赖项而不仅仅是引用它们,则依赖项的源代码可能会有一些挑战)。 相比之下,容器镜像通常使用先前编译的组件构建。 构建容器镜像时,这些软件组件的源代码可能很容易获得,也可能不容易获得,具体取决于二进制文件的获取方式以及它们的构建方式。
当我们构建容器镜像时,我们应该收集用于构建该镜像的每个组件的相应源代码。 如果不是在构建镜像时,那又是什么时候呢? 然后,收集的源代码应成为相应容器镜像的逻辑部分。
当然,将相应的源代码作为容器镜像的逻辑部分提供,有助于该镜像分发中的合规性。 而且,这种做法也支持一个符合规范的生态系统。 如果有人基于基础镜像构建,他们如何知道可能需要提供哪些源代码? 如果容器镜像具有相应的源代码镜像,那么解决方案就很简单。 没有必要从弄清楚基础镜像中包含什么开始。 相反,从使用基础镜像的源代码镜像开始,开始提供整体镜像的源代码。
这里有一个机会将合规性构建到工具中 - 当有人基于基础镜像构建时,这些工具应该使他们能够轻松地为基础镜像提供相应的源代码,以及为构建在基础镜像之上的内容提供相应的源代码。 工具应该通过从基础镜像的源代码镜像开始,并添加适用于构建在该基础镜像之上的软件的任何源代码,来创建与最终组合镜像对应的源代码镜像。
交付
假设您有一个与容器镜像对应的源代码构件列表。 该列表将在哪里托管? 源代码构件将在哪里托管? 如果容器镜像托管在各种注册表中,那么如何为每个分发点提供源代码? 是否需要在每个注册表中或与注册表关联的每个目录中完成一些工作? 从每个不同的注册表中提取容器镜像的人如何知道这些材料在哪里? 这如何大规模运作? 需要多少种机制?
我们应该构建一个容器生态系统,使其合规性可以在各个注册表中移植。 不需要从每个注册表中获取新的指南。
如上所述,容器镜像中的支持软件组件通常包括在 GPL 许可下许可的软件。 考虑通过下载方式商业分发软件以满足源代码可用性要求的各种替代方案:二进制文件附带源代码; 二进制文件附带书面报价; 或者,同等的复制访问权限。 至于同等访问选项,让我们看看 GPLv2,第 3 节,最后一段:“如果通过提供从指定地点复制的访问权限来分发可执行或目标代码,那么**提供从同一地点复制源代码的同等访问权限**就算作源代码的分发,即使第三方没有被迫随目标代码一起复制源代码。”
为什么通过容器注册表交付源代码? 同等访问权限、效率和可移植性。
- 同等访问权限 - 源代码可用性的注册表原生方法(使容器镜像的源代码可以作为容器镜像提供)是提供源代码同等访问权限的好方法。
- 高效合规性 - 在构建可执行镜像时创建一个源代码可用性“软件包”(一个源代码镜像),然后使用相同的工具来提供源代码,避免了维护其他流程和机制来提供源代码的低效率。
- 合规性的可移植性 - 移动可执行镜像的相同工具可用于在所有托管注册表上移动、存储和服务源代码镜像。
源代码可用性的注册表原生方法
通过容器注册表交付源代码利用了容器镜像格式的设计以及容器注册表的某些相关特征。
容器镜像包括(请参阅下面链接的两个示例中的容器镜像详细信息)
- 一个镜像清单,用于标识镜像的其他元素;
- 配置数据,其中包含关于镜像的元数据,包括用于配置镜像以便在容器中执行的信息;
- 多个“层”(每个层都是一个 tar 归档文件),用于存储为容器配置的文件系统。
考虑使相应的源代码可用的挑战:我们希望服务器提供的一组源代码工件(RPM、zip 文件、tarball 等)。容器镜像是由注册表提供的组件列表。如果源代码工件列表被安排为镜像清单,那么容器注册表可以像提供其他容器镜像部分一样提供源代码工件。并且,用于移动容器镜像的工具可以直接应用于移动该合规性包(清单加上引用的源代码工件)。
源代码镜像(也称为源代码容器)就是一个简单的容器镜像。镜像中的层不是用于为执行容器配置的文件的 tarball,而是每个层都是一个源代码工件。
从容器注册表提供非文件系统内容的概念对于容器注册表来说,也适用于其他目的,而不仅仅是源代码。容器注册表提供的非层内容的一个例子是 Helm charts(用于描述一组 Kubernetes 资源)。通过容器注册表传递非层内容是 Open Container Initiative 的 artifacts 项目的主题。
去重
容器注册表将镜像作为多个部分存储和传递,而不是作为单个数字对象。这很有价值,因为容器镜像非常冗余。许多镜像的内容只有一小部分不同。容器镜像可以通过将运行特定程序所需的所有组件打包在一起,从而简化数据中心操作;因此,容器镜像很大,并且每个镜像都包含许多与其他镜像相同的内容。一个镜像中可能有 100-300 个软件组件,其中大多数存在于许多其他容器镜像中。此外,镜像也是不可变的。当需要更新镜像时,将创建一个与先前版本几乎相同的新镜像。通过将容器文件系统的归档文件分解为多个层并使用内容寻址存储,可以缓解这种大小挑战。
这种去重能力在源代码中也很有优势。利用此注册表功能的关键是不将镜像的所有相应源代码存储在单个 blob 中。如果源代码以每个镜像构建的数百个或更多软件组件的单独源代码工件的粒度存储,则可以轻松实现超过 100:1 的去重率。
实现
Red Hat 已经开始了实现注册表原生源代码交付的两阶段方法的第一阶段。
在第一阶段,Red Hat 已经开始生成可以托管在现有注册表上且不会混淆期望所有镜像都是可执行的工具的源代码镜像。在这种方法中,源代码工件伪装成常规层 - 每个源代码工件都包装在一个 tar 归档文件中;这些层的媒体类型与常规可执行镜像的媒体类型相同。源代码镜像通过命名约定与相应的可执行镜像相关联。
第二阶段涉及利用某些 OCI 镜像格式功能。
容器镜像应通过镜像索引而不是使用标签命名约定链接到相应的可执行镜像。容器镜像格式使源代码镜像可以实际成为可执行镜像所属的整个镜像的一部分。与源代码无关,能够使为不同处理器架构构建的镜像成为单个整体镜像的一部分是很有用的。可以作为单个对象管理整个镜像,并且镜像的使用者可以选择要下载的镜像的版本和/或部分(例如,amd64 或 arm64 或 source)。因此,与其通过标签约定将源代码与相应的可执行镜像相关联,不如应在镜像索引中列出源代码镜像清单。
此外,将来,源代码工件不应伪装成可执行层 tar 文件;应该消除在 tar 归档文件中对源代码工件的额外包装,并且应该使用适当的媒体类型来标识源代码工件。成功的去重需要仔细、一致的 tar 归档文件。直接存储源代码工件(并用适当的媒体类型标记它)可以减少因不一致的 tarball 包装器而造成的潜在去重损失。
最后,注册表中的源代码工件应与注册表中其他非层内容(例如 Helm charts)的方法一致。提供一种一致的方式来提供此类内容是 Open Container Initiative 中“artifacts”项目的主题。注册表原生源代码分发可以是该项目的受益者。
总结
当前方法(参见 当前方法示例)
- 源代码工件伪装成常规层 - 将每个源代码工件包装在一个 tarball 中,并用常规层媒体类型标记它。
- 在 tar 包装器内识别源代码工件的名称。
- 使用标签命名约定关联源代码和可执行镜像 - 使用相应的可执行镜像的标签扩展“-source”来标记源代码镜像清单。
未来方法(参见 未来方法示例)
- 不伪装 - 将源代码工件直接存储在注册表中(像其他注册表内容一样,通过哈希摘要命名),并为它们提供非层媒体类型。
- 使用清单中的层注释标识源代码工件的名称。
- 使用镜像索引关联源代码和可执行镜像 - 在镜像索引中列出源代码清单,以及每个机器架构的清单。
- 在源代码清单中使用独特的 config.媒体类型(如 OCI artifacts 项目中所建议的那样)。
我们现在在哪里?
UBI 镜像的源代码“容器”(实际上是镜像)现在位于 Red Hat 注册表中。这些源代码镜像是使用生产工具构建的。下一步是将其推广到其他镜像。
我们要去哪里?
我们需要一种行业范围内的、一致的方法来使容器镜像的源代码可用。让我们在 OCI 的 artifacts 项目中一起工作,并就“不伪装”方法的细节达成一致。
机遇
包括源代码会带来机会。将完整的相应源代码作为相应的源代码镜像提供,可以解决不仅仅是 GPL 源代码可用性的问题
- 开源软件的确切许可证可能很复杂(参见 开源软件许可是否已损坏?)。但是,有了完整的源代码,人们就可以确定对他们来说重要的任何细节(参见 源代码就是许可证)。
- 许可证通知怎么样?你可以尝试提取所有这些通知。或者,你可以通过源代码直接提供通知(参见 经济高效的开源软件许可证合规性模型)。
14 条评论