Infrastructure de pipeline CI/CD sur des serveurs dédiés

Infrastructure de pipeline CI/CD sur des serveurs dédiés

Les services CI partagés ralentissent au pire moment possible. GitHub Actions met les tâches en file d'attente quand il y a un pic d'utilisation simultanée. Les runners partagés de GitLab expirent sur les builds qui prennent plus d'une heure. Les niveaux payants s'additionnent vite : GitHub Actions facture 0,008 $ par minute pour les runners Linux, ce qui veut dire qu'un build de 20 minutes exécuté 50 fois par jour coûte 240 $ par mois, sans compter les suites de tests parallèles plus importantes.

Un serveur dédié qui tourne sous Jenkins ou des runners GitLab auto-hébergés, ça change complètement la donne. Un coût mensuel fixe. Pas de facturation à la minute. Pas d'attente pendant les heures de pointe. Et avec le stockage NVMe, la mise en cache de la couche Docker et la lecture des artefacts de test, tout va super vite, ce qui fait une différence notable sur la durée totale du pipeline.

Pourquoi une infrastructure CI dédiée est une bonne idée à grande échelle

Le problème de la file d'attente

Les services CI partagés fonctionnent avec une file d'attente équitable. Quand ton équipe envoie 15 commits en même temps avant une sortie, ces tâches se mettent en file d'attente les unes derrière les autres. Sur un serveur dédié à 16 cœurs qui tourne sous Jenkins avec 16 exécuteurs en parallèle, les 15 tâches démarrent en même temps. Le temps réel entre le dernier commit et le résultat final de la compilation diminue vraiment.

Cette compression est super importante pendant les cycles de révision du code, où le temps d'attente des développeurs a un impact direct sur le nombre de révisions qui peuvent être faites chaque jour. Les équipes qui réduisent le temps d'attente CI de 15 à 3 minutes voient souvent leur débit PR augmenter et leurs cycles de fusion s'accélérer.

Facturation à la minute ou coût fixe

Une équipe d'ingénieurs de taille moyenne qui fait 200 builds par jour, chacun prenant en moyenne 15 minutes, accumule 3 000 minutes de build par jour. Avec le tarif GitHub Actions de 0,008 $ par minute pour Linux, ça fait 24 $ par jour, soit environ 720 $ par mois. Sur le niveau Premium de GitLab avec des minutes de runner supplémentaires achetées, les coûts sont similaires.

Un serveur dédié InMotion Hosting à 99,99 $ par mois fait les mêmes 3 000 minutes de compilation par jour avec de la marge. Au niveau Advanced, le test en parallèle sur 64 Go de RAM gère des suites de tests complètes sans pression sur la mémoire.

Jenkins : Topologie maître/agent sur du matériel dédié

Configuration à serveur unique

Pour les équipes qui font moins de 500 builds par jour, un seul serveur dédié qui gère à la fois le maître Jenkins et les agents de build, c'est pratique. Le processus maître Jenkins est léger : il s'occupe de la planification, de la gestion des plugins et de l'interface utilisateur web. Les agents de build font le boulot.

Configurez Jenkins avec 12 à 14 exécuteurs de build sur un serveur à 16 cœurs, en gardant 2 cœurs pour le processus maître et la charge du système d'exploitation. Chaque exécuteur fait tourner un job de build. Avec 14 exécuteurs en parallèle, une file d'attente de 14 jobs se vide en un temps équivalent à celui nécessaire pour faire tourner un seul job.

Multi-serveur pour les grandes équipes

Quand le volume de build dépasse les 500 tâches par jour, ou quand il faut isoler différents environnements de build (Python 3.9 vs 3.12, différentes versions du démon Docker), une topologie maître-agent sur plusieurs serveurs dédiés permet une séparation plus claire. Le maître Jenkins tourne sur un serveur plus petit (le niveau Essential suffit). Les agents de build tournent sur des serveurs plus puissants, adaptés aux besoins de la charge de travail.

Les serveurs agents se connectent au maître via SSH ou JNLP. Ça permet d'augmenter la capacité petit à petit : un deuxième serveur dédié avancé double le débit de compilation sans avoir à reconfigurer le maître.

GitLab Runners sur du matériel dédié

Les runners auto-hébergés GitLab s'enregistrent auprès d'une instance GitLab (gitlab.com ou auto-hébergée) et exécutent des tâches de pipeline. Chaque processus runner peut gérer une tâche à la fois ; l'exécution de plusieurs processus runners sur un serveur à 16 cœurs permet d'obtenir un parallélisme.

La configuration du runner GitLab pour un parallélisme max sur un serveur Extreme :

  • concurrent = 16 ( dans /etc/gitlab-runner/config.toml ; définit le nombre max de tâches en parallèle pour tous les runners enregistrés)
  • exécuteur = docker ( l'exécuteur Docker met chaque tâche dans un nouveau conteneur, ce qui empêche les problèmes de fuite d'état entre les tâches)
  • pull_policy = if-not-present ( utilise les images Docker mises en cache localement plutôt que de les récupérer à chaque tâche ; super important pour les performances NVMe )

Avec l'exécuteur Docker et la mise en cache des images locales sur NVMe, les exécutions suivantes du même pipeline sautent complètement l'étape de récupération des images. Une image Python 3.12 qui prend 45 secondes à récupérer depuis Docker Hub s'exécute depuis NVMe local en moins de 2 secondes.

NVMe : là où les performances de l'infrastructure informatique s'améliorent le plus

Mise en cache des couches Docker

Les builds Docker sont organisés en couches. Quand un build change juste le code de l'appli mais pas les dépendances, Docker réutilise les couches mises en cache pour les étapes d'installation des dépendances. Ce cache se trouve sur le stockage local du runner. Sur SSD SATA, lire une couche mise en cache de 2 Go prend environ 4 secondes. Sur NVMe 5 Go/s, la même lecture prend moins d'une demi-seconde.

Pour une build qui exécute 20 tâches de pipeline par jour, chacune utilisant des couches Docker mises en cache, la différence représente plusieurs minutes gagnées chaque jour. Pour une équipe de 20 développeurs, c'est pas rien.

Stockage des artefacts de test

Les suites de tests génèrent pas mal de fichiers : rapports de couverture, captures d'écran des tests de navigateur, binaires compilés, fichiers XML de résultats de tests. Sur un serveur CI super chargé, des centaines de fichiers sont écrits par heure sur le stockage. NVMe cette charge d'écriture sans que les attentes d'E/S s'accumulent dans les journaux de compilation.

Configurez Jenkins ou GitLab pour stocker les artefacts sur le NVMe local pendant la compilation, puis téléchargez les artefacts finaux vers un stockage d'objets ou un référentiel partagé une fois le pipeline terminé. Cette approche en deux étapes permet de conserver la rapidité de la compilation tout en préservant les artefacts au-delà de la capacité locale du serveur.

Parallélisation des tests et espace de travail temporaire

Les frameworks de test modernes répartissent les tests sur plusieurs processus. pytest-xdist, le drapeau –maxWorkers de Jest et le gem parallel_tests de RSpec écrivent tous des fichiers temporaires sur le stockage local pendant l'exécution parallèle des tests. Sur NVMe, 16 testeurs parallèles écrivant simultanément des fichiers temporaires ne créent pas de contention d'E/S. Sur SSD SATA SSD un stockage réseau, c'est souvent le cas.

Configure les répertoires temporaires de test pour qu'ils pointent directement vers le NVMe :nvme pour les exécuteurs de tests basés sur un shell, ou configure le répertoire temporaire spécifique au framework. C'est un changement simple qui élimine une cause fréquente d'échecs des tests parallèles.

Mettre en place des stratégies de mise en cache

Caches de dépendance

L'étape la plus coûteuse dans la plupart des pipelines d'intégration continue, c'est l'installation des dépendances : npm install, pip install, résolution des dépendances Maven. Ces étapes récupèrent les paquets sur Internet et les enregistrent dans des répertoires cache locaux.

  • npm: mettez en cache node_modules et le répertoire cache npm entre les compilations en utilisant la fonctionnalité stash de Jenkins ou la clé cache de GitLab.
  • pip: Mettre en cache le cache de téléchargement pip (~/.cache/pip) et utiliser –find-links pour servir à partir NVMe local.
  • Maven: mets en cache ~/.m2/repository entre les compilations pour éviter de redescendre les dépendances JAR.
  • Gradle: met en cache ~/.gradle entre les compilations ; le cache de compilation de Gradle stocke aussi les résultats des tâches.

Sur un serveur dédié avec NVMe , ces caches restent naturellement entre les tâches. Le problème avec les services CI partagés, c'est que les caches doivent être téléchargés à chaque fois, ce qui ajoute des frais. Sur ton propre serveur, le cache est toujours local.

Environnements de test automatisés

Docker Compose pour les tests d'intégration

Les tests d'intégration ont souvent besoin de services externes : bases de données, files d'attente de messages, API fictives. Docker Compose lance ces dépendances de service à chaque exécution de test. Sur un serveur dédié avec 192 Go de RAM et 16 cœurs, l'exécution de PostgreSQL, Redis et d'un serveur API fictif dans Docker parallèlement à la suite de tests réelle n'ajoute qu'une surcharge minimale.

Configure Docker Compose pour utiliser des volumes nommés soutenus par le NVMe pour les données de service. PostgreSQL un volume nommé sur NVMe une base de données de test en moins d'une seconde, contre 5 à 8 secondes sur un stockage plus lent.

Test du navigateur

Les tests Playwright et Cypress sont super gourmands en ressources : chaque contexte de navigateur utilise entre 200 et 400 Mo de RAM et pas mal de temps CPU pour le rendu. Sur un CI runner partagé, les tests de navigateur plantent souvent ou donnent des résultats bizarres à cause du manque de mémoire. Sur un serveur dédié avec 192 Go de RAM qui fait tourner 8 testeurs de navigateur en parallèle, chaque testeur a plein de mémoire et il n'y a pas de pression externe sur l'allocation des ressources.

Comparaison : CI partagé vs serveur dédié

ConfigurationTravaux en parallèleAttente en file d'attenteLimite de minutes de construction
Équipe GitHub Actions (minutes illimitées)20 en même tempsOui, pendant les périodes de pointeLimité à grande échelle
GitLab Premium + minutes de runner en plusVariableOuais, des coureurs partagés2 000 minutes incluses
InMotion Essential + Jenkins14 en même tempsAucunSans limite de temps
InMotion Advanced + Jenkins16 en même tempsAucunSans limite de temps
InMotion Extreme + coureurs GitLab16 en même tempsAucunSans limite de temps

Choisir le bon niveau InMotion pour CI/CD

  • Aspire : petites équipes de moins de 50 builds par jour, validation de pipeline de base. Limité à 4-6 exécuteurs en parallèle.
  • Indispensable : équipes faisant entre 50 et 200 compilations par jour. 64 Go de RAM, ça gère bien les compilations Docker avec des caches de dépendances.
  • Avancé : les équipes qui font entre 200 et 500 builds par jour ou des tests d'intégration complets qui ont besoin de gros conteneurs de services.
  • Extrême : les boîtes d'ingénierie qui font plus de 500 compilations par jour, des tests intensifs en parallèle sur navigateur ou l'entraînement de modèles ML dans le cadre de pipelines CI.

Pour commencer

La plupart des équipes se rendent compte dès le premier cycle de facturation que leurs dépenses CI à la minute dépassaient le coût d'un serveur dédié. L'amélioration des performances en termes de temps de compilation est souvent tout aussi importante : les pipelines qui prenaient 20 minutes sur une infrastructure partagée s'exécutent généralement en 4 à 6 minutes sur du matériel dédié NVMe avec une véritable exécution parallèle.

Partager cet article

Laisser une réponse

Ton adresse e-mail ne sera pas publiée. Les champs obligatoires sont marqués *