专栏首页CNCF克服清理容器镜像的挑战

克服清理容器镜像的挑战

客座文章作者:Alexey Igrychevm,Flant的软件工程师。最初在Flant博客发表。

https://medium.com/flant-com/cleaning-up-container-images-with-werf-ec35b5d46569

处理交付到Kubernetes的现代云原生应用程序CI/CD流水线时,在容器注册表(container registry)驻留大量镜像会成为一个明显的问题。如果你不想让注册表超载(并为无用的占用空间买单),那么你需要了解哪些镜像将不再使用。

找到它们的标准是什么?为什么注册表根本不能意识到它们?这里是我们理解和以一个普遍的方式解决这个问题的旅程。虽然我们的解决方案的实践部分是在一个特定的开放源码工具(werf)中展示,但是同样的方法也可以应用到你自己的案例和工具中。

https://github.com/werf/werf

介绍

随着时间的推移,容器注册表中的镜像数量会大幅增长,这会占用越来越多的空间,并让你付出巨大的代价。为了规范、限制或维持注册表空间可接受的增长速度,你最有可能可以:

  • 对镜像使用固定数量的标记(tag);
  • 以某种方式清理镜像。

第一种方法只适用于小团队。如果永久标记(如latest、main、test、boris等)满足开发人员的需求,注册中心的规模就不会增长,你可以在很长一段时间内忘记清理它。这是因为所有过期的镜像最终都将被重写,并且你不必手动清理任何内容(常规的垃圾收集器会处理任何残留物)。

然而,这种方法严重限制了开发过程,很少在现代应用程序的CI/CD中使用。自动化已经成为开发过程中不可分割的一部分。它允许你更快地测试、部署和交付令人兴奋的新特性给用户。例如,在每次提交之后,CI流水线会在所有项目中自动创建。作为CI流水线的一部分,我们构建和测试镜像,将其部署到各种Kubernetes环境(层)中进行调试和其他测试。如果一切正常,那么这些更改将到达最终用户。这不再是一个火箭科学--毕竟,如果你正在阅读这篇文章,你可能也会这样做。

由于调试和实现新功能是同时进行的,而且每天可能有多个版本,因此开发过程涉及大量提交,这会导致注册表中出现大量镜像。因此,我们需要找到一种方法来清除注册表中未使用的(不再相关的)镜像。

但是我们如何知道镜像是否相关呢?

镜像相关性的标准

在大多数情况下,相关性的标准如下:

  1. 我们需要的第一种(最明显也是最重要的)镜像类型是目前在Kubernetes中使用的那些。删除这些镜像可能会导致生产停机(例如,可能需要使用这些镜像来创建新的副本),或者在某些环境中消除团队的所有调试工作。(这就是为什么我们创建了这个Prometheus exporter来监控Kubernetes集群中丢失的镜像)。 https://github.com/flant/k8s-image-availability-exporter
  2. 第二种类型(不太明显,但仍然很关键且与操作相关)是在当前版本出现严重问题时执行回滚所需的镜像。例如,在Helm的情况下,这些是在已保存的版本中使用的镜像。(作为一个边注,Helm默认保持256个版本;这是一个很大的数字,而且你很可能不需要所有这些版本)。毕竟,我们在开发过程中保留了所有这些不同的版本,因为我们希望使用它们进行回滚。
  3. 另一种类型是关于开发人员的需求:我们需要所有以某种方式与他们正在进行的活动相关的镜像。例如,如果我们考虑PR,那么保持链接到上一个提交的镜像是有意义的。通过这种方式,开发人员可以快速回滚更改并对其进行分析。
  4. 最后一种类型包括匹配应用程序特定版本的镜像,即表示最终产品:v1.0.0、20.04.01、sierra等等。

注:以上标准是根据我们与来自不同公司的数十个开发团队合作的经验设计出来的。然而,这些标准可能会因开发过程和使用的基础设施的特性而有所不同(例如,没有使用Kubernetes)。

现有的注册中心以及它们如何满足这些标准

流行的容器注册解决方案都有用于清理镜像的内置策略。它们允许你设置从注册表中删除标记的条件。但是,这些规则通常仅限于指定名称、创建时间和标记的数量*。

*取决于容器注册表的具体实现。我们分析了以下解决方案:Azure CR、Docker Hub、ECR、GCR、GitHub package、GitLab Container Registry、Harbor Registry、JFrog Artifactory、Quay.io(2020年9月)。

这组参数足以选择符合第四个条件的镜像,即链接到应用程序特定版本的镜像。然而,你必须做出妥协,以某种方式满足剩下的三个标准--以更严格或更宽松的方式,这取决于你的期望和经济能力。

例如,你可以修改开发团队中的工作流,以匹配第三种与开发人员相关的镜像类型:引入定制的镜像命名,维护特定的允许列表,或者安排团队成员之间的内部协议。但最终你必须让它自动化。如果可用的解决方案不够灵活,你将不得不自己设计!

其他两个条件(#1和#2)也会出现类似的情况:如果不从部署应用程序的外部系统(在我们的例子中是Kubernetes)获取数据,就无法满足这些条件。

Git工作流程说明

想象一下,这是你完整的Git工作流程:

在一个通用的开发工作流中提交、分支和发布

在上面的图片中,我们使用了一个男人的头部图标来标记目前Kubernetes中为任何类型的用户(最终用户、测试人员、管理人员等)部署的所有镜像,或者开发人员正在使用的所有镜像(用于调试和其他原因)。

如果你的清理策略允许你仅通过特定标记的名称来保存镜像,将会发生什么?

使用特定的标记保存镜像

这绝对不是我们想要的。

但是,如果你的策略允许你在特定的时间框架/最后N次提交前保留镜像,会发生什么呢?

使用特定的时间框架保存镜像

这个看起来好多了。然而,它远非完美!例如,仍然有一些开发人员需要其他可用的镜像(甚至部署到K8s)来进行调试。

总结一下当前的市场情况:在清理镜像时,现有的容器注册解决方案没有提供足够的灵活性。造成这种情况的主要原因是他们无法与外部世界进行交流。因此,需要这种灵活性的团队被迫“从外部”实现镜像删除,使用Docker Registry API(或特定注册表实现的API)的变通方法。

这就是为什么我们试图找到一个通用的解决方案,可以为所有团队和所有类型的容器注册自动化镜像清理过程……

我们的通用镜像清理方法

但我们究竟为什么需要它呢?问题是,我们并不是一个特定的开发团队,而是一个业务团队,支持各种类型的团队,帮助他们全面而实际地解决CI/CD问题。而werf开源工具是这一过程的主要驱动因素。werf的显著特性是它监视CD进程贯穿所有阶段:从构建到部署。

将镜像推送到注册表*(在它们被构建之后)是这样一个工具的明显功能之一。由于所有的镜像都必须被存储,因此自然需要以某种方式清理它们。这就是为什么我们提出的解决这个问题的方法符合上面列出的所有四个标准。

*注册表用户面临同样的问题,即使注册表本身可以是不同的种类(Docker Registry、GitLab Container Registry,Harbor等)。在我们的例子中,通用解决方案没有绑定到特定的注册表实现,因为它运行在注册表之外,并且它的行为在所有实现中是相同的。

虽然我们在示例中使用了werf,但我们希望其他有类似困难的团队会发现我们的方法是有用的,并能提供信息。

因此,我们转向清理机制的外部实现,而不是构建在容器注册表中的实现。我们的第一步是使用Docker Registry API根据标记的数量和它们的创建日期(上面讨论过)重新实现相同的基本策略。它们扩展为基于部署在Kubernetes中的镜像的特殊允许列表。为了实现它,我们使用Kubernetes API循环了所有已部署的资源,并获得了一个关联镜像列表。当我们有这个列表时,我们永远不会从注册表中清除这些镜像。

这个相当琐碎的解决方案解决了最关键的问题(标准1),但这仅仅是我们改进清理机制之旅的开始。下一步--也是更有趣的一步--是我们决定将发布的镜像链接到Git历史记录中。

标记方案

首先,我们选择了一种方法,在最终的镜像中包含清洗所需的所有信息,并引入标记方案。发布镜像时,用户选择首选的标记选项(git-branch、git-commit或git-tag)并使用相应的值。在CI系统中,这些值是根据环境变量自动分配的。基本上,最终的镜像链接到一个特定的Git对象(branch/commit/tag),并将清理所需的数据存储在标记中。

这种方法产生了一组策略,允许我们使用Git作为真相的单一来源:

  • 当删除Git branch/tag时,注册表中相关的镜像会自动删除。
  • 我们可以通过更改标记方案中的标记数量,和设置自创建关联提交以来的最大天数,来控制链接到Git标记/提交的镜像数量。

总的来说,这个实现符合我们的需要,但很快我们就面临了一个新的挑战。使用基于Git的标记方案给我们带来了几个缺点--足以重新考虑这种方法。(对这些缺陷的详细描述超出了本文的范围;你可以在这里了解更多信息)。因此,转向一种更有效的标记策略,基于内容的标记,导致我们改变了清理容器镜像的方式。

https://medium.com/flant-com/content-based-tagging-in-werf-eb96d22ac509

新算法

技术上的原因是什么?当使用基于内容的标记策略时,每个标记都可以在Git中链接到多次提交。因此,当你在清理过程中选择镜像时,你不能再单独依赖提交。

在我们新的清理算法中,我们决定完全放弃标记方案,而将整个过程建立在元镜像基础上。每个meta-image包含:

  • 发布镜像里的提交(也就是说,镜像是否在容器注册表中添加、更改或保持不变并不重要);
  • 对应于所构建的镜像的内部标识符。

换句话说,我们将发布的标记链接到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

上面的配置定义了三个策略,它们对应以下规则:

  • 保存10个最新的Git标记的镜像(根据创建日期)。
  • 保持上周发布的镜像不超过两个,并且上周的分支不超过10个。
  • 为主分支、登台分支和生产分支保留10个镜像。

最终算法包括以下步骤:

  • 从容器注册表获取清单。
  • 不包括在Kubernetes中使用的镜像(我们通过Kubernetes API获得已部署的资源)。
  • 扫描Git历史记录,并根据指定的策略排除镜像。
  • 删除其余的镜像。

回到我们的演示,下面是我们现在可以使用werf做的事情:

用werf保存所有你真正需要的容器镜像

即使你不使用werf,你可以或多或少(取决于标记镜像的方法)使用与其他系统/使用其他工具中的高级镜像清理类似的方法。只要记住潜在的问题,并尝试在你的堆栈中找到最好的方法来顺利地解决这些问题。

我们希望我们的经验教训将帮助你重新审视你的具体案例,带来新的想法和见解。

总结

  • 迟早,大多数团队都会面临容器注册表变得非常大且无法管理的问题。
  • 在寻找解决方案时,首先必须定义镜像相关性的标准。
  • 一般容器注册中心的功能受到基本清理算法的限制,这些算法没有考虑到“外部世界”,比如Kubernetes中使用的镜像以及团队工作流的特性。
  • 灵活和高效的算法必须了解CI/CD进程,不应该限制自己的Docker镜像数据。

如果你对本文中讨论有任何想法或问题,请通过Twitter与我们的开发人员联系。要了解更多关于werf作为CI/CD工具的信息,请访问其网站。

https://werf.io/

本文分享自微信公众号 - CNCF(lf_cncf),作者:CNCF

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Dragonfly发布Nydus容器镜像加速服务

    在容器的生产实践中,偏小的容器镜像能够很快的部署启动。当应用的镜像达到几个GB以上的时候,在节点上下载镜像通常会消耗大量的时间。Dragonfly 通过引入 P...

    CNCF
  • 你知道你的Docker镜像里有什么吗?Buildpacks知道。

    你不能修补一个你不知道你有的漏洞。这就是为什么了解Docker镜像中的内容是确保其安全性的第一步。幸运的是,任何使用Cloud Native Buildpack...

    CNCF
  • CNCF接纳Harbor为沙盒项目

    今天,云原生计算基金会(CNCF)将云原生镜像仓库项目Harbor引入CNCF的沙盒,沙盒是处在早期阶段的、进化中的云原生项目的主要基地。

    CNCF
  • 谈谈容器镜像运维

    (本文发布时,开源镜像仓库Harbor在Github上已获得3043颗星: https://github.com/vmware/harbor )

    Henry Zhang
  • Docker | Docker技术基础梳理(二) - 镜像管理

    镜像是一个Docker的可执行文件,其中包括运行应用程序所需的所有代码内容、依赖库、环境变量和配置文件等。

    咸鱼学Python
  • Docker镜像超详细介绍

      首先我们来看看镜像到底是什么?虽然前面有介绍过镜像和容器 ,但也不是特别的深入。

    用户4919348
  • Docker的Image

    同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,如ubuntu仓库源里,有15.10、14.04等多个不同的版本,我们使用 REPOSITORY:TA...

    Criss@陈磊
  • docker学习之使用镜像

    原文在此 ? 获取镜像 之前提到过,Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像并运行。 从 Docker Regist...

    若与
  • 腾讯云接入域名变更备案资料过程记录

    魏艾斯博客www.vpsss.net
  • Docker 入门(二)

    上一篇文章 Docker 入门(一)简单讲述了 docker 中的一些基本概念。文本将会重点讲述 image 镜像的内容。

    凌虚

扫码关注云+社区

领取腾讯云代金券