Nos actualités
Gérer plus de 200 instances Jenkins comme une seule : c’est possible avec FluxCD
Dans cet article, nous vous proposons de découvrir une partie du design de la software factory de Bpifrance, en particulier la gestion de Jenkins à grande échelle pour l'ensemble des applications d'une DSI hétérogène en termes de langage de programmation, qualité de code, modernité du design et aussi d'expérience des équipes. Nous aborderons comment nous maintenons une plateforme Jenkins robuste et flexible, adaptée à des besoins divers, tout en garantissant une utilité maximale pour le client final.
1. Why Jenkins ? it's so 2010 !
Grand débat, parfois sérieux, parfois trollesque et surtout plein de partis pris, parlons-en.
Une DSI est forcément hétérogène (+900 développeurs et +300 applications), il va donc falloir disposer d'une solution "couteau suisse" que l'on peut hautement personnaliser.
Jenkins est open-source et propose un mécanisme d'extension relativement simple via des plugins, il n'est donc pas obligatoire de fournir un patch upstream ou de maintenir un fork.
Autre argument, la gouvernance autour de Jenkins est stable et l'historique montre que c'est un projet fiable et éprouvé.
Son usage très répandu permet aussi d'être rassuré sur la partie sécurité, qui est cruciale pour nous et notre contexte.
Même si le choix de Jenkins semble dater de 2010, il reste pertinent pour répondre à nos besoins et c'est pourquoi il est offert en standard aux équipes de développement Bpifrance.
2. Small is beautiful
Avant toute chose, revenons sur le choix de disposer d'une instance controller de Jenkins par équipe plutôt qu'une grosse instance mutualisée.
Une grosse instance est plus facile à surveiller et appréhender, c'est d'ailleurs pour cela que la plupart de ceux qui débutent choisissent ce format, et reconnaissons-le, jusqu’à un certain usage c'est un setup tout à fait correct. C'était d'ailleurs le setup Bpifrance jusqu'en 2020.
Toutefois, les problèmes arrivent avec le temps…
2.a Vertical Limit
En premier lieu, le plus évident : la scalabilité verticale n'est pas infinie. Un système monolithique conçu pour 100 utilisateurs pourra-t-il passer à 2000 ? (Rappel utile ici : en version communautaire, Jenkins controller ne propose pas de mécanisme de répartition de charge sur plusieurs instances).
Le cloud peut amener une flexibilité sur ce point avec les larges gammes de machines disponibles en quelques clics, mais toujours au prix d'un coût financier conséquent.
Une JVM consommant énormément de RAM sera toujours plus lente qu'une petite sur les opérations de démarrage et GC par exemple.
2.b Nobody can hurt me without my permission
Second point : la gestion des droits (RBAC). Faire en sorte que chaque équipe n'accède qu'à ses propres jobs, ses propres secrets, ses propres logs d'exécution devient de plus en plus complexe avec l'arrivée de nouvelles équipes.
Bonus : dans un milieu réglementé comme la banque, en plus de gérer correctement les droits, il faut être capable de prouver que les permissions sont respectées et qu'il n'y a aucun trou dans la matrice de droits.
2.c Success is not permanent and failure is not fatal
Troisième point : le Single Point Of Failure (SPOF). Une seule grosse instance, c'est la promesse d'impacter tout le monde (humains et robots compris) au moindre problème (disponibilité, fonctionnalité, performance…). Il faut donc viser une stabilité maximale.
Et pourtant, il va falloir faire régulièrement des maintenances sinon c'est aller vers des ennuis d'une autre catégorie ! Il faut garder à l'esprit que le rythme de mise à jour des projets open-source comme Jenkins est soutenu : minimum une LTS toutes les 12 semaines. Ce qui implique des opérations de patching tout aussi fréquentes. À ces mises à jour planifiées du core, il faut ajouter les patchs de sécurité, les plugins et les mises à jour de l'infrastructure et des composants sous-jacents (a minima kernel, paquet système et JVM).
2.d Noisy neighbour
Sur une instance mutualisée, si une équipe a un usage déraisonnable ou commet une erreur, elle va impacter les autres équipes. Deux exemples ultra-fréquent : saturation de la file d'attente des jobs et saturation des espaces disques.
Deux problèmes faciles à résoudre pour un admin (purge file d'attente et purge des vieux jobs par exemple) mais qui provoque des effets collatéraux sur les équipes qui n'avaient rien demandé.
2.e Design Design Design
Avec des instances plus petites on résout ou mitige les points ci-dessus.
On passe de facto sur un scaling horizontale qui est plus facilement soutenable.
Chacune des instances n'est accédée que par un sous-ensemble des utilisateurs ce qui simplifie drastiquement la gestion des droits au sein de chaque instance.
Les instances étant réparties sur plusieurs machines, il est possible de faire les upgrades système en mode canary. Idem pour le déploiement des patchs logiciel. Bonus si une équipe se retrouve face à une incompatibilité (la plupart du temps une évolution de plugin) il est possible de la roll-back unitairement sur l'ancienne version le temps de préparer un fix ou une évolution.
3. Pet vs Cattle
Pour les raisons citées avant, notre choix de design est donc un controller jenkins par application. On entend par application un nombre indéfinis de composants qui répond à un besoin métier. Ce qui fait donc dans notre contexte une flotte d'environ 250 Jenkins à gérer. Pour prendre en charge un tel volume on va avoir besoin d'infrastructure, d'automatisation, mais aussi d'un fonctionnement le plus simple possible.
3.a Socle
L'infrastructure, qui ne sera pas détaillée dans cet article, est un cluster Kubernetes qui fonctionne sur OS minimaliste (linux kernel +containerd pour faire court).
Le choix a été fait d'avoir des composants containerisés pour être reproductible, le plus immutable possible et offrir une belle couche d'abstraction.
3.b KISS
Le fonctionnement le plus simple qu'on a adopté c'est GitOps. On veut que le contenu de notre production soit ce qui est décrit dans notre repo git. Pour chaque jenkins on a quelques fichier YAML qui décrivent l'application à installer (notamment la version) et les configurations à appliquer.
Pour implémenter cela on a utilisé FluxCD et HelmRelease. En quelques mots FluxCD qui se déploie sur le cluster kubernetes cible fait la synchronisation Git <=> Cluster Kubernetes et HelmRelease se charge de déployer effectivement l'application via Helm. Petit raffinement lorsque l'on fait un commit sur git un webhook est configuré pour déclencher la synchronisation fluxCD.
L'avantage de FluxCD c'est que l'outil fonctionne avec ce que l'on appelle du flux tiré (pull pattern). Dans notre contexte l'avantage est que l'information de déploiement est directement coté cluster kubernetes. C'est le cluster qui vient chercher sa configuration et nous pas un composant équipé de droit d'admin qui vient pousser la configuration. Ceci donne un atout sur la partie sécurité car on fait disparaitre un compte technique a haut privilège (le fameux ci_user) sur l'infrastructure de production. Cela offre aussi une souplesse dans le déploiement si l'on veut monter un autre cluster (actif passif par exemple) il suffit de le configurer à l'identique, inutile de venir toucher la configuration de la CICD.
3.c Automate
Ainsi notre problématique de gérer 250 instances est transformée en gérer quelques centaines de fichiers de configuration yaml dans un repository git.
Et ça c'est tout de suite beaucoup plus facile, un peu de ansible, de template Jinja et ligne de Bash permettent de générer des fichiers de configuration qui permettent des montées de versions, des corrections, des améliorations le tout à l'échelle.
Effet bonus, comme nous utilisons git, il est facile de remonter l'historique des déploiements et/ou de revert un changement, on a en quelque sorte une piste d'audit gratuite.
Exemple réel une mise à jour casse le fonctionnement d'un jenkins pour corriger inutile de se connecter sur un bastion d'administration ou autre. Un commit pour revert le dernier changement et on revient à l'état initial, la procédure opérationnelle de rollback est extrêmement simplifiée.
4. Lesson learned
En utilisant GitOps et de l'automatisation il n'est pas très compliqué de gérer plusieurs Jenkins comme un seul et cela nous a permis de répondre aux critères fonctionnels de taux de disponibilité et cloisonnement des accès.
Quelques métriques sur l'usage de la CI Bpifrance :
- 260 controllers jenkins en production (le premier est né fin 2020)
- Environ 900 développeurs actifs
- +30k build par semaine
- Le top des agents utilisés : Java, ansible et python
Et des chiffres sur la partie plateforme CI elle-même :
- 1h30 d'exécution de script pour préparer et déployer une mise à jour sur l'ensemble du parc
- Entre 2 et 4 mises en production (renouvellement complet du parc) par mois
- Environ 80 CPU et 100 Go de RAM utilisé
5. It's not enough
Pour gérer une telle flotte d'asset, il a quelques problématiques non adressées dans cet article qu'il faut prendre en compte.
Etant donné que Jenkins est un composant qui ne fait pas de HA en version communautaire, le temps de (re)démarrage est critique pour maximiser le uptime.
La démultiplication des instances a aussi un impact sur la consommation des machines et donc un impact finops qu'il faut traiter.
Idem, des centaines d'instances donnent des centaines de route DNS à gérer heureusement quelques outils existent pour nous sortir de ce problème...
Ces points feront l'objet d'un prochain article à paraitre ! Stay tuned !
Technologies associées