共计 4217 个字符,预计需要花费 11 分钟才能阅读完成。
TL;DR
- 咱们须要对 container 供应链进行更好的元数据管理,以便更好地进行剖析;
- OCI 标准目前没有方法打包容器镜像工件或一组容器镜像。但他们会缓缓做到这一点;
- 同时,咱们须要一个用于容器镜像的包管理器;
一些背景
我保护着一个叫做 Tern 的开源我的项目,这个我的项目是为容器镜像生成一个软件资料清单(SBOM)。很多装置在容器镜像中的组件都是独立装置的,而非通过包管理器。这使得咱们很难弄清楚创立这个容器镜像的作者的用意。它也没有提供更多对于容器镜像贡献者的信息。大多数容器镜像都是基于之前已有的容器镜像,通过客户端工具或者镜像仓库都很难看到这些信息。
我想如果有一个“容器镜像”的包管理器,应该能解决这个问题。因而,我早在 KubeCon 2018 的时候就提出了 “ 打包 ” 的想法,我问容器镜像的 manifest 是否能够保留这些信息,以便工具能够依据容器镜像的供应链来进行剖析。
事实证明,社区曾经在思考如何治理 helm chart、OPA 策略、文件系统和签名等事件了。这就是我参加 凋谢容器打算(OCI)组织 的起因(我还欠 @vbatts 一个介绍我的人情)。过后的了解是,容器镜像除了须要通过摘要来进行辨认外,不须要进行其余治理。也不须要治理依赖,因为所有的依赖都被打包进了容器镜像中。通过再次重建容器和放弃一个上游消费者能够 pull 的滚动标签来解决更新。
然而,容器生态除了可移植性外,并没有提供太多货色。和容器镜像一起治理容器元数据能够为使用者和贡献者提供更多对于供应链的贵重信息。通过了两年工夫和几次供应链攻打之后,咱们依然在探讨,如何最好的做到这一点。在这里,我试图将一些提议的概念归纳起来,看看它们如何满足咱们对元数据管理的要求。
回到终点
咱们写一个包管理器次要有以下三个起因:
- 标识 – 为你的新文件或者包提供一个名字和其余惟一可辨认的特色;
- 上下文 – 理解你的包和其余包的关系(即,依赖性治理);
- 新鲜度 – 确保你的包在其生态系统中可保护并放弃更新;
实际上还有第 4 个起因,“构建的可重复性”– 我认为它属于“上下文”,但在这里,它的确值得一提,因为理解你的包在特定工夫的状态是很必要的。这样你能力剖析出过来的“好”状态,和当初“不那么好”的状态,即:应用二等分的办法剖析构建。
因为一些(善意的)假如,容器镜像生态并没有这些性能。一个容器能够通过它的摘要(digest)被标识。你不须要治理生态,因为整个生态曾经存在于一个单元中了。你不须要更新容器 – 只须要构建一个新的镜像,所有须要更新的内容都将被更新。只有你的应用程序没问题,那它便能够失常工作。然而,如果你打算长期保护你的应用程序,你必须筹备好解决堆栈更新和重大版本。咱们以后除了“下载最新版本”外没有其余好的方法来治理堆栈更新(一个值得注意的例外是 Cloud Native Buildpacks,但咱们此处将专一于通用案例)。堆栈的破坏性变更可能会阻塞你从新构建镜像,这迫使你须要保留一个旧版本的镜像,因为你曾经晓得这个镜像能够工作。你能够设想到,保护一组容器镜像将变得更加费劲。如果保护一组容器镜像所需的信息是内置的,并在须要时可用,那就真的太好了。
用于治理元数据的镜像仓库
咱们能够建设一个独自的元数据存储解决方案,但当初咱们曾经有镜像仓库了。通过一些改良,它们能够被用来和容器镜像一起存储补充元数据。组织曾经对蕴含源 tarball 的源镜像和签名 payload(正如 cosign 所做的那样)执行此操作。
应用镜像仓库的益处在于元数据能够和指标镜像一起存储。对 OCI 标准的倡议次要波及结构化和援用这些数据。这基本上是服务端的包治理,让这些倡议被合并是很艰难的,因为目前存在的客户端 - 服务端关系是很严密的,对服务端的任何扭转都会影响到客户端,对客户端打包机制的任何变更也都会影响到服务端。因而,目前的 OCI 标准除了扩大与以后 Docker 客户端和 Docker registry 工作形式放弃同步的标准外,还不能包含加强性能。
这篇文章并不是要批评以后的生态,而是想要把对话引到一个社区能够更可继续的保护它们的容器镜像的地位。就我集体而言,我也想证实在容器镜像畛域是须要一个包管理器的,只管镜像仓库能够反对相干 artifacts 和容器镜像的链接,也能够反对在容器镜像之间进行链接。
其余生态系统中的包管理器也有客户端和服务端的关系,所以在客户端和服务端之间摊派压力的架构并不陈腐。只不过在以后场景下,这种关系略有不同。
Identification(辨认)
容器镜像的工作形式有点像 Merkle Tree 有一个镜像配置的数据块,以及代表容器文件系统的一个或多个数据块。每个数据块都通过了哈希解决,并且放在了通过哈希解决过的 Image Manifest 中。
当初能够很好的专门辨认这个包,但它短少其余可辨认的特色,最显著的是名称和版本。在容器世界里,名字通常是一个镜像仓库的名称,而版本则通常是一个 tag。
就像 Git 一样,所有对文件集的援用都是哈希值,其余援用能够指向它们,例如 HEAD
,FETCH_HEAD
或 tag。tag 能够遵循语义版本控制,能够被移到另一个提交中。在开源的世界里没有人这样做,因为这会毁坏我的项目维护者和社区其余成员间的固有契约。容器镜像的 tag 并没有须要遵循语义版本控制的规定,但很多语言包管理器都依赖它,因而应用语义化版本控制容器镜像是有肯定的心愿的。
无论如何,哈希值对容器镜像而言是相当好的一个标识符。
那么其余可辨认的特色呢?以下是一些其余包管理器应用的特色:
- 许可证和版权信息
- 作者 / 供应商
- 公布日期
- 指向源代码的链接
- 第三方组件的列表(应用的操作系统,须要预装置的包等)
最初一项可能会变得相当长,因为创立一个容器镜像理论须要的第三方组件可能达到数百个。最近我始终主张将这些信息追随容器镜像放入一个 SBOM(软件资料清单)中,容器镜像签名是另一个能够和容器镜像一起流传的工件,例如镜像清单的分离式后面,或者一个签名荷载。
咱们当初有多个容器镜像的辨认工件,咱们心愿将它们与容器镜像分割起来。以后的 OCI 倡议应用 references (援用),一个援用是蕴含了 blob 哈希和其援用清单的哈希组成的清单。在咱们的例子中,援用是图像清单的哈希值。
这种布局还存在一些问题:
- registry 可能只辨认被标记的工件,因而会删除任何没有被标记的货色;
- 删除镜像会导致空援用;
一个疾速的解决方案可能是标记所有的清单。
这意味着须要有货色可能跟踪标签和它们之间的关系,同时跟踪所有工件的版本。
一个长期的解决方案可能是定义一个标准的工件清单,registry 将辨认并将其视为非凡的存在。如果是这样的话,那就须要计算或者跟踪与每个清单关联的援用数量了。
registry 实现者能够依照他们想要的任何形式来跟踪图中的链接。例如,在这个图中,对每个清单的援用数量都会被跟踪(减去哈希),但镜像清单被删除时,操作将会沿着树向下走到每个援用的末端,并依照肯定的程序去删除它们,直到援用数为 0。
但在这里,咱们为了追踪所有的相干对象,正在进入一些简单的追踪零碎。这是在 registry 端实现的包治理。能够提出一个论点,实现一个中立的垃圾收集器,它能够被 registry 或客户端应用,但咱们当初讲的有点超前了。
我心愿这足以阐明有必要对工件的汇合进行追踪,无论是在客户端还是服务端,或是两者都有。
Context
据理解,一个容器在运行时没有内部依赖性。然而在构建时,最终的容器镜像的确取决于初始容器镜像的状态,通常是 Dockerfile 中的 FROM
语句所定义的镜像。因为 Merkle Trees 的魔力,衍生的镜像与之前的镜像之间没有任何分割。因而,所有对旧镜像的援用都须要为新镜像创立一次,同时须要增加一些额定的工件。
与一般的援用机制相比,工件清单机制可能有一个劣势,因为在工件元数据被更新的同时,援用的数量被放弃在最低水平。
这两种机制都反对供应链平安,监管链和系谱查看等要求。这两种机制都须要 援用治理。在前者中,客户端将会拷贝原始镜像的 SBOM 和签名清单,更新它的援用,和减少新的清单。在后者中,客户端必须下载工件清单,对其进行补充,并与新的容器镜像一起推送。无论哪种形式,客户端都须要了解一些语法,无论是 tag 名称,工件构造或者工件类型,更不用说对工件来自的生态系统的一些了解。(SBOMs 和签名只是其中两种常见的工作类型,能够包含在内)。
我始终在思考的一个用例是如何将一系列的镜像链接到一起,来形容一个云原生利用。例如,jaeger 应用程序实际上是有本人依赖关系图的容器汇合,
如果能以一种能够上传到 registry 的格局来形容这些链接,这样整个镜像就能够和它的补充工件,一起在 registry 之间进行转移。
这是处于我想象力边缘的局部(我的图太简单了),但我心愿这些用例能阐明,如果当初不须要包管理器,那么很快就须要它来治理这些高层次的关系。
更新
这是我心愿语义版本控制可能失去更认真对待的一个中央。软件包管理器应用语义版本控制来容许一系列在整个堆栈中兼容的版本。这使得上游消费者能以最小的烦扰来适应更新。目前,因为容器镜像只能通过其摘要进行辨认,tag 是标识容器镜像处于什么版本的惟一方法,这就是包管理器真正有用的中央。
通常状况下,客户端查问服务器,看看它们应用程序所需的任何包是否有更新。而后客户端通知用户它们想下载的更新是可用的,如果用户有一个他们的应用程序兼容的版本范畴,客户端就会在这个范畴内下载新版本。
以后的 distribution SPEC 反对列出 tags。除此之外,registry 没有提供任何治理更新的形式,兴许这就是 registry 端所须要的所有。这也意味着,检查和更新的工作,次要落在了客户端。
下一步
社区统一认为,这些提议对于以后的 SPEC 我的项目而言,要承受的话是过于大了。然而(心愿)能有一场公开的对话,试图将这些变动合成为可吸纳的局部,从而容许向后兼容以后的状态,并推动标准向前倒退。
可能在未来,并不需要有一个包管理器,因为 registry,镜像和 artifacts 格局,讲负责提供推理供应链所需的所有信息。但那时一个边远的将来,在此期间,咱们须要一个货色来填补空白,也就是一个包管理器。
本文原作者 nishakm 原文地址:https://nishakm.github.io/cod… 经受权翻译
欢送订阅我的文章公众号【MoeLove】