werf中容器镜像的“智能”清理问题及其解决方案

werf中容器镜像的“智能”清理问题及其解决方案

本文讨论了在交付给 Kubernetes 的云原生应用程序的现代 CI/CD 管道的现实中清理容器注册表(Docker 注册表及其类似物)中积累的映像的问题。 给出了图像相关性的主要标准以及由此产生的自动化清洁、节省空间和满足团队需求的困难。 最后,我们将使用特定开源项目的示例来告诉您如何克服这些困难。

介绍

容器注册表中的镜像数量可能会快速增长,占用更多的存储空间,从而显着增加其成本。 为了控制、限制或维持注册表中所占用空间的可接受的增长,接受:

  1. 对图像使用固定数量的标签;
  2. 以某种方式清理图像。


第一个限制有时对于小团队来说是可以接受的。 如果开发者有足够的永久标签(latest, main, test, boris 等),注册表的大小不会膨胀,很长一段时间你根本不需要考虑清理它。 毕竟,所有不相关的图像都被删除,并且根本没有剩余的清理工作(一切都是由常规垃圾收集器完成的)。

然而,这种方法极大地限制了开发,并且很少适用于现代 CI/CD 项目。 开发的一个组成部分是 自动化,它允许您更快地测试、部署并向用户交付新功能。 例如,在我们所有的项目中,每次提交都会自动创建一个 CI 管道。 在其中,映像被组装、测试,并部署到各种 Kubernetes 电路以进行调试和剩余检查,如果一切顺利,更改将到达最终用户。 这不再是复杂的科学,而是许多人每天都会遇到的事情——很可能对你来说,因为你正在阅读这篇文章。

由于修复错误和开发新功能是并行进行的,并且一天可以进行多次发布,因此显然开发过程伴随着大量的提交,这意味着 注册表中存在大量图像。 因此,出现了组织有效清理注册表的问题,即删除不相关的图像。

但如何确定图像是否相关呢?

图像相关性的标准

在绝大多数情况下,主要标准是:

1. 第一个(最明显也是最关键的)是图像 目前在 Kubernetes 中使用。 删除这些映像可能会导致大量的生产停机成本(例如,复制可能需要这些映像)或抵消团队在任何循环上调试的努力。 (为此我们还专门做了一个 普罗米修斯出口商,它跟踪任何 Kubernetes 集群中是否存在此类镜像。)

2. 第二(不太明显,但也非常重要,并且再次与剥削有关)- 图像 如果发现严重问题则需要回滚 在当前版本中。 例如,对于 Helm,这些是在发布的保存版本中使用的图像。 (顺便说一句,默认情况下,Helm 的限制是 256 个修订,但不太可能有人真正需要保存 这样的 大量版本?..)毕竟,我们特别存储版本,以便以后可以使用它们,即如有必要,“回滚”给他们。

3. 第三—— 开发者需求:与他们当前工作相关的所有图像。 例如,如果我们正在考虑 PR,那么保留与最后一次提交(例如前一次提交)相对应的图像是有意义的:这样开发人员可以快速返回到任何任务并使用最新的更改。

4. 第四——图像 对应我们应用程序的版本, IE。 最终产品:v1.0.0、20.04.01/XNUMX/XNUMX、sierra 等。

注意:这里定义的标准是根据与来自不同公司的数十个开发团队的互动经验制定的。 然而,当然,根据开发过程的具体情况和所使用的基础设施(例如不使用 Kubernetes),这些标准可能会有所不同。

资格和现有解决方案

通常,具有容器注册表的流行服务提供自己的映像清理策略:您可以在其中定义从注册表中删除标签的条件。 然而,这些条件受到名称、创建时间和标签数量*等参数的限制。

* 取决于特定的容器注册表实现。 我们考虑了以下解决方案的可能性:Azure CR、Docker Hub、ECR、GCR、GitHub Packages、GitLab ContainerRegistry、HarborRegistry、JFrog Artifactory、Quay.io - 截至 2020 年 XNUMX 月。

这组参数足以满足第四个标准——即选择与版本对应的图像。 然而,对于所有其他标准,人们必须选择某种折衷的解决方案(更严厉或相反更宽松的政策)——具体取决于期望和财务能力。

例如,第三个标准 - 与开发人员的需求相关 - 可以通过在团队内组织流程来解决:图像的特定命名、维护特殊的允许列表和内部协议。 但最终它仍然需要自动化。 如果现成的解决方案的功能不够,您就必须自己做一些事情。

前两个标准的情况类似:如果不从外部系统(部署应用程序的系统(在我们的例子中为 Kubernetes))接收数据,就无法满足它们。

Git 中的工作流程图示

假设您正在 Git 中进行类似的工作:

werf中容器镜像的“智能”清理问题及其解决方案

图中带头的图标表示当前部署在 Kubernetes 中供任何用户(最终用户、测试人员、管理人员等)使用或由开发人员用于调试和类似目的的容器镜像。

如果清理策略只允许保留图像(而不是删除),会发生什么 通过给定的标签名称?

werf中容器镜像的“智能”清理问题及其解决方案

显然,这样的场景不会让任何人高兴。

如果政策允许图片不被删除,会发生什么变化? 根据给定的时间间隔/最后提交的次数?

werf中容器镜像的“智能”清理问题及其解决方案

结果已经好很多了,但还远远不够理想。 毕竟,我们仍然有开发人员需要注册表中的映像(甚至部署在 K8s 中)来调试错误......

总结一下目前的市场情况:容器注册中心提供的功能在清理时没有提供足够的灵活性,主要原因是 无法与外界互动。 事实证明,需要这种灵活性的团队被迫使用 Docker 注册表 API(或相应实现的本机 API)“从外部”独立实现镜像删除。

然而,我们正在寻找一种通用的解决方案,可以使用不同的注册表为不同的团队自动进行图像清理......

我们的通用图像清洁之路

这种需求从何而来? 事实上,我们不是一个独立的开发人员群体,而是一个同时为许多人提供服务的团队,帮助全面解决 CI/CD 问题。 主要的技术工具是开源实用程序 韦尔夫。 它的独特之处在于它不执行单一功能,而是伴随着从组装到部署的所有阶段的持续交付流程。

将映像发布到注册表*(在构建映像后立即)是此类实用程序的一个明显功能。 由于图像被放置在那里进行存储,那么 - 如果您的存储不是无限的 - 您需要负责它们的后续清理。 我们将进一步讨论我们如何在这方面取得成功并满足所有指定标准。

* 尽管注册表本身可能不同(Docker 注册表、GitLab 容器注册表、Harbor 等),但它们的用户面临相同的问题。 我们案例中的通用解决方案不依赖于注册表的实现,因为在注册表本身之外运行,并为每个人提供相同的行为。

尽管我们使用 werf 作为示例实现,但我们希望所使用的方法对面临类似困难的其他团队有用。

于是我们就开始忙碌起来 外部 实施清理图像的机制 - 而不是那些已经内置到容器注册表中的功能。 第一步是使用 Docker 注册表 API 为标签数量及其创建时间创建相同的原始策略(如上所述)。 添加到他们 基于已部署基础设施中使用的映像的允许列表, IE。 库伯内斯。 对于后者,使用 Kubernetes API 迭代所有已部署的资源并获取值列表就足够了 image.

这个简单的解决方案解决了最关键的问题(标准 1),但这只是我们改进清洁机制之旅的开始。 下一步 - 也是更有趣的 - 一步是决定 将发布的图像与 Git 历史记录关联起来.

标记方案

首先,我们选择了一种方法,其中最终图像应存储清洁所需的信息,并在标记方案上构建过程。 发布图像时,用户选择了特定的标记选项(git-branch, git-commit или git-tag)并使用相应的值。 在 CI 系统中,这些值是根据环境变量自动设置的。 实际上 最终图像与特定的 Git 原语相关联,在标签中存储清洁所需的数据。

这种方法产生了一组允许 Git 作为单一事实来源的策略:

  • 当在 Git 中删除分支/标签时,注册表中关联的镜像会被自动删除。
  • 与 Git 标签和提交关联的图像数量可以通过所选模式中使用的标签数量以及创建关联提交的时间来控制。

总的来说,最终的实施满足了我们的需求,但新的挑战很快就等待着我们。 事实是,在使用基于 Git 原语的标记方案时,我们遇到了许多缺点。 (由于他们的描述超出了本文的范围,大家可以自行熟悉细节 这里.) 因此,在决定改用更有效的标记方法(基于内容的标记)后,我们不得不重新考虑图像清理的实施。

新算法

为什么? 通过基于内容的标记,每个标记都可以满足 Git 中的多次提交。 清理图像时,您不能再假设 来自将新标签添加到注册表的提交。

对于新的清洁算法,决定放弃标记方案并构建 元图像处理,每个存储一堆:

  • 执行发布的提交(在容器注册表中是否添加、更改映像或保持不变并不重要);
  • 以及与组装图像相对应的内部标识符。

换句话说,它提供了 将发布的标签与 Git 中的提交链接起来.

最终配置和通用算法

配置清理时,用户现在可以访问选择当前映像的策略。 每个此类策略的定义如下:

  • 许多参考文献,即扫描期间使用的 Git 标签或 Git 分支;
  • 以及集合中每个参考的搜索图像限制。

为了说明这一点,默认策略配置如下所示:

cleanup:
  keepPolicies:
  - references:
      tag: /.*/
      limit:
        last: 10
  - references:
      branch: /.*/
      limit:
        last: 10
        in: 168h
        operator: And
    imagesPerReference:
      last: 2
      in: 168h
      operator: And
  - references:  
      branch: /^(main|staging|production)$/
    imagesPerReference:
      last: 10

此配置包含三个符合以下规则的策略:

  1. 保存最后 10 个 Git 标签的图像(按标签创建日期)。
  2. 对于上周有活动的不超过 2 个帖子,保存上周发布的图像不超过 10 张。
  3. 保存 10 张树枝图像 main, staging и production.

最终的算法可归结为以下步骤:

  • 从容器注册表中检索清单。
  • 排除 Kubernetes 中使用的镜像,因为我们已经通过轮询 K8s API 预先选择了它们。
  • 扫描Git历史记录并根据指定策略排除图像。
  • 删除剩余图像。

回到我们的插图,这就是 werf 发生的情况:

werf中容器镜像的“智能”清理问题及其解决方案

然而,即使您不使用 werf,类似的高级图像清理方法 - 在一种实现或另一种实现中(根据图像标记的首选方法) - 也可以应用于其他系统/实用程序。 要做到这一点,记住出现的问题并在堆栈中找到那些机会就足够了,这些机会可以让您尽可能顺利地集成他们的解决方案。 我们希望我们所走过的道路能够帮助您以新的细节和想法来看待您的特定案例。

结论

  • 大多数团队迟早都会遇到注册表溢出的问题。
  • 在寻找解决方案时,首先需要确定图像相关性的标准。
  • 流行的容器注册表服务提供的工具允许您组织非常简单的清理,而无需考虑“外部世界”:Kubernetes 中使用的图像以及团队工作流程的特殊性。
  • 灵活高效的算法必须了解 CI/CD 流程,并且不仅要操作 Docker 镜像数据。

PS

另请阅读我们的博客:

来源: habr.com

添加评论