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





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



介绍



容器注册表中的图像数量可以快速增长,占用更多的存储空间,并因此大大增加了其成本。为了控制,限制或保持注册表中占用空间的可接受增长,可以接受以下方法:



  1. 为图像使用固定数量的标签;
  2. 以任何方式清理图像。


第一个限制有时对小团队有效。如果开发者有足够的永久标签(latestmaintestboris等),注册表不会在尺寸膨胀,可以是一个很长一段时间不去想清洗。毕竟,所有不相关的图像都会磨损,并且根本没有清理工作(所有工作都由常规垃圾收集器完成)。



但是,这种方法严重限制了开发,并且几乎不适用于当今的CI / CD项目。自动化已成为发展不可或缺的一部分使您可以更快地测试,部署和交付新功能给用户。例如,在我们所有的项目中,每次提交时都会自动创建一个CI管道。它构建一个映像,对其进行测试,并将其部署到各种Kubernetes电路中,以进行调试和其余检查,如果一切顺利,则更改将到达最终用户。长期以来,这不是火箭科学,而是许多人的日常生活-最有可能适合您,因为您正在阅读本文。



由于错误已修复且新功能并行开发,并且一天可以执行多次发布,因此很明显,开发过程中伴随着大量的提交,这意味着注册表中大量的映像... 结果,组织有效的注册表清理的问题,即 删除不相关的图像。



但是,您如何知道图像是否最新?



图像相关性标准



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



1.第一个(最明显,最关键的)是Kubernetes当前使用的图像。删除这些映像可能会导致严重的生产停机成本(例如,在复制过程中可能需要映像),或者使从事任何电路调试工作的团队的工作无效。(因此,我们甚至创建了一个特殊的Prometheus导出器,以监视任何Kubernetes集群中此类图像的缺失。)



2.第二个(不太明显,但也很重要,并且又与操作有关)-在严重情况下需要回滚的图像问题在当前版本中。例如,对于Helm,这些是在发行版的已保存版本中使用的映像。 (顺便说一句,Helm中的默认限制是256个修订版,但是几乎没有人真的需要保存这么多版本吗?..)毕竟,为此,特别是,我们存储版本以便以后可以使用使用,即如有必要,“回滚”给他们。



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



4.第四-图像对应于我们应用程序的版本,即 是最终产品:v1.0.0、20.04.01,sierra等。



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



资格和现有解决方案



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



*取决于特定的容器注册表实现。我们考虑了以下解决方案的可能性:截至2020年9月,Azure CR,Docker Hub,ECR,GCR,GitHub软件包,GitLab容器注册表,港口注册表,JFrog Artifactory,Quay.io。



这组参数足以满足第四个条件-即选择与版本匹配的图像。但是,对于所有其他标准,则必须根据期望和财务能力选择某种折衷解决方案(更严格的政策,或者相反,选择保留政策)。



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



这种情况与前两个条件相似:如果不从外部系统(部署应用程序的外部系统)接收数据(在我们的示例中为Kubernetes),就无法满足它们。



Git中的工作流程插图



假设您在Git中进行如下操作:







图表中带有头部图标标记了当前已部署到Kubernetes的任何用户(最终用户,测试人员,管理人员等)或开发人员用于调试的容器映像和类似的目标。



如果清理策略允许您仅保留(不删除)指定标记名的图像会怎样?







显然,这种情况不会使任何人满意。



如果策略允许您在给定的时间间隔/最近提交的次数内不删除图像将会发生什么变化







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



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



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



我们通往通用图像清洁的道路



这种需求从何而来?事实是我们不是一个独立的开发人员小组,而是一个可以立即为其中许多人提供服务的团队,有助于全面解决CI / CD问题。而主要的技术工具是开源werf实用程序。它的特点是它不执行单个功能,而是在从组装到部署的所有阶段都伴随着连续的交付过程。



将映像发布到注册表*(在生成映像后立即)是这种实用程序的一项明显功能。并且由于将图像放置在此处进行存储,因此-如果存储空间不是无限的,则需要对它们的后续清洁负责。满足所有指定条件的方式,我们将如何取得成功,将作进一步讨论。



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



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



因此,我们占用了外部实现清洁图像的机制-而不是容器注册表中已内置的功能。第一步是使用Docker Registry API通过标记的数量及其创建时间来创建所有相同的原始策略(如上所述)。根据已部署的基础架构中使用的映像,将允许列表添加到其中Kubernetes。对于后者,通过Kubernetes API足以遍历所有已部署的资源并获取值列表image



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



标记方案



首先,我们选择了一种方法,在该方法中,最终图像必须存储用于清洁的必要信息,然后在标记方案上构建流程。当发布的图像,用户所选择的一定的标记选项(git-branchgit-commitgit-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张图像mainstaging然后production


最终算法可简化为以下步骤:



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


回到我们的说明中,这是werf发生的情况:







但是,即使您不使用werf,在一种或另一种实现中(根据用于标记图像的首选方法)也可以使用类似的方法来高级清除图像-也可以在其他系统中应用。 /实用程序。为此,您只需记住所出现的问题并在堆栈中找到那些机会,就可以最顺利地构建其解决方案。我们希望所走的道路将有助于以新的细节和思想来审视您的特殊情况。



结论



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


聚苯乙烯



另请参阅我们的博客:






All Articles