Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

In diesem Artikel wird das Problem der Bereinigung von Images erörtert, die in Container-Registries (wie Docker Registry und anderen) in den modernen CI/CD-Pipelines für cloud-native Anwendungen, die in Kubernetes bereitgestellt werden, angesammelt werden. Es werden die grundlegenden Kriterien für die Relevanz von Images sowie die daraus resultierenden Schwierigkeiten bei der Automatisierung der Bereinigung, dem Platzverbrauch und der Erfüllung der Bedürfnisse der Teams vorgestellt. Schließlich zeigen wir anhand eines konkreten Open-Source-Projekts auf, wie diese Herausforderungen überwunden werden können.

Einführung

Die Anzahl der Images in der Container-Registry kann schnell ansteigen, was mehr Speicherplatz beansprucht und somit die Kosten erheblich erhöht. Um das Wachstum des Speicherplatzes in der Registry zu kontrollieren, zu begrenzen oder auf einem akzeptablen Niveau zu halten, ist es üblich:

  1. eine feste Anzahl von Tags für die Images zu verwenden;
  2. auf irgendeine Art und Weise die Images zu bereinigen.


Die erste Einschränkung ist manchmal für kleine Teams akzeptabel. Wenn die Entwickler mit den konstanten Tags auskommen (

, , ...),latest, main, test, boris und so weiter), das Register wird nicht aufgebläht, und man muss lange Zeit überhaupt nicht an eine Bereinigung denken. Denn alle veralteten Images werden verdrängt, und für die Reinigung bleibt einfach keine Arbeit übrig (alles wird vom regulären Garbage Collector erledigt).

Dennoch schränkt dieser Ansatz die Entwicklung stark ein und ist selten auf CI/CD moderner Projekte anwendbar. Ein unverzichtbarer Bestandteil der Entwicklung ist Automatisierung, die es ermöglicht, neuen Funktionalitäten viel schneller zu testen, bereitzustellen und den Nutzern zur Verfügung zu stellen. Zum Beispiel wird in all unseren Projekten bei jedem Commit automatisch eine CI-Pipeline erstellt. Dort wird ein Image erstellt, getestet und in verschiedene Kubernetes-Umgebungen zur Fehlerbehebung und weiteren Prüfungen bereitgestellt, und wenn alles gut läuft, gelangen die Änderungen an den Endnutzer. Und das ist längst keine Raketenwissenschaft mehr, sondern für viele ganz normal – wahrscheinlich auch für Sie, da Sie diesen Artikel lesen.

Da das Beheben von Bugs und die Entwicklung neuer Funktionalitäten parallel erfolgen und Releases mehrmals täglich durchgeführt werden können, ist offensichtlich, dass der Entwicklungsprozess mit einer erheblichen Anzahl von Commits verbunden ist, und das bedeutet – eine große Anzahl von Abbildungen im Registry. Infolgedessen stellt sich die dringende Frage nach der effektiven Bereinigung des Registrys, d.h. der Entfernung nicht aktueller Abbildungen.

Aber wie lässt sich überhaupt bestimmen, ob eine Abbildung aktuell ist?

Kriterien für die Aktualität von Abbildungen

In den überwiegenden meisten Fällen sind die Hauptkriterien wie folgt:

1. Das erste (das offensichtlichste und kritischste von allen) — sind die Abbildungen, die aktuell in Kubernetes verwendet werden.Das Löschen dieser Abbildungen kann ernsthafte Kosten aufgrund von Produktionsausfall verursachen (z.B. können diese Abbildungen bei der Replikation benötigt werden) oder die Bemühungen des Teams, das an der Fehlerbehebung in einem der Konturen arbeitet, zunichte machen. (Aus diesem Grund haben wir sogar einen speziellen Prometheus-Exporter, der das Fehlen solcher Abbildungen in jedem Kubernetes-Cluster überwacht.)

2. Das zweite (weniger offensichtlich, aber ebenfalls sehr wichtig und erneut betrieblich relevant) — sind Abbildungen, die für einen Rollback benötigt werden, wenn ernsthafte Probleme auftreten. in der aktuellen Version. Zum Beispiel betrifft es bei Helm die Abbilder, die in den gespeicherten Versionen des Releases verwendet werden. (Übrigens liegt das Standardlimit in Helm bei 256 Revisionen, aber ich bezweifle, dass jemand tatsächlich einen so hohen Bedarf an der Aufbewahrung hat. so vieler Versionen?..) Schließlich speichern wir Versionen unter anderem, um sie später nutzen zu können, d.h. um im Bedarfsfall "zurückzurollen".

3. Der dritte — Bedürfnisse der Entwickler: alle Abbilder, die mit ihren aktuellen Arbeiten in Verbindung stehen. Wenn wir beispielsweise einen PR betrachten, ist es sinnvoll, das Abbild des letzten Commits und, sagen wir, des vorherigen Commits zu behalten: So kann der Entwickler schnell zu jeder Aufgabe zurückkehren und mit den letzten Änderungen arbeiten.

4. Der vierte — Abbilder, die entsprechend den Versionen unserer Anwendung, d.h. die Endprodukte darstellen: v1.0.0, 20.04.01, sierra usw.

Hinweis: Die hier definierten Kriterien basieren auf Erfahrungen aus der Zusammenarbeit mit Dutzenden von Entwicklerteams verschiedener Unternehmen. Allerdings können diese Kriterien je nach den spezifischen Entwicklungsprozessen und der verwendeten Infrastruktur (z. B. wenn Kubernetes nicht eingesetzt wird) variieren.

Einhaltung der Kriterien und bestehende Lösungen

Beliebte Dienste mit Container-Registries bieten in der Regel eigene Richtlinien zur Bereinigung von Images an: In diesen können Sie festlegen, unter welchen Bedingungen ein Tag aus der Registry entfernt wird. Die Möglichkeiten dieser Bedingungen sind jedoch durch Parameter wie Namen, Erstellungszeit und Anzahl der Tags* begrenzt.

* Abhängig von den konkreten Implementierungen der Container-Registry. Wir haben die Funktionen der folgenden Lösungen untersucht: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io — Stand September 2020.

Dieses Set an Parametern reicht aus, um das vierte Kriterium zu erfüllen – also um Images auszuwählen, die mit den Versionen übereinstimmen. Für alle anderen Kriterien muss jedoch eine Art Kompromisslösung gefunden werden (eine strengere oder eher mildere Politik), abhängig von den Erwartungen und finanziellen Möglichkeiten.

Das dritte Kriterium – das mit den Bedürfnissen der Entwickler zusammenhängt – kann durch die Organisation der internen Teamprozesse gelöst werden: spezifische Namensgebungen von Images, Führung spezieller Allow-Listen und interne Vereinbarungen. Letztendlich muss es jedoch auch automatisiert werden. Wenn die vorhandenen Lösungen nicht ausreichen, bleibt oft nur, etwas Eigenes zu schaffen.

Ähnlich verhält es sich mit den ersten beiden Kriterien: Diese können nicht erfüllt werden, ohne Daten von einem externen System zu erhalten – dem System, in dem die Anwendungen bereitgestellt werden (in unserem Fall ist es Kubernetes).

Illustration des Workflows in Git

Angenommen, Sie arbeiten ungefähr nach folgendem Schema in Git:

Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

Die Symbole mit dem Kopf in dem Diagramm kennzeichnen die Container-Images, die derzeit in Kubernetes für verschiedene Benutzer (Endbenutzer, Tester, Manager usw.) bereitgestellt oder von Entwicklern zu Debugging- und ähnlichen Zwecken verwendet werden.

Was passiert, wenn die Aufbewahrungsrichtlinien erlauben, Images nur bei bestimmten Tag-Namen zu belassen (nicht zu löschen)? offensichtlich wird ein solches Szenario niemanden erfreuen.?

Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

Was ändert sich, wenn die Richtlinien das Behalten von Images erlauben?

basierend auf einem bestimmten Zeitintervall / der Anzahl der letzten Commits? Das Ergebnis hat sich deutlich verbessert, bleibt jedoch nach wie vor weit vom Ideal entfernt. Immerhin gibt es weiterhin Entwickler, die Images im Repository (oder sogar in K8s bereitgestellt) benötigen, um Bugs zu debuggen…?

Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

Zusammenfassend lässt sich sagen, dass die in den Container-Repositories verfügbaren Funktionen nicht die notwendige Flexibilität beim Cleanup bieten, und der Hauptgrund dafür ist,

dass keine Möglichkeit besteht, mit der Außenwelt zu interagieren. keine Möglichkeit, mit der Außenwelt zu interagieren. Das bedeutet, dass Teams, die solche Flexibilität benötigen, gezwungen sind, das Löschen von Images „von außen“ selbst umzusetzen, indem sie die Docker Registry API (oder die native API der entsprechenden Implementierung) verwenden.

Wir suchten jedoch nach einer universellen Lösung, die die Bereinigung von Images für verschiedene Teams automatisiert, die unterschiedliche Registries verwenden…

Unser Weg zur universellen Image-Bereinigung

Warum besteht diese Notwendigkeit? Wir sind nicht eine isolierte Gruppe von Entwicklern, sondern ein Team, das viele solche Gruppen unterstützt und hilft, CI/CD-Fragen umfassend zu lösen. Das wichtigste technische Tool dafür ist eine Open Source-Anwendung werf. Ihre Besonderheit ist, dass sie nicht nur eine einzige Funktion erfüllt, sondern die kontinuierlichen Lieferprozesse in allen Phasen begleitet: von der Erstellung bis zur Bereitstellung.

Die Veröffentlichung in einem Registry* (sofort nach der Erstellung) ist eine offensichtliche Funktion eines solchen Tools. Da die Images dort gespeichert werden, müssen—falls Ihr Speicher nicht unbegrenzt ist—auch deren anschließende Bereinigung gewährleistet sein. Im Folgenden wird erläutert, wie wir dies erfolgreich umgesetzt haben, während wir alle festgelegten Kriterien erfüllt haben.

* Auch wenn die Registries unterschiedlich sein können (Docker Registry, GitLab Container Registry, Harbor usw.), stehen ihre Benutzer vor ähnlichen Herausforderungen. Eine universelle Lösung in unserem Fall hängt nicht von der Implementierung der Registry ab, da sie außerhalb der Registries selbst durchgeführt wird und für alle dasselbe Verhalten bietet.

Obwohl wir werf als Beispiel für die Implementierung nutzen, hoffen wir, dass die verwendeten Ansätze auch anderen Teams, die vor ähnlichen Schwierigkeiten stehen, nützlich sein werden.

Also haben wir uns mit externen Implementierung eines Mechanismus zur Bereinigung von Images beschäftigt – anstelle der bereits in den Registries für Container integrierten Möglichkeiten. Der erste Schritt war die Nutzung der Docker Registry API zur Erstellung einfacher Richtlinien hinsichtlich der Anzahl der Tags und ihrer Erstellungszeit (wie oben erwähnt). Dazu wurde eine Allow-Liste basierend auf den Images, die in der bereitgestellten Infrastruktur verwendet werden, hinzugefügt, d.h. Kubernetes. Für letzteres genügte es, über die Kubernetes API alle bereitgestellten Ressourcen zu durchlaufen und eine Liste der Werte zu erhalten, image.

Diese triviale Lösung hat das kritischste Problem (Kriterium Nr. 1) gelöst, war jedoch nur der Anfang unseres Weges zur Verbesserung des Reinigungsmechanismus. Der nächste — und deutlich interessantere — Schritt war die Entscheidung die veröffentlichten Images mit der Git-Historie zu verknüpfen..

Tagging-Schemata

Zu Beginn wählten wir einen Ansatz, bei dem das endgültige Image die notwendige Information für die Reinigung speichern sollte, und wir bauten den Prozess auf Tagging-Schemata auf. Bei der Veröffentlichung eines Images wählte der Benutzer eine bestimmte Tagging-Option (git-branch, git-commit oder git-tag) und verwendete den entsprechenden Wert. In CI-Systemen wurden diese Werte automatisch basierend auf Umgebungsvariablen gesetzt. Im Grunde genommen wurde das endgültige Image mit einem bestimmten Git-Primitiv verknüpft, wobei die erforderlichen Daten für die Reinigung in Labels gespeichert wurden.

Im Rahmen dieses Ansatzes entstand ein Satz von Richtlinien, die es ermöglichten, Git als einzige Quelle der Wahrheit zu verwenden:

  • Beim Löschen eines Branches/Tages in Git wurden automatisch auch die verknüpften Images im Registry gelöscht.
  • Die Anzahl der Images, die mit Git-Tags und Commits verknüpft sind, konnte durch die Anzahl der in dem ausgewählten Schema verwendeten Tags und den Zeitpunkt der Erstellung des zugehörigen Commits reguliert werden.

Insgesamt erfüllte die entstandene Implementierung unsere Bedürfnisse, doch bald erwartete uns eine neue Herausforderung. Während der Nutzung von Git-Tagging-Schemata stießen wir auf mehrere Nachteile. (Da ihre Beschreibung den Rahmen dieses Artikels sprengt, können Interessierte die Details einsehen. hier.) Daher, nachdem wir uns entschieden haben, zu einem effizienteren Ansatz für das Tagging (content-based tagging) überzugehen, mussten wir auch die Implementierung der Image-Bereinigung überdenken.

Ein neuer Algorithmus

Warum? Beim Tagging im Rahmen des content-based Ansatzes kann jedes Tag mehrere Commits in Git erfüllen. Bei der Image-Bereinigung kann man nicht länger nur vom Commit ausgehen, bei dem das neue Tag ins Repository hinzugefügt wurde.

Für den neuen Algorithmus zur Bereinigung wurde beschlossen, von Tagging-Schemata abzusehen und den Prozess auf Meta-Images aufzubauen,die jeweils eine Verknüpfung enthalten aus:

  • Der Commit, in dem das Bild veröffentlicht wurde (unabhängig davon, ob das Bild im Container-Registry hinzugefügt, geändert oder beibehalten wurde);
  • und unserer internen Identifikation, die dem erstellten Bild entspricht.

Mit anderen Worten wurde sichergestellt, dass die veröffentlichten Tags mit den Commits in Git verbunden sind..

Die endgültige Konfiguration und der allgemeine Algorithmus

Benutzern stehen beim Konfigurieren der Bereinigung Richtlinien zur Verfügung, die zur Auswahl der aktuellen Bilder verwendet werden. Jede dieser Richtlinien wird definiert durch:

  • eine Vielzahl von References, d.h. Git-Tags oder Git-Branches, die während des Scannens verwendet werden;
  • und ein Limit der gesuchten Bilder für jedes Reference aus der Menge.

Zur Veranschaulichung — so könnte die Standardkonfiguration der Richtlinien aussehen:

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

Diese Konfiguration enthält drei Richtlinien, die den folgenden Regeln entsprechen:

  1. Das Bild für die letzten 10 Git-Tags (nach dem Erstellungsdatum des Tags) zu behalten.
  2. Speichern Sie maximal 2 Images, die in der letzten Woche veröffentlicht wurden, für nicht mehr als 10 Branches mit Aktivität in der letzten Woche.
  3. Speichern Sie 10 Images für Branches. main, staging und production.

Der endgültige Algorithmus umfasst die folgenden Schritte:

  • Abrufen der Manifeste aus dem Container-Registry.
  • Ausnehmen von Images, die in Kubernetes verwendet werden, da diese bereits ausgewählt wurden, indem wir die K8s-API abgefragt haben.
  • Durchsuchen der Git-Historie und Ausschließen von Images gemäß festgelegten Richtlinien.
  • Löschen der verbleibenden Images.

Zurückblickend auf unsere Abbildung, so sieht es mit werf aus:

Das Problem der "intelligenten" Bereinigung von Container-Images und seine Lösung in werf

Selbst wenn Sie werf nicht verwenden, kann ein ähnlicher Ansatz zur fortgeschrittenen Bereinigung von Images – in einer oder anderen Implementierung (gemäß dem bevorzugten Tagging-Ansatz für Images) – auch in anderen Systemen/Dienstprogrammen angewendet werden. Es genügt, sich der auftretenden Probleme bewusst zu sein und die Möglichkeiten in Ihrem Stack zu finden, die eine nahtlose Integration ihrer Lösungen ermöglichen. Wir hoffen, dass unser Weg Ihnen hilft, auch Ihre speziellen Fälle mit neuen Details und Gedanken zu betrachten.

Fazit

  • Früher oder später sieht sich die Mehrheit der Teams mit dem Problem der Überlastung des Registries konfrontiert.
  • Bei der Suche nach Lösungen sollten zunächst die Kriterien für die Relevanz des Abbilds festgelegt werden.
  • Die von beliebten Container-Registry-Diensten angebotenen Tools ermöglichen eine sehr einfache Bereinigung, die die "äußere Welt" nicht berücksichtigt: Abbilder, die in Kubernetes verwendet werden, sowie die spezifischen Arbeitsabläufe im Team.
  • Ein flexibler und effektiver Algorithmus sollte ein Verständnis für CI/CD-Prozesse haben und nicht nur mit Docker-Abbilddaten operieren.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster