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:
Une telle approche peut sembler commode à première vue. Cependant, il y a des problèmes:
- C'est difficile à automatiser .
- Comment refléter la configuration dans Git? Comment revoir les changements survenus dans un cluster?
- Comment assurer la reproductibilité de la configuration au redémarrage?
- ...
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: