Fusion à 3 voies dans werf: déploiement dans Kubernetes avec Helm "sous stéroïdes"

Quelque chose s'est produit que nous (et pas seulement nous) attendions: werf , notre utilitaire Open Source pour créer des applications et les livrer à Kubernetes, prend désormais en charge l'application des modifications à l'aide de correctifs de fusion à 3 voies! En plus de cela, il est devenu possible d'adopter les ressources K8 existantes dans les versions de Helm sans recréer ces ressources.



S'il est très court, définissez WERF_THREE_WAY_MERGE=enabled - nous obtenons le déploiement "comme dans kubectl apply ", compatible avec les installations existantes sur Helm 2 et même un peu plus.

Mais commençons par la théorie: qu'est-ce que les correctifs de fusion à 3 voies en général, comment les gens en sont-ils venus à leur génération et pourquoi sont-ils importants dans les processus CI / CD avec une infrastructure basée sur Kubernetes? Et après cela - voyons ce qu'est la fusion à 3 dans werf, quels modes sont utilisés par défaut et comment le gérer.

Qu'est-ce qu'un patch de fusion à 3 voies?


Commençons donc par déployer les ressources décrites dans les manifestes YAML dans Kubernetes.

Pour travailler avec des ressources, l'API Kubernetes propose les opérations de base suivantes: créer, corriger, remplacer et supprimer. Il est supposé qu'avec leur aide, il est nécessaire de créer un déploiement continu pratique des ressources vers le cluster. Comment?

Équipes impératives de Kubectl


La première approche de la gestion des objets dans Kubernetes consiste à utiliser les commandes impératives de kubectl pour créer, modifier et supprimer ces objets. Autrement dit:

  • kubectl run commande kubectl run peut exécuter le déploiement ou le travail:

     kubectl run --generator=deployment/apps.v1 DEPLOYMENT_NAME --image=IMAGE 
  • kubectl scale - modifiez le nombre de répliques:

     kubectl scale --replicas=3 deployment/mysql 
  • etc.

Une telle approche peut sembler commode à première vue. Cependant, il y a des problèmes:

  1. C'est difficile à automatiser .
  2. Comment refléter la configuration dans Git? Comment revoir les changements survenus dans un cluster?
  3. Comment assurer la reproductibilité de la configuration au redémarrage?
  4. ...

Il est clair que cette approche ne cadre pas bien avec le stockage de l'application et de l'infrastructure sous forme de code (IaC; ou même GitOps en tant qu'option plus moderne, gagnant en popularité dans l'écosystème Kubernetes) avec le code. Par conséquent, ces équipes n'ont pas reçu de développement supplémentaire dans kubectl.

Créer, obtenir, remplacer et supprimer des opérations


Avec la création primaire , tout est simple: on envoie le manifeste à l'opération de create de kube api et la ressource est créée. La représentation YAML du manifeste peut être stockée dans Git, et pour créer, utilisez la commande kubectl create -f manifest.yaml .

La suppression est également simple: nous substituons le même manifest.yaml de Git à la commande kubectl delete -f manifest.yaml .

L'opération de replace vous permet de remplacer complètement la configuration des ressources par une nouvelle, sans recréer la ressource. Cela signifie qu'avant d'apporter une modification à une ressource, il est logique de demander la version actuelle avec l'opération get , de la modifier et de la mettre à jour avec l'opération replace . Le verrouillage optimiste est intégré à kube apiserver, et si l'objet a changé après l'opération get , l'opération de replace échouera.

Pour stocker la configuration dans Git et la mettre à jour à l'aide de replace, vous devez effectuer une opération get , conserver la configuration de Git avec ce que nous avons obtenu et effectuer le replace . Normalement, kubectl vous permet uniquement d'utiliser la commande kubectl replace -f manifest.yaml , où manifest.yaml est le manifest.yaml entièrement préparé (dans notre cas, joint) qui doit être installé. Il s'avère que l'utilisateur doit implémenter des manifestes de fusion, mais ce n'est pas une mince affaire ...

Il convient également de noter que bien que manifest.yaml soit stocké dans Git, nous ne pouvons pas savoir à l'avance si vous devez créer un objet ou le mettre à jour - cela devrait être fait par le logiciel utilisateur.

Conclusion: pouvons-nous créer un déploiement continu uniquement avec créer, remplacer et supprimer, en veillant à ce que la configuration de l'infrastructure soit stockée dans Git avec le code et un CI / CD pratique?

Fondamentalement, nous pouvons ... Pour ce faire, nous devons implémenter l'opération de fusion des manifestes et une sorte de liaison qui:

  • vérifie la présence d'un objet dans le cluster,
  • effectue la création initiale de la ressource,
  • le met à jour ou le supprime.

Lors de la mise à jour, vous devez tenir compte du fait que la ressource peut avoir changé depuis le dernier get et gérer automatiquement le cas d'un verrouillage optimiste - effectuez des tentatives répétées de mise à jour.

Cependant, pourquoi réinventer la roue lorsque kube-apiserver offre une autre façon de mettre à jour les ressources: l'opération de patch , qui supprime certains des problèmes décrits par l'utilisateur?

Patch


Nous sommes donc arrivés aux patchs.

Les correctifs sont le principal moyen d'appliquer des modifications aux objets existants dans Kubernetes. L'opération de patch fonctionne de telle sorte que:

  • l'utilisateur kube-apiserver doit envoyer le patch au format JSON et spécifier l'objet,
  • et apiserver lui-même traitera l'état actuel de l'objet et l'amènera à la forme souhaitée.

Un verrouillage optimiste dans ce cas n'est pas nécessaire. Cette opération est plus déclarative par rapport à remplacer, bien qu'à première vue, cela puisse sembler l'inverse.

De cette façon:

  • en utilisant l'opération de create , nous créons un objet à partir du manifeste de Git,
  • en utilisant delete - supprimer si l'objet n'est plus requis,
  • en utilisant patch - nous modifions l'objet, en le ramenant à la forme décrite dans Git.

Cependant, pour ce faire, vous devez créer le correctif correct !

Fonctionnement des correctifs dans Helm 2: fusion bidirectionnelle


La première fois qu'une version est installée, Helm effectue une opération de create sur les ressources de graphique.

Lors de la mise à jour de la version Helm pour chaque ressource:

  • compte le patch entre la version de la ressource du graphique précédent et la version actuelle du graphique,
  • applique ce patch.

Nous appellerons un tel correctif de fusion bidirectionnelle , car 2 manifestes participent à sa création:

  • Manifeste des ressources de la version précédente,
  • Manifeste de la ressource à partir de la ressource actuelle.

Lors de la suppression, l'opération de delete dans kube apiserver est appelée pour les ressources déclarées dans la version précédente mais non déclarées dans la version actuelle.

L'approche avec le correctif de fusion bidirectionnel a un problème: elle conduit à une désynchronisation de l'état réel de la ressource dans le cluster et du manifeste dans Git .

Un exemple de problème


  • Dans Git, un manifeste est stocké sur le graphique dans lequel le champ image déploiement a la valeur ubuntu:18.04 .
  • L'utilisateur via kubectl edit changé la valeur de ce champ en ubuntu:19.04 .
  • Lorsque vous redéployez le graphique, Helm ne génère pas de correctif , car le champ d' image dans la version précédente de la version et dans le graphique actuel est le même.
  • Après le déploiement répété de l' image , ubuntu:19.04 reste, bien que ubuntu:18.04 soit écrit sur le graphique.

Nous avons obtenu la désynchronisation et perdu la déclarativité.

Qu'est-ce qu'une ressource synchronisée?


D'une manière générale, il est impossible d'obtenir une correspondance complète entre un manifeste de ressources dans un cluster en cours d'exécution et un manifeste de Git. Parce que dans le manifeste réel, il peut y avoir des annotations / étiquettes de service, des conteneurs supplémentaires et d'autres données ajoutées et supprimées dynamiquement par certains contrôleurs de la ressource. Nous ne pouvons pas et ne voulons pas conserver ces données dans Git. Cependant, nous voulons que lors du déploiement, les champs que nous avons explicitement spécifiés dans Git prennent les valeurs appropriées.

Il s'avère que cette règle générale d'une ressource synchronisée : lorsque vous déployez une ressource, vous pouvez modifier ou supprimer uniquement les champs qui sont explicitement spécifiés dans le manifeste de Git (ou qui étaient enregistrés dans la version précédente, mais sont maintenant supprimés).

Patch de fusion à 3 voies


L'idée principale du patch de fusion à 3 voies : nous générons un patch entre la dernière version appliquée du manifeste de Git et la version cible du manifeste de Git, en tenant compte de la version actuelle du manifeste du cluster de travail. Le correctif final doit respecter la règle des ressources synchronisées:

  • les nouveaux champs ajoutés à la version cible sont ajoutés à l'aide du patch;
  • les champs existants dans la dernière version appliquée et qui n'existent pas dans le champ cible sont réinitialisés à l'aide du correctif;
  • Les champs de la version actuelle de l'objet qui diffèrent de la version cible du manifeste sont mis à jour à l'aide du correctif.

C'est par ce principe que sont générés les patchs kubectl apply :

  • la dernière version appliquée du manifeste est stockée dans l'annotation de l'objet lui-même,
  • target - extrait du fichier YAML spécifié,
  • actuel - à partir d'un cluster de travail.

Maintenant que nous avons compris la théorie, il est temps de vous dire ce que nous avons fait au werf.

Appliquer les modifications à werf


Auparavant, werf, comme Helm 2, utilisait des correctifs de fusion bidirectionnelle.

Patch de réparation


Afin de passer à un nouveau type de correctifs - fusion à 3 voies - la première étape, nous avons introduit les correctifs dits de réparation .

Lors du déploiement, le correctif de fusion bidirectionnel standard est utilisé, mais werf génère en outre un correctif qui synchronise l'état réel de la ressource avec ce qui est écrit dans Git (un tel correctif est créé en utilisant la même règle de ressource synchronisée décrite ci-dessus).

En cas de rassynchronisation, à la fin du déploiement, l'utilisateur reçoit un AVERTISSEMENT avec le message et le correctif appropriés, qui doivent être appliqués pour amener la ressource sous une forme synchronisée. De plus, ce patch est enregistré dans une annotation spéciale werf.io/repair-patch . Il est supposé que l'utilisateur lui-même appliquera ce patch avec ses mains: werf ne l'appliquera pas en principe.

La génération de correctifs de réparation est une mesure temporaire qui vous permet de tester réellement la création de correctifs sur le principe de la fusion à trois, mais sans appliquer automatiquement ces correctifs. Pour le moment, ce mode de fonctionnement est activé par défaut.

Patch de fusion à 3 voies pour les nouvelles versions uniquement


À compter du 1er décembre 2019, les versions bêta et alpha de werf commencent par défaut à utiliser des correctifs de fusion à trois voies à part entière pour appliquer les modifications uniquement aux nouvelles versions de Helm déployées via werf. Les versions existantes continueront d'utiliser l'approche de correctif de fusion + réparation bidirectionnelle.

Vous pouvez activer ce mode de fonctionnement de manière explicite en définissant WERF_THREE_WAY_MERGE_MODE=onlyNewReleases maintenant.

Remarque : la fonctionnalité est apparue dans werf sur plusieurs versions: dans le canal alpha, elle est devenue prête à partir de la version v1.0.5-alpha.19 , et dans le canal bêta avec v1.0.4-beta.20 .

Patch de fusion à 3 voies pour toutes les versions


À partir du 15 décembre 2019, les versions bêta et alpha de werf commencent à utiliser des correctifs de fusion à trois voies par défaut pour appliquer les modifications à toutes les versions.

Ce mode de fonctionnement peut être explicitement WERF_THREE_WAY_MERGE_MODE=enabled définissant WERF_THREE_WAY_MERGE_MODE=enabled maintenant.

Que faire des ressources de mise à l'échelle automatique?


Kubernetes propose 2 types de mise à l'échelle automatique: HPA (horizontal) et VPA (vertical).

Horizontal sélectionne automatiquement le nombre de répliques, vertical - le nombre de ressources. Le nombre de répliques et les besoins en ressources sont spécifiés dans le manifeste de ressources (voir spec.replicas ou spec.containers[].resources.limits.cpu , spec.containers[].resources.limits.memory et autres ).

Problème: si un utilisateur configure une ressource sur le graphique afin qu'elle affiche des valeurs spécifiques pour les ressources ou que les répliques et les scalers automatiques soient activés pour cette ressource, à chaque déploiement werf réinitialisera ces valeurs à ce qui est écrit dans le manifeste du graphique.

Il existe deux solutions au problème. Pour commencer, il est préférable de ne pas spécifier explicitement les valeurs de mise à l'échelle automatique dans le manifeste du graphique. Si, pour une raison quelconque, cette option ne convient pas (par exemple, car il est pratique de définir les limites de ressources initiales et le nombre de répliques sur le graphique), werf propose les annotations suivantes:

  • werf.io/set-replicas-only-on-creation=true
  • werf.io/set-resources-only-on-creation=true

Si une telle annotation est présente, werf ne réinitialisera pas les valeurs correspondantes à chaque déploiement, mais les définira uniquement lors de la création initiale de la ressource.

Pour plus d'informations, consultez la documentation du projet pour HPA et VPA .

Refuser l'utilisation du correctif de fusion à 3 voies


L'utilisateur peut toujours interdire l'utilisation de nouveaux correctifs dans werf en utilisant la variable d'environnement WERF_THREE_WAY_MERGE_MODE=disabled . Cependant, à partir du 1er mars 2020, cette interdiction cessera de fonctionner et il ne sera possible d'utiliser que des correctifs de fusion à 3 voies.

Adoption de ressources dans werf


La maîtrise de la méthode d'application des modifications dans les correctifs de fusion à 3 voies nous a permis d'implémenter immédiatement une fonctionnalité telle que l'adoption des ressources existantes dans le cluster dans la version Helm.

Helm 2 a un problème: vous ne pouvez pas ajouter une ressource à un manifeste de graphique qui existe déjà dans le cluster sans recréer cette ressource à partir de zéro (voir # 6031 , # 3275 ). Nous avons appris à werf à accepter les ressources existantes dans une version. Pour ce faire, vous devez définir une annotation sur la version actuelle de la ressource à partir d'un cluster en cours d'exécution (par exemple, en utilisant kubectl edit ):

 "werf.io/allow-adoption-by-release": RELEASE_NAME 

Maintenant, la ressource doit être décrite sur le graphique et lors du prochain déploiement par la version werf de la version avec le nom correspondant, la ressource existante sera acceptée dans cette version et restera sous son contrôle. De plus, dans le processus d'acceptation de la ressource pour publication, werf ramènera l'état actuel de la ressource du cluster de travail à l'état décrit sur le graphique en utilisant les mêmes correctifs de fusion à 3 voies et la règle de ressource synchronisée.

Remarque : la définition de WERF_THREE_WAY_MERGE_MODE n'affecte pas l'adoption des ressources - dans le cas de l'adoption, un correctif de fusion à 3 voies est toujours utilisé.

Les détails sont dans la documentation .

Conclusions et plans futurs


J'espère qu'après cet article, il est devenu plus clair ce que sont les correctifs de fusion à 3 et pourquoi ils sont venus à eux. D'un point de vue pratique du développement du projet werf, leur mise en œuvre a constitué une nouvelle étape vers l'amélioration du déploiement de type Helm. Vous pouvez maintenant oublier les problèmes de synchronisation de configuration, qui se sont souvent produits lors de l'utilisation de Helm 2. En même temps, une nouvelle fonctionnalité utile de l'adoption des ressources Kubernetes déjà téléchargées dans la version Helm a été ajoutée.

Il y a encore des problèmes et des difficultés dans le déploiement de type Helm, tels que l'utilisation des modèles Go, et nous continuerons à les résoudre.

Des informations sur les méthodes de mise à jour des ressources et leur adoption sont également disponibles sur cette page de documentation .

Heaume 3


Une note spéciale est digne de la nouvelle version majeure récemment publiée de Helm - v3 - qui utilise également des correctifs de fusion à 3 voies et se débarrasse de Tiller. La nouvelle version de Helm nécessite la migration des installations existantes afin de les convertir dans un nouveau format de stockage de version.

Werf, pour sa part, a désormais éliminé l'utilisation de Tiller, est passé à la fusion à 3 et en a ajouté beaucoup plus , tout en restant compatible avec les installations existantes sur Helm 2 (aucun script de migration n'est nécessaire). Par conséquent, jusqu'à ce que werf passe à Helm 3, les utilisateurs de werf ne perdent pas les principaux avantages de Helm 3 par rapport à Helm 2 (ils existent également dans werf).

Cependant, le passage de werf à la base de code de Helm 3 est inévitable et se produira dans un avenir proche. Vraisemblablement, ce sera werf 1.1 ou werf 1.2 (pour le moment, la version principale de werf est 1.0; pour plus de détails sur le dispositif de version werf, voir ici ). Pendant ce temps, Helm 3 aura le temps de se stabiliser.

PS


Lisez aussi dans notre blog:

Source: https://habr.com/ru/post/fr476646/


All Articles