L’usage, les subtilités, et le maintien en vie de la forge
L’ouverture de la forge s’est passée en deux temps : une première étape avec quelques projets sur lesquels les équipes étaient motivées et compétentes pour tester et debugger les premiers problèmes, puis une seconde où l’on a rapatrié tous les projets depuis Gitlab.com.
Le build
Durant cette première phase, nous avons pu tester avec succès le déploiement des pages (et le certificats wildcard de let’s encrypt), les build gitlab CI : pour rappel nous utilisons les runners au sein de Kubernetes. Ces runners sont donc des containers Docker. Le premier point (mais vous l’aviez peut-être remarqué dans la configuration des runners du précédent article) est l’image de base si l’utilisateur ne la spécifie pas.
En effet, sur Gitlab.com, cette image de base est « ruby:2.5 », alors que dans la configuration par défaut des runners de la chart Helm c’en est une autre. Nous avons pris la décision de tenter de nous rapprocher autant que possible de la configuration Gitlab.com et avons donc changé cette image par défaut pour « ruby:2.5 »
Ensuite, un autre point complexe et dont la solution actuelle ne nous convient pas totalement : le build d’image Docker. L’utilisation de la commande Docker au sein d’un container possède plusieurs contraintes : montage du socket du démon Docker, ainsi que faire tourner le container en mode « privileged ». Ceci ne nous convenait pas du tout, principalement pour toutes les problématiques de sécurité que cela peut poser : un container avec des accès privilégiés peut faire n’importe quoi au niveau de la machine hôte, et donc une personne mal intentionnée pourrait détruire un nœud du cluster Kubernetes ou encore accéder aux autres containers.
Deux solutions alternatives sont possibles : création d’instance de runner dédié, hors du cluster Kubernetes et avec une durée de vie très courte (potentiellement un seul job), mais nous ne sommes pas encore assez matures pour cela. L’autre solution consiste à l’utilisation de Kaniko, produit de Google permettant de construire des images Docker sans besoin d’un démon Docker. Cette solution fonctionne bien et s’intègre aisément dans un build CI. Mais possède tout de même certaines lacunes qui font que nous ne sommes pas totalement satisfaits :
- Le build est globalement plus long, et dans certains cas extrêmement longs (build Angular avec tous les petits fichiers issus d’un node_modules par exemple).
- L’usage mémoire du build est très élevé et dans certains cas (toujours Angular par exemple), la consommation mémoire peut facilement atteindre plusieurs gigas.
Cette solution étant tout de même fonctionnelle, elle nous permet de patienter en attendant de trouver mieux. Nous explorons actuellement des solutions avec Podman, mais nous ne les jugeons pas encore suffisamment transparentes en termes d’impact sur le cluster Kubernetes pour être mises en place.
Le déploiement projets
Enfin, la partie déploiement : une volonté autour de cet écosystème est aussi de permettre de déployer facilement des environnements de test afin de pouvoir montrer et partager nos prototypes directement avec le client.
Pour y parvenir tout en conservant un niveau de sécurité correspondant à nos exigences, nous sommes partis sur le respect de plusieurs bonnes pratiques :
- Un namespace par binôme projet-environnement (par exemple projet1-staging, projet1-test, projet2-test, ...)
- Un service account (hormis les admin et système) ne peut avoir accès à deux namespaces
- Une isolation totale des namespaces entre eux (communication inter namespace impossible)
- Un seul Ingress controller et certCmanager pour l’intégralité du cluster
- Des limites de ressource fortes sur les namespaces (ResourceQuota) et donc la mise en place de « default limit/request » pour l’ensemble des pods (LimitRange)
- Des accès restreints aux types d’API fourni par Kubernetes
- L’interdiction de monter des PVC et des LoadBalancer
Certaines de ces contraintes peuvent être fortes et sont bien entendu désactivables avec le bon argumentaire au cas par cas. Cela permet aussi d’accompagner les projets dans une philosophie container : a-t-on vraiment besoin de stockage à tel endroit ? A-t-on réellement besoin de CPU/RAM pour un NginX ? Comment optimiser la répartition des ressources allouées… ?
La création du namespace, des règles de sécurité, des service account et de tout le nécessaire à un projet est bien entendu scripté afin de faciliter la création mais aussi permettre de les maintenir dans le temps.
La montée en charge
Nous avons maintenant un écosystème prêt (et surtout documenté !) pour permettre à tout le monde de se connecter, de travailler, de builder et de déployer nos différents projets. Il est donc enfin venu le temps de réaliser la montée en charge : rapatrier les projets Gitlab.com et faire de cette forge la « norme » pour nos projets.
Gitlab propose d’importer automatiquement des projets depuis Gitlab.com, mais (il y a toujours un mais !) cette solution fait l’impasse sur certaines features tels que le Wiki.
Nous avons donc écrit un petit script pour référencer les 200 projets sur Gitlab.com et vérifier lesquels utilisaient ces features : ouf il n’y en avait que quelques-uns. Nous avons donc utilisé l’import automatique pour tous les autres projets, puis sommes passés par un cycle d’export/import classique pour les quelques autres, afin d’éviter toute perte de données/informations.
Certains jobs CI ont dû être adaptés pour remplacer Docker build par Kaniko, faire évoluer les URLs des registry Docker (ou autre Package registry), mais au global, cela a été très fluide et permet un usage beaucoup plus intensif de l’intégration continue, mais aussi du déploiement d’environnement (notamment de tests) de manière automatique et aisément exposable au client.