Déployer des microservices avec des dépendances

Microservices

Le principe de bases des microservices est simple : une application est composée d’un ensemble de services, qui peuvent discuter entre eux.

La règle de base étant que ces microservices puissent avoir un cycle de vie (et de déploiement) autonome.

Si vous devez déployer plusieurs microservices d’un coup, bravo, vous avez un monolithe distribué.

La confusion entre microservice et conteneur

Attention, il n’y a absolument pas de ratio un pour un entre microservice et conteneur. Un service peut être rendu par plusieurs conteneurs, regroupé ou non en pod. L’exemple typique étant le site web et son traitement asynchrone (rails+sidekiq, flask+celery …). Les serveurs de bases de données peuvent être mutualisés, tant qu’on ne touche pas à la sacrosainte isolation des bases.

Versionning

Un service peut consommer un autre service. Les différents microservices sont versionnés, que ce soit avec un hash git tout moche, ou un joli semver.

Même s’il est possible d’appeler un service par son nom, il est quand même sage d’avoir un graphe des différents services : qui consomme qui. C’est l’approche des links de Docker-compose, Kubernetes préférant l’anonymat des Services.

Les services sont des boites, la version du service doit rester privée, ce qui est public (et concerne les autres services), c’est la version de son API.

Du coup, chaque service peut réclamer un autre service avec une version d’API.

Un peu comme de la gestion de bibliothèque, quoi, on retombe sur le pattern bien maitrisé de gem/yarn/dep/pip et compagnie.

Pour chaque service, on peut déterminer sa version d’API, et la liste des services avec les versions minimum attendues. Ces informations peuvent être rangées dans des étiquettes des images (des labels docker, quoi), ce qui permet de retrouver simplement l’information sur un environnement live.

La procédure de déploiement peut débuter par un preflight : on vérifie que le service que l’on souhaite déployer va trouver les services consommés dans les versions attendues.

Intégrer le preflight, à titre informatif, dans son cycle d’intégration continue, est une bonne idée : “attention, là maintenant, votre service n’a pas les prérequis pour être déployé en prod, il va peut être falloir discuter avec l’autre équipe”.

D’un autre côté, présenter une API, mais sans la déployer, ce n’est pas super fairplay non plus, ça sent le refactoring qui a du mal à aboutir.

Déployer à blanc

Pour limiter les embrouilles de chronologie de déploiement pour des histoires de dépendances, chose que l’approche micro services promettait d’éviter, il ne faut pas hésiter à déployer des services à blanc, en déployant des bouts d’API qui ne seront consommés que par des tests fonctionnels. On déploie d’abord un serveur sans clients, puis dans un second temps les clients.

Le service est déployé, testé, un éventuel test de charge pour évaluer son comportement en charge, ou même déployé en fantôme, avec des clients qui l’utilisent, mais sans le dire, soit en doublon d’un service qu’il souhaite remplacer, soit headless, sans que les utilisateurs le voie. Facebook avait fait ce genre de chose pour son tchat.

Lorsque l’on déploie un service, on se réserve la possibilité de faire un rollback en cas de drame. Par contre, attention, quand on déploie une nouvelle API, qu’on la valide incorrectement, puis en déployant le client qui va la consommer, on se rend compte d’un problème : c’est le drame, le seul rollback possible est une cascade de rollback, ce que l’on souhaiter éviter à tout prix.

Pour les services très fortement sollicités, le déploiement se fera de manière progressive, en gardant les yeux rivés sur les performances et les taux d’erreurs. Le déploiement progressif rend physiquement impossible, ou presque le déploiement par lot. Ça tombe bien, c’est ce que l’on souhaite éviter.

Étendre le concept

Cette notion de version d’API peut être étendue aux modèles de données. La modification devra se faire en deux étapes, la première va ajouter des colonnes, sans casser le code actuellement déployé. Ensuite, on pourra déployer le code utilisant le nouveau modèle, et enfin, déploiement des modifications qui vont enlever des colonnes.

Modifier des colonnes va être un peu plus compliqué. Soit le modèle logiciel (l’ORM, à priori) va assurer la transition en calculant les modifications à la volée, puis en tache de fond, de gros batchs assurent une transition complète, sans subir un phénomène de longue traine. Mongodb se prête bien à ce genre d’astuce.

La migration de modèle est un sujet complet, complémentaire du déploiement. Allez jeter un oeil à gh-ost pour avoir une étendu du sujet.

Déprécier

Effacer des trucs, ça veut dire prendre le risque de le regretter plus tard. À grande échelle, on passe du regret à l’angoisse. Du coup, il est tellement plus confortable d’entasser à l’infini, de se proposer des API avec une compatibilité ascendante depuis le premier jour. Dans le même esprit, on ne rajoute pas de contrainte sur des données stockées (dans l’approche de stockage massif à la HDFS). C’est ce que propose Protobuff et Thrift.

Il est quand même possible de connaitre les versions de clients déployés (en interne), et d’avoir des stats sur les clients externes. Avec ces infos, on peut déprécier l’API, forcer la main pour mettre à jour les clients, puis retirer l’ancienne API. Bazarder du code mort, ça fait toujours plaisir, et ça évitera d’avoir des surprises plus tard. Mettre à disposition un proxy qui fera le passe plat entre différentes versions de l’API peut être un palliatif, mais une coupe franche est nettement plus simple.

Automatiser

Il existe des grammaires pour les différentes API distantes. OpenAPI (ex-swagger), grpc et tant d’autres sont basé sur cette approche. Ce contrat doit être le seul lien entre les différents services, le sujet de discussion entre les différentes équipes. Ce contrat va permettre de générer du code, ou le valider, selon les cultures logicielles.

Avec du code bien rangé, il est possible de lancer automatiquement des tests sur les différents clients quand on modifie le serveur. Trivial pour des tests fonctionnels avec des services conteneurisés, envisageable pour des bibliothèques. C’est là que débute le troll sur le monorepo, mais un sujet orthogonal au déploiement de microservices.

blogroll

social