[TOC]
# L'Approche microservices
## Contexte
Une grosse partie de nos solutions chez `AT Internet` sont hébergées au sein de monolithes avec une approche classique de développement en architecture n-tiers. Cette approche très répandue au début dans les années 90-2000 était considérée comme l'état de l'art dans le développement logiciel, mais n'était pas exempte de critiques :
- L'Architecture monolithique est très difficilement scalable (voir pas du tout)
- Couplage fort entre les différents composants du code métier (librairies partagée, anti-pattern plat de spaghetti).
- Le déploiement en production d'un monolithe se fait souvent avec une interruption de service, à moins d'avoir les moyens nécéssaire pour le déployer en parallèle et de basculer le trafic dessus (blue-green deployment)
- Passé une certaine taille, le monolithe devient difficile à maintenir et à débugger.
- Le couplage fort en fait une architecture peu adaptée au développement agile et aux approches itératives.
- Le monolithe est lié à un et un seul langage (Microsoft.NET chez `AT Internet`)
Avec le virage de l'agilité, nous souhaitons désormais revoir notre architecture technique pour aller vers plus de scalabilté, de maintenabilité, de découplage et de rapidité dans la production de valeur cliente.
## Besoin de scalabitité
Parmis les inconvénients du monolithe évoqués précédemment, celui qu'on souhaite éliminer en priorité pour notre `DataQuery3` est la difficulté de passer à l'échelle.

Le schéma ci-dessus montre que si on souhaite être en capacité de prendre plus de charge, on doit déployer plusieurs monolithes, et ce, même si tous les composants de celui-ci n'ont pas la même consommation de ressources.
**Premier inconvénient :** Le dimensionnement du serveur qui héberge le monolithe doit être adapté à la taille de ce dernier : Plus le monolithe est gros et plus le serveur doit l'être. Donc le coût de l'infrastructure augmente de façon linéaire avec la taille du monolithe.
**Autre inconvénient :** Tout dans le monolithe est "in-process". C'est à dire que lorsqu'une requête arrive sur un service, elle sera entièrement traitée au sein de ce même service. Elle ne pourra pas faire appel à un composant hébergé ailleurs. Ce qui veut dire que si un serveur plante, tout le monolithe qu'il héberge plante et les requêtes en cours sont perdues sauf si un dispositif de retentative ("retry") est mis en place en amont.
### Break the Monolith

Le schéma ci-dessus représente une vision très simplifiée de ce que l'approche microservice nous permet d'obtenir en terme de scalabilité :
**D'une part** : La possibilité d'avoir un parc de machines plus petites car elles ne doivent plus héberger la totalité du monotlithe.
**Et d'autre part** : La possibilité d'avoir une scalabilité plus fine dès lors que chaque microservice est indépendant. C'est pourquoi sur le schéma on ne trouve par exemple qu'une seule instance du `Parser` et 3 instances de `QueryExec` car ce sont 2 services complètement différents en terme de sollicitation et de consommation de ressources.
## Les grands principes de l'architecture microservices
L'approche microservice est une approche technique **ET** organisationnelle, comme l'explique d'ailleurs `Martin Fowler` dans [cet article](https://martinfowler.com/articles/microservices.html#CharacteristicsOfAMicroserviceArchitecture). Parmis les multiples aspects de l'approche, ceux qui nous ont le plus fortement intéressés sont `la gouvernance décentralisée` et la notion de `Component as a service`.
### Une gouvernance décentralisée
Pendant très longtemps chez `AT Internet` nous avions souhaité tout standardiser, et en particulier les frameworks de développements (.Net). Cependant, pour nos nouvelles solutions, les technologies envisagées sortaient du cadre "Microsoft". L'approche microservice nous permet de changer de point de vue et nous dit que _**tous les problèmes ne sont pas "clou" et toutes les solutions ne sont pas "marteau"**_.
L'idée derrière la gouvernance décentralisée c'est de redonner le pouvoir aux équipes de choisir quel outil est le mieux pour réaliser leur travail.
C'est ainsi que nous passons progressivement d'un monolithe écrit en C# à une galaxie de microservices écrits en NodeJS, Python, Go, Java ou encore Scala.
### Component as a Service : Le découplage **fort**
Les architectures microservices pronnent l'approche "`Component As A Service`" grâce à laquelle les équipes de développement peuvent créer des composants métiers et les partager au travers d'API (le plus souvent REST ou GRPC).
Dès lors que les contrats sont stables, les développeurs peuvent assurer le cycle de vie de leurs composants et ajouter de nouvelles fonctionnalités sans avoir d'impact sur les autres équipes. Chaque composant peut être mis à l'échelle et déployé indépendemment.

Le schéma ci-dessus est une illustration de l'anti-pattern `plat de spaghettis` dans laquelle beaucoup de librairies gérées par des équipes différentes sont fortement couplées. Dans un tel environnement la moindre modification peut avoir des conséquences non maîtrisées.
## Conséquence de l'approche microservice : la conteneurisation
Comme dit précédemment, nous sommes passés d'une architecture monolithique et mono-langage à une architecture microservice et multi-langages.
La première conséquence de tout ceci porte sur notre infrastructure et son coût. En effet, si nous avions dû déployer tous nos nouveaux microservices directement sur notre infrastructure de machines virtuelles Amazon, le budget aurait explosé.
#### Pourquoi ?
Le fait d'utiliser plusieurs langages de développement différents (Go, Python, Java, NodeJs ...etc) nous aurait contraint à leur dédier autant de type de VM (en tenant compte des potentielles différentes versions de runtime utilisés) ce qui serait allé à l'encontre de notre besoin de rationnalisation des ressources.
Ajouté à celà la difficulté d'administration et de maintenance d'un parc de machines virtuelles hétérogènes.
#### La solution
La conteneurisation apparaît comme la solution à ce problème de rationnalisation et de densification de l'infrastructure.
Le problème avec une machine virtuelle est la place prise par l'OS par rapport aux applications qu'elle héberge.

Le schéma ci-dessus montre les différentes couches entrant en jeu dans la virtualisation. On retrouve l'OS de la machine physique qui fait tourner un hyperviseur responsable de la gestion des machines virtuelles (partage des ressources, isolations des processus ...Etc).
A chaque fois qu'une VM est créée, un OS est installé (OS VM1 et VM2 sur le schéma). Ces OS ne mutualisent rien et occupent une place considérable sur la machine physique.
De plus, nos application n'ont en général pas besoin de toutes les fonctionnalités d'un OS complet pour tourner. Or, avec une VM nous n'avons pas le choix.

En revanche, dans le cas d'un conteneur comme montré ci-dessus, on va directement partager les ressources de la machine physique sans redéployer un OS pour chaque conteneur.
De plus, l'étanchéïte entre les conteneurs nous autorise à déployer sur la même machine des applications écrite dans des langages différents sans avoir à les installer d'abord.
En conséquence, on arrivera à densifier beaucoup plus, et à réduire les coûts de l'infrastructure (ainsi que l'empreinte carbone), et à réduire également les coûts de maintenance de tout ça.
#### Docker
Nous avons donc choisi `Docker` comme moteur de conteneurisation pour nos microservices. La conteneurisation nous permet de définir exactement ce dont notre application a besoin pour fonctionner. Docker nous permet d'héberger sur des machines **homogènes** des applications **hétérogènes** (web app, daemon, service, etc...) développées dans les technologies **hétérogènes**.
## Conséquence de la conteneurisation : l'orchestration
Créer et déployer un conteneur Docker sur son poste de travail est une chose, mais en déployer des centaines sur un parc de machines distantes, puis assurer leur cycle de vie en est une autre.
Cette tâche porte un nom : l'orchestration.
### L'état du marché

Le marché de l'orchestration sort d'une période de forte concurrence. Beaucoup de solutions s'affrontent, en particulier Docker (avec Swarm) et Kubernetes. Cependant, au debut 2018, beaucoup de signaux montrent que Kubernetes est sur le point de remporter la guerre des orchestrateurs. Notamment avec Amazon qui rejoint le CNCF (Fondation responsable, entre autre, du développement de Kubernetes) et qui, quelques mois plus tard, annonce la sortie d'un nouveau service AWS : EKS (**E**lastic **K**ubernetes **S**ervice).
### Kubernetes: le choix qui s'impose
Beaucoup des acteurs cités plus haut ont soit abandonné (comme CoreOS avec Fleet), soit basculé leurs produits sur kubernetes (comme Rancher ou DC/OS). Nous avons donc aussi fait le choix de `Kubernetes` comme plateforme d'orchestration de nos conteneurs applicatifs, et ce, pour plusieurs raisons :
- **Sa communauté** : A partir de la v1.0 de kubernetes, ce dernier a été reversé au CNCF par Google. Depuis, la communauté qui gravite autour du produit ne cesse de grandir et est très active. On parle même d'un éco-système K8S tant le nombre de produits, d'outils ou de plateformes s'appuyant dessus est impressionnant. Le poids et l'activité de cette communauté K8S ont été déterminants dans son adoption chez AT Internet.
- **Son architecture** : L'architecture interne de Kubernetes nous garantit la stabilité et la résilience que nous recherchons ainsi que tout un tas de fonctionnalités très utiles pour nos microservices (LoadBalancing, Service discovery, DNS, Auto-scaling, resource management, QoS ...etc).
- **Son API** : L'API de Kubernetes est un élément central dans son architecture. Tout passe par elle, et notemment `kubectl`, son outil en ligne de commande (CLI). La richesse de cette API en fait une fonctionnalité indispensable pour l'automatisation des deploiements (CI/CD).
## Automatisation et CI/CD
L'un des avantages de l'approche monolithique c'est le déploiement : En effet, comme son nom l'indique, on a qu'une seule entité à déployer : 1 monolithe = 1 déploiement
Avec l'approche microservice le `déploiement et l'intégration continus` se compliquent car chaque microservice pouvant être déployé indépendemment chaque opération de déploiement ne peut plus se faire manuellement.
De plus, il faut être capable à tout moment de revenir à une version précédente ou simplement de tout remonter en cas de crash du cluster Kubernetes.
Et lorsque votre architecture microservice ressemble à celle du graph ci-dessous, on doit impérativement automatiser.

`Helm-deployer` est un job global Jenkins qui centralise le déploiement des `charts` helm des équipes sur les différents clusters `Kubernetes` de l'entreprise (integration, preproduction, staging, production). Il assure en même temps le fameux principe de SSOT en faisant une copie systématique de ces mêmes charts sur S3 (compte Management) de sorte qu'en cas de crash on puisse facilement remonter rapidement un environnement K8S opérationnel tel qu'il était avant.
Ce job peut-être lancé manuellement depuis `Jenkins` ou bien appelé dpuis un autre job/jenkinsFile.
### Big picture

Comme on le voit sur le schéma ci-dessus, `helm-deployer` va faire 4 choses :
1. Valider que le chart respecte certains critères (cf. *Les validations* ci-après)
2. Deployer le chart Helm dans le dépot `git` officiel d'AT Internet
3. Deployer le binaire dans un bucket S3 dédié.
4. Deployer le chart sur le cluster Kubernetes choisi :
* *integration*
* *preproduction*
* *staging*
* *production*
### Exemple
Vous pouvez trouver à [cette adresse](http://gitlab.intraxiti.com/team-infra/helm-deployer/tree/master/sample-charts/sample-atinternet) un exemple de chart qui reprend tous éléments présentés ci-dessus.
Ce chart `sample-atinternet` contient les 4 fichiers de values (1 par environnement) ainsi qu'un exemple de `Deployment` et d'`Ingress`. Vous pouvez vous en inspirer pour créer les vôtres.
## Exemple de worflow
Le premier exemple de Worflow concerne la livraison continue sur l'environnement de DEV qui ne dépend pas du `Helm-deplyer`, mais uniquement de l'équipe de dev :

Ensuite, une fois que la livraison continue en dev est mise en place, lorsque l'équipe le décide, elle peut préparer son Chart pour monter en intégration. L'exemple de flux suivant illustre ce besoin :

Enfin, lorsqu'on souhaite grimper d'un niveau, il suffit juste d'écraser les fichiers `preproduction.yaml` par `integration.yaml` pour une promotion en preprod, ou `production.yaml` par `preproduction.yaml` pour une promotion en prod.