
In dem Artikel wird die Problematik der Bereinigung von Images behandelt, die in den Registries von Containern (Docker Registry und ähnlichen) im Kontext moderner CI/CD-Pipelines für Cloud-native Anwendungen gespeichert werden, die in Kubernetes bereitgestellt werden. Es werden die Hauptkriterien für die Relevanz von Images und die daraus resultierenden Schwierigkeiten bei der Automatisierung der Bereinigung, der Platzersparnis und der Erfüllung der Anforderungen der Teams beschrieben. Schließlich zeigen wir am Beispiel eines konkreten Open Source-Projekts, wie man diese Schwierigkeiten überwinden kann.
Einführung
Die Anzahl der Images in der Container-Registry kann rasant ansteigen, wodurch mehr Speicherplatz beansprucht wird, was wiederum die Kosten erheblich erhöht. Um das Wachstum des in der Registry belegten Speicherplatzes zu kontrollieren, zu begrenzen oder auf einem akzeptablen Niveau zu halten, wird Folgendes empfohlen:
- eine feste Anzahl von Tags für Images zu verwenden;
- auf irgendeine Weise Images zu bereinigen.
Diese erste Einschränkung ist manchmal für kleine Teams akzeptabel. Wenn den Entwicklern die konstanten Tags (latest, main, test, boris usw.) ausreichen, wird die Registry nicht wachsen und man muss lange Zeit nicht an die Bereinigung denken. Alle nicht mehr relevanten Images werden überschrieben, und es bleibt einfach keine Arbeit für die Bereinigung (das geschieht alles durch den integrierten Garbage Collector).
Dennoch schränkt dieser Ansatz die Entwicklung stark ein und ist nur selten auf CI/CD in modernen Projekten anwendbar. Ein wesentlicher Teil der Entwicklung ist geworden Automatisierung, was es ermöglicht, neuen Funktionen viel schneller zu testen, bereitzustellen und an die Benutzer zu liefern. Zum Beispiel wird in all unseren Projekten bei jedem Commit automatisch eine CI-Pipeline erstellt. Dort wird ein Image erstellt, getestet, in verschiedenen Kubernetes-Umgebungen für Debugging und weitere Prüfungen bereitgestellt, und wenn alles gut ist – gelangen die Änderungen zum Endbenutzer. Und das ist längst keine Raketenwissenschaft mehr, sondern Alltag für viele – wahrscheinlich auch für Sie, da Sie diesen Artikel lesen.
Da die Behebung von Fehlern und die Entwicklung neuer Funktionen parallel vor sich gehen und Releases mehrere Male am Tag durchgeführt werden können, ist es offensichtlich, dass der Entwicklungsprozess mit einer erheblichen Anzahl von Commits begleitet wird, was bedeutet – eine große Anzahl von Images in der Registry. Infolgedessen stellt sich die dringende Frage der Organisation einer effektiven Bereinigung der Registry, d.h. der Entfernung nicht mehr relevanter Images.
Aber wie kann man überhaupt feststellen, ob ein Image relevant ist?
Kriterien der Relevanz von Images
In der überwiegenden Mehrzahl der Fälle werden die Hauptkriterien wie folgt aussehen:
1. Das erste (das offensichtlichste und kritischste von allen) sind die Images, die momentan in Kubernetes verwendet werden. Das Entfernen dieser Images kann zu erheblichen Kosten durch Ausfallzeiten in der Produktion führen (zum Beispiel können Images bei der Replikation benötigt werden) oder die Bemühungen des Teams, das an der Fehlerbehebung in einer der Umgebungen arbeitet, zunichte machen. (Aus diesem Grund haben wir sogar einen speziellen , der das Fehlen solcher Images in jedem Kubernetes-Cluster überwacht.)
2. Das zweite (weniger offensichtlich, aber ebenfalls sehr wichtig und wieder relevant für den Betrieb) sind die Images, die für Rollbacks im Falle schwerwiegender Probleme in der aktuellen Version erforderlich sind. Beispielsweise sind dies im Falle von Helm die Images, die in den gespeicherten Versionen des Releases verwendet werden. (Übrigens hat Helm standardmäßig ein Limit von 256 Revisionen, aber es bezweifelt jemand ernsthaft, dass es einen Bedarf gibt, so viele Versionen zu speichern?..) Schließlich speichern wir Versionen, damit wir sie später verwenden können, das heißt, um "zurückzuspringen", falls notwendig. 3. Das dritte sind die
Bedürfnisse der Entwickler: alle Images, die mit ihren aktuellen Arbeiten zusammenhängen. Wenn wir beispielsweise einen PR betrachten, macht es Sinn, das Image zu behalten, das dem letzten Commit und, sagen wir, dem vorherigen Commit entspricht: So kann der Entwickler schnell zu jeder Aufgabe zurückkehren und mit den letzten Änderungen arbeiten.4. Das vierte sind die Images, die
den Versionen unserer Anwendung entsprechen , d.h. das Endprodukt sind: v1.0.0, 20.04.01, sierra usw.NB: Die hier definierten Kriterien wurden auf der Grundlage der Erfahrungen mit Dutzenden von Entwicklungsteams aus verschiedenen Unternehmen formuliert. Allerdings können diese Kriterien je nach den Besonderheiten in den Entwicklungsprozessen und der verwendeten Infrastruktur (zum Beispiel wenn Kubernetes nicht verwendet wird) variieren.
Erfüllung der Kriterien und bestehende Lösungen
Beliebte Services mit Container-Registrys bieten in der Regel ihre eigenen Richtlinien zur Bereinigung von Images an: Dort können Sie Bedingungen definieren, unter denen ein Tag aus dem Registry entfernt wird. Allerdings sind die Möglichkeiten für diese Bedingungen auf Parameter wie Namen, Erstellungszeit und Anzahl der Tags* beschränkt.
Beliebte Dienste mit Container-Registries bieten in der Regel ihre eigenen Richtlinien zur Bereinigung von Images an: Sie können darin die Bedingungen festlegen, unter denen ein Tag aus der Registry gelöscht wird. Die Möglichkeiten dieser Bedingungen sind jedoch auf Parameter wie Namen, Erstellungszeit und Anzahl der Tags* beschränkt.
* Es hängt von den spezifischen Implementierungen des Container-Registrys ab. Wir haben die Möglichkeiten der folgenden Lösungen geprüft: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io – Stand September 2020.
Ein solches Set an Parametern reicht aus, um das vierte Kriterium zu erfüllen – das heißt, um Images auszuwählen, die den Versionen entsprechen. Für alle anderen Kriterien muss jedoch eine Kompromisslösung gewählt werden (eine strengere oder im Gegenteil eine nachsichtige Politik) – abhängig von den Erwartungen und finanziellen Möglichkeiten.
Das dritte Kriterium – das mit den Bedürfnissen der Entwickler verknüpft ist – kann beispielsweise durch die Organisation von Prozessen innerhalb der Teams gelöst werden: spezifische Namensgebung von Images, Führung spezieller Allow-Listen und interne Vereinbarungen. Letztendlich muss es jedoch dennoch automatisiert werden. Wenn die Möglichkeiten vorhandener Lösungen nicht ausreichen, muss man etwas Eigenes entwickeln.
Ähnlich verhält es sich mit den zwei ersten Kriterien: Diese können nicht ohne die Erfassung von Daten aus einem externen System erfüllt werden – nämlich dem System, in dem die Anwendungen bereitgestellt werden (in unserem Fall Kubernetes).
Illustration des Workflows in Git
Angenommen, Sie arbeiten ungefähr nach folgendem Schema in Git:

Auf dem Diagramm sind die Container-Images mit Köpfen markiert, die derzeit in Kubernetes für beliebige Benutzer (Endanwender, Tester, Manager usw.) bereitgestellt oder von Entwicklern zur Fehlersuche und ähnlichen Zwecken verwendet werden.
Was passiert, wenn die Aufräumpolitiken zulassen, dass Images nur nach bestimmten Tag-Namen?

offengelassen (nicht gelöscht) werden?
Offensichtlich wird ein solches Szenario niemanden erfreuen. Was würde sich ändern, wenn die Politiken erlauben, Images nicht zu löschen?

nach einem bestimmten Zeitintervall / der Anzahl der letzten Commits?
Das Ergebnis wurde deutlich besser, ist jedoch immer noch weit von ideal entfernt. Immerhin haben wir immer noch Entwickler, die Images im Repository (oder sogar in K8s bereitgestellt) zur Fehlersuche benötigen ... Zusammenfassend lässt sich sagen, dass die im Container-Repository verfügbaren Funktionen nicht genügend Flexibilität bei der Bereinigung bieten, und der Hauptgrund dafür ist, dass es nicht möglich ist, mit der Außenwelt zu interagieren.Das bedeutet, dass Teams, die eine solche Flexibilität benötigen, gezwungen sind, das Löschen von Images "von außen" selbst zu implementieren, indem sie die Docker Registry API (oder die native API der entsprechenden Implementierung) verwenden.
Wir haben jedoch nach einer universellen Lösung gesucht, die die Bereinigung von Images für verschiedene Teams automatisiert, die unterschiedliche Registries verwenden ...
Unser Weg zur universellen Bereinigung von Images
Woher kommt diese Notwendigkeit? Wir sind keine isolierte Gruppe von Entwicklern, sondern ein Team, das mehreren solchen Gruppen dient und ihnen hilft, Fragen zu CI/CD umfassend zu lösen. Das wichtigste technische Werkzeug dafür ist das Open Source-Tool . Sein Merkmal ist, dass es nicht nur eine einzelne Funktion ausführt, sondern die kontinuierlichen Lieferprozesse in allen Phasen begleitet: von der Erstellung bis zur Bereitstellung.
Die Veröffentlichung in der Registry* von Images (unmittelbar nach ihrer Erstellung) ist eine offensichtliche Funktion eines solchen Tools. Da die Images dort zur Speicherung abgelegt werden, muss man— falls der Speicher nicht unbegrenzt ist— auch für deren anschließende Bereinigung sorgen. Im Folgenden wird erläutert, wie wir darin erfolgreich waren und alle vorgegebenen Kriterien erfüllt haben.
* Obwohl die Registries unterschiedlich sein können (Docker Registry, GitLab Container Registry, Harbor usw.), sehen sich ihre Benutzer denselben Problemen gegenüber. Die universelle Lösung hängt in unserem Fall nicht von der Implementierung der Registry ab, da sie außerhalb der Registries selbst ausgeführt wird und für alle das gleiche Verhalten bietet.
Obwohl wir werf als Beispiel für die Implementierung verwenden, hoffen wir, dass die verwendeten Ansätze auch anderen Teams, die ähnlichen Herausforderungen gegenüberstehen, von Nutzen sein werden.
Also haben wir uns mit der externen Implementierung eines Mechanismus zur Bereinigung von Images beschäftigt — anstelle der bereits in die Registries für Container integrierten Möglichkeiten. Der erste Schritt war die Verwendung der Docker Registry API zur Erstellung der oben erwähnten grundlegenden Richtlinien hinsichtlich der Anzahl von Tags und deren Erstellungszeit. Dies wurde durch eine Allow-Liste basierend auf den in der bereitgestellten Infrastruktur verwendeten Images ergänzt, d.h. Kubernetes. Dafür genügte es, über die Kubernetes-API alle bereitgestellten Ressourcen zu durchlaufen und eine Liste von Werten zu erhalten. Image.
Diese triviale Lösung hat das kritischste Problem (Kriterium Nr. 1) behoben, war jedoch nur der Anfang unseres Weges zur Verbesserung des Reinigungsmechanismus. Der nächste – und weitaus interessantere – Schritt war die Entscheidung, die veröffentlichten Images mit der Geschichte von Git zu verknüpfen..
Tagging-Schemata
Zu Beginn wählten wir einen Ansatz, bei dem das endgültige Image die erforderlichen Informationen für die Reinigung speichern sollte, und wir bauten den Prozess auf Tagging-Schemata auf. Bei der Veröffentlichung des 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 Wesentlichen wurde das endgültige Image mit einem bestimmten Git-Primitiv verknüpft, das die notwendigen Daten für die Bereinigung in Labels speichert.
Im Rahmen eines solchen Ansatzes entstand ein Satz von Richtlinien, der es ermöglichte, Git als einzige Informationsquelle zu verwenden:
- Beim Löschen von Branches/Tags wurden automatisch auch die zugehörigen 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 gewählten Schema verwendeten Tags und den Zeitpunkt der Erstellung des zugehörigen Commits geregelt werden.
Insgesamt erfüllte die entstandene Implementierung unsere Bedürfnisse, aber bald wartete eine neue Herausforderung auf uns. Während unserer Nutzung der Tagging-Schemata anhand von Git-Primitiven stießen wir auf eine Reihe von Nachteilen. (Da ihre Beschreibung den Rahmen dieses Artikels sprengt, können sich Interessierte über die Details informieren. .) Daher mussten wir, nachdem wir uns für einen effektiveren Ansatz zum Tagging (content-based tagging) entschieden hatten, auch die Implementierung der Image-Reinigung überdenken.
Ein neuer Algorithmus
Warum? Bei der Tagging im Rahmen von content-based kann jeder Tag mehreren Commits in Git entsprechen. Bei der Bereinigung von Images kann man nicht mehr aus nur dem Commit ausgehen, in dem der neue Tag zum Registry hinzugefügt wurde.
Für den neuen Algorithmus zur Image-Reinigung wurde entschieden, von Tagging-Schemata abzugehen und einen Prozess auf Meta-Images aufzubauen, die jeweils eine Verbindung aus speichern:
- dem Commit, zu dem die Veröffentlichung erfolgte (unabhängig davon, ob das Image im Container-Registry hinzugefügt, geändert oder beibehalten wurde);
- und unserem internen Identifikator, der dem erstellten Image entspricht.
Mit anderen Worten, wurde sichergestellt, dass die veröffentlichten Tags mit Commits in Git verknüpft sind,.
Die endgültige Konfiguration und der allgemeine Algorithmus
Benutzern standen bei der Konfiguration der Bereinigung Richtlinien zur Verfügung, die für die Auswahl relevanter Images verwendet werden. Jede solcher Richtlinie wird definiert durch:
- eine Menge von References, d.h. Git-Tags oder Git-Branches, die beim Scannen verwendet werden;
- und dem Limit der gesuchten Images für jede Reference aus der Menge.
Zur Veranschaulichung — so sieht die Konfiguration der Standardrichtlinien aus:
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:
- Das Bild für die letzten 10 Git-Tags (nach Erstellungsdatum des Tags) zu speichern.
- Nicht mehr als 2 Images, die in der letzten Woche veröffentlicht wurden, für nicht mehr als 10 Branches mit Aktivität in der letzten Woche zu speichern.
- Speichern von 10 Images für die Branches
main,stagingundProduktion.
Der endgültige Algorithmus lässt sich in folgende Schritte zusammenfassen:
- Abrufen der Manifeste aus dem Container-Registry.
- Ausschluss von Images, die in Kubernetes verwendet werden, da diese bereits vorselektiert wurden, indem wir die K8s API befragt haben.
- Scannen der Git-Historie und Ausschluss der Images gemäß den festgelegten Richtlinien.
- Löschen der verbleibenden Images.
Um zu unserer Veranschaulichung zurückzukehren, so sieht das Ergebnis mit werf aus:

Selbst wenn Sie werf nicht verwenden, kann ein ähnlicher Ansatz zur fortgeschrittenen Bereinigung von Images — in einer oder anderen Umsetzung (in Übereinstimmung mit der bevorzugten Vorgehensweise bei der Tag-Verwaltung) — auch in anderen Systemen/Utlilitäten angewendet werden. Es genügt, die Probleme, die auftreten können, zu beachten und die Möglichkeiten in Ihrem Stack zu finden, die es ermöglichen, Lösungen nahtlos zu integrieren. Wir hoffen, dass der Weg, den wir gegangen sind, Ihnen hilft, auch Ihren spezifischen Fall mit neuen Details und Gedanken zu betrachten.
Fazit
- Früher oder später sieht sich die Mehrheit der Teams mit dem Problem der Überfüllung des Registrys konfrontiert.
- Bei der Suche nach Lösungen müssen zunächst die Kriterien für die Relevanz des Images bestimmt werden.
- Die von beliebten Container-Registry-Diensten angebotenen Tools ermöglichen eine sehr einfache Bereinigung, die die "äußere Welt" nicht berücksichtigt: Bilder, die in Kubernetes verwendet werden, und die Besonderheiten der Arbeitsabläufe im Team.
- Ein flexibler und effizienter Algorithmus sollte ein Verständnis für CI/CD-Prozesse haben und nicht nur mit den Daten von Docker-Images umgehen.
P.S.
Lesen Sie auch in unserem Blog:
- «»;
- «»;
- «»;
- «».
Quelle: habr.com
