Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

L'article traite des problèmes de nettoyage des images qui s'accumulent dans les registres de conteneurs (Docker Registry et ses analogues) dans les réalités des pipelines CI/CD modernes pour les applications cloud natives livrées sur Kubernetes. Les principaux critères de pertinence des images et les difficultés qui en découlent pour automatiser le nettoyage, gagner de la place et répondre aux besoins des équipes sont donnés. Enfin, à partir de l'exemple d'un projet Open Source spécifique, nous vous expliquerons comment surmonter ces difficultés.

introduction

Le nombre d'images dans un registre de conteneurs peut croître rapidement, occupant davantage d'espace de stockage et augmentant ainsi considérablement son coût. Pour contrôler, limiter ou maintenir une croissance acceptable de l'espace occupé dans le registre, il est accepté :

  1. utiliser un nombre fixe de balises pour les images ;
  2. nettoyer les images d'une manière ou d'une autre.


La première limitation est parfois acceptable pour les petites équipes. Si les développeurs disposent de suffisamment de balises permanentes (latest, main, test, boris etc.), le registre ne gonflera pas et vous n’aurez pas du tout à penser à le nettoyer pendant longtemps. Après tout, toutes les images non pertinentes sont effacées et il n'y a tout simplement plus de travail de nettoyage (tout est fait par un ramasse-miettes ordinaire).

Cependant, cette approche limite considérablement le développement et est rarement applicable aux projets CI/CD modernes. Une partie intégrante du développement a été automatisation, qui vous permet de tester, déployer et fournir de nouvelles fonctionnalités aux utilisateurs beaucoup plus rapidement. Par exemple, dans tous nos projets, un pipeline CI est automatiquement créé à chaque commit. Dans celui-ci, l'image est assemblée, testée, déployée sur divers circuits Kubernetes pour le débogage et les vérifications restantes, et si tout va bien, les modifications parviennent à l'utilisateur final. Et ce n’est plus sorcier, mais quelque chose de quotidien pour beaucoup – probablement pour vous, puisque vous lisez cet article.

Étant donné que la correction des bugs et le développement de nouvelles fonctionnalités sont effectués en parallèle et que les versions peuvent être effectuées plusieurs fois par jour, il est évident que le processus de développement s'accompagne d'un nombre important de commits, ce qui signifie un grand nombre d'images dans le registre. De ce fait se pose la question de l’organisation d’un nettoyage efficace du registre, c’est-à-dire supprimer les images non pertinentes.

Mais comment déterminer si une image est pertinente ?

Critères de pertinence de l'image

Dans la grande majorité des cas, les principaux critères seront :

1. La première (la plus évidente et la plus critique de toutes) concerne les images qui actuellement utilisé dans Kubernetes. La suppression de ces images peut entraîner des coûts d'arrêt de production importants (par exemple, les images peuvent être nécessaires pour la réplication) ou annuler les efforts de l'équipe de débogage sur l'une des boucles. (Pour cette raison, nous avons même fait un spécial Exportateurs Prometheus, qui suit l'absence de telles images dans n'importe quel cluster Kubernetes.)

2. Deuxièmement (moins évident, mais aussi très important et encore une fois lié à l'exploitation) - les images qui requis pour le rollback en cas de détection de problèmes graves dans la version actuelle. Par exemple, dans le cas de Helm, ce sont des images utilisées dans les versions enregistrées de la version. (À propos, par défaut dans Helm, la limite est de 256 révisions, mais il est peu probable que quiconque ait vraiment besoin de sauvegarder un tel un grand nombre de versions ?..) Après tout, nous stockons notamment les versions afin de pouvoir les utiliser plus tard, c'est-à-dire « revenir » vers eux si nécessaire.

3. Troisièmement - besoins des développeurs: Toutes les images liées à leur travail actuel. Par exemple, si nous envisageons un PR, il est alors logique de laisser une image correspondant au dernier commit et, disons, au commit précédent : de cette façon, le développeur peut rapidement revenir à n'importe quelle tâche et travailler avec les dernières modifications.

4. Quatrièmement - des images qui correspondent aux versions de notre application, c'est à dire. sont le produit final : v1.0.0, 20.04.01/XNUMX/XNUMX, sierra, etc.

NB : Les critères définis ici ont été formulés sur la base d'une expérience d'interaction avec des dizaines d'équipes de développement de différentes entreprises. Cependant, bien entendu, en fonction des spécificités des processus de développement et de l'infrastructure utilisée (par exemple, Kubernetes n'est pas utilisé), ces critères peuvent différer.

Éligibilité et solutions existantes

En règle générale, les services populaires dotés de registres de conteneurs proposent leurs propres politiques de nettoyage d'image : vous pouvez y définir les conditions dans lesquelles une balise est supprimée du registre. Cependant, ces conditions sont limitées par des paramètres tels que les noms, l'heure de création et le nombre de balises*.

* Dépend des implémentations spécifiques du registre de conteneurs. Nous avons examiné les possibilités des solutions suivantes : Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io - à partir de septembre 2020.

Cet ensemble de paramètres est largement suffisant pour satisfaire le quatrième critère, c'est-à-dire sélectionner les images qui correspondent aux versions. Cependant, pour tous les autres critères, il faut choisir une sorte de solution de compromis (une politique plus dure ou, à l'inverse, plus indulgente) - en fonction des attentes et des capacités financières.

Par exemple, le troisième critère - lié aux besoins des développeurs - peut être résolu en organisant des processus au sein des équipes : dénomination spécifique des images, maintien de listes d'autorisation spéciales et d'accords internes. Mais à terme, il faudra encore l’automatiser. Et si les capacités des solutions toutes faites ne suffisent pas, vous devez faire quelque chose par vous-même.

La situation des deux premiers critères est similaire : ils ne peuvent être satisfaits sans recevoir des données d'un système externe - celui sur lequel les applications sont déployées (dans notre cas, Kubernetes).

Illustration du workflow dans Git

Disons que vous travaillez quelque chose comme ceci dans Git :

Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

L'icône avec une tête dans le diagramme indique les images de conteneurs actuellement déployées dans Kubernetes pour tous les utilisateurs (utilisateurs finaux, testeurs, gestionnaires, etc.) ou utilisées par les développeurs à des fins de débogage et similaires.

Que se passe-t-il si les politiques de nettoyage autorisent uniquement la conservation des images (et non leur suppression) par noms de balises donnés?

Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

Évidemment, un tel scénario ne fera plaisir à personne.

Qu’est-ce qui changera si les politiques autorisent la suppression des images ? par un intervalle de temps donné/nombre de derniers commits?

Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

Le résultat est bien meilleur, mais est encore loin d'être idéal. Après tout, nous avons encore des développeurs qui ont besoin d'images dans le registre (ou même déployées dans les K8) pour déboguer les bugs...

Pour résumer la situation actuelle du marché : les fonctions disponibles dans les registres de conteneurs n'offrent pas suffisamment de flexibilité lors du nettoyage, et la principale raison en est aucun moyen d'interagir avec le monde extérieur. Il s'avère que les équipes qui ont besoin d'une telle flexibilité sont obligées d'implémenter indépendamment la suppression d'images « de l'extérieur », en utilisant l'API Docker Registry (ou l'API native de l'implémentation correspondante).

Cependant, nous recherchions une solution universelle qui automatiserait le nettoyage des images pour différentes équipes utilisant différents registres...

Notre chemin vers le nettoyage universel des images

D’où vient ce besoin ? Le fait est que nous ne sommes pas un groupe distinct de développeurs, mais une équipe qui sert plusieurs d'entre eux à la fois, aidant à résoudre de manière globale les problèmes de CI/CD. Et le principal outil technique pour cela est l'utilitaire Open Source cour. Sa particularité est qu'il ne remplit pas une seule fonction, mais accompagne les processus de livraison continue à toutes les étapes : de l'assemblage au déploiement.

La publication d'images dans le registre* (immédiatement après leur création) est une fonction évidente d'un tel utilitaire. Et puisque les images y sont placées pour être stockées, alors - si votre stockage n'est pas illimité - vous devez être responsable de leur nettoyage ultérieur. La manière dont nous y sommes parvenus, en satisfaisant à tous les critères spécifiés, sera discutée plus en détail.

* Bien que les registres eux-mêmes puissent être différents (Docker Registry, GitLab Container Registry, Harbor, etc.), leurs utilisateurs sont confrontés aux mêmes problèmes. La solution universelle dans notre cas ne dépend pas de la mise en œuvre du registre, car fonctionne en dehors des registres eux-mêmes et offre le même comportement pour tout le monde.

Bien que nous utilisions werf comme exemple d’implémentation, nous espérons que les approches utilisées seront utiles à d’autres équipes confrontées à des difficultés similaires.

Alors nous nous sommes occupés externe mise en œuvre d'un mécanisme de nettoyage des images - au lieu des capacités déjà intégrées aux registres de conteneurs. La première étape consistait à utiliser l'API Docker Registry pour créer les mêmes politiques primitives pour le nombre de balises et l'heure de leur création (mentionnées ci-dessus). Ajouté à eux liste d'autorisation basée sur les images utilisées dans l'infrastructure déployée, c'est à dire. Kubernetes. Pour ce dernier, il suffisait d'utiliser l'API Kubernetes pour parcourir toutes les ressources déployées et obtenir une liste de valeurs image.

Cette solution triviale a résolu le problème le plus critique (critère n°1), mais n'était que le début de notre parcours pour améliorer le mécanisme de nettoyage. L'étape suivante - et bien plus intéressante - fut la décision associer les images publiées à l'historique Git.

Schémas de marquage

Pour commencer, nous avons choisi une approche dans laquelle l'image finale doit stocker les informations nécessaires au nettoyage, et avons construit le processus sur des schémas de marquage. Lors de la publication d'une image, l'utilisateur a sélectionné une option de balisage spécifique (git-branch, git-commit ou git-tag) et utilisé la valeur correspondante. Dans les systèmes CI, ces valeurs étaient définies automatiquement en fonction des variables d'environnement. En fait l'image finale était associée à une primitive Git spécifique, en stockant les données nécessaires au nettoyage dans des étiquettes.

Cette approche a abouti à un ensemble de politiques qui ont permis d'utiliser Git comme source unique de vérité :

  • Lors de la suppression d'une branche/tag dans Git, les images associées dans le registre étaient automatiquement supprimées.
  • Le nombre d'images associées aux balises et aux commits Git peut être contrôlé par le nombre de balises utilisées dans le schéma sélectionné et l'heure à laquelle le commit associé a été créé.

Dans l’ensemble, la mise en œuvre qui en a résulté a satisfait nos besoins, mais un nouveau défi nous attendait bientôt. Le fait est qu'en utilisant des schémas de balisage basés sur les primitives Git, nous avons rencontré un certain nombre de lacunes. (Comme leur description dépasse le cadre de cet article, chacun peut se familiariser avec les détails ici.) Par conséquent, après avoir décidé de passer à une approche de balisage plus efficace (balisage basé sur le contenu), nous avons dû reconsidérer la mise en œuvre du nettoyage des images.

Nouvel algorithme

Pourquoi? Avec le balisage basé sur le contenu, chaque balise peut satisfaire plusieurs validations dans Git. Lors du nettoyage des images, vous ne pouvez plus supposer seulement à partir du commit où la nouvelle balise a été ajoutée au registre.

Pour le nouvel algorithme de nettoyage, il a été décidé de s'éloigner des schémas de marquage et de construire processus de méta-image, dont chacun stocke un tas de :

  • le commit sur lequel la publication a été effectuée (peu importe si l'image a été ajoutée, modifiée ou est restée la même dans le registre de conteneurs) ;
  • et notre identifiant interne correspondant à l'image assemblée.

En d'autres termes, il a été prévu lier les balises publiées aux commits dans Git.

Configuration finale et algorithme général

Lors de la configuration du nettoyage, les utilisateurs ont désormais accès aux politiques qui sélectionnent les images actuelles. Chacune de ces politiques est définie :

  • de nombreuses références, c'est-à-dire Balises Git ou branches Git utilisées lors de l'analyse ;
  • et la limite des images recherchées pour chaque référence de l'ensemble.

Pour illustrer, voici à quoi la configuration de stratégie par défaut a commencé à ressembler :

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

Cette configuration contient trois stratégies qui respectent les règles suivantes :

  1. Enregistrez l'image pour les 10 dernières balises Git (par date de création de balise).
  2. N'enregistrez pas plus de 2 images publiées la semaine dernière pour pas plus de 10 fils de discussion ayant eu une activité la semaine dernière.
  3. Enregistrez 10 images pour les branches main, staging и production.

L'algorithme final se résume aux étapes suivantes :

  • Récupération des manifestes à partir du registre de conteneurs.
  • À l'exclusion des images utilisées dans Kubernetes, car Nous les avons déjà présélectionnés en interrogeant l'API K8s.
  • Analyser l'historique Git et exclure les images en fonction des politiques spécifiées.
  • Suppression des images restantes.

Revenant à notre illustration, voici ce qui se passe avec werf :

Le problème du nettoyage « intelligent » des images de conteneurs et sa solution dans werf

Cependant, même si vous n'utilisez pas werf, une approche similaire du nettoyage avancé des images - dans une implémentation ou une autre (selon l'approche préférée du balisage des images) - peut être appliquée à d'autres systèmes/utilitaires. Pour ce faire, il suffit de mémoriser les problèmes qui se posent et de trouver dans votre stack les opportunités qui vous permettent d'intégrer leur solution le plus facilement possible. Nous espérons que le chemin que nous avons parcouru vous aidera à examiner votre cas particulier avec de nouveaux détails et réflexions.

Conclusion

  • Tôt ou tard, la plupart des équipes sont confrontées au problème du débordement du registre.
  • Lors de la recherche de solutions, il faut d’abord déterminer les critères de pertinence de l’image.
  • Les outils proposés par les services populaires de registre de conteneurs permettent d'organiser un nettoyage très simple qui ne prend pas en compte le « monde extérieur » : les images utilisées dans Kubernetes et les particularités des workflows de l'équipe.
  • Un algorithme flexible et efficace doit comprendre les processus CI/CD et ne pas fonctionner uniquement avec les données d'image Docker.

PS

A lire aussi sur notre blog :

Source: habr.com

Ajouter un commentaire