Skip to content

Sites Spektrum : fonctionnement CI/CD

En plus des sites StarterKit, Spektrum héberge des sites qui ne reposent pas sur le StarterKit, comme IVV ou Travelise. Ils ont chacun leur propre pipeline, mais suivent des règles de déploiement communes décrites ici.

Règles de déploiement par branche

L'environnement déployé dépend de la branche sur laquelle tu merges :

  • Merge sur staging => déploiement en staging.
  • Merge sur master => déploiement en production.

Le déploiement en production est manuel

Un merge sur master ne part pas tout seul en production. Le job de déploiement prod doit être confirmé manuellement dans GitLab (bouton "play" sur le job). Ça laisse le temps de vérifier le build avant de toucher au site en ligne, et ça évite qu'un simple merge parte en prod par accident.

Plateforme selon la version d'Umbraco

L'hébergement dépend de la version d'Umbraco du site :

  • Anciens sites (Umbraco 6, 7, 8, 9) : hébergés sur Windows (IIS).
  • Nouveaux sites (Umbraco 10+) : hébergés sur Linux, via une image Docker construite par le pipeline.

Image Docker pour les sites Umbraco 10+

Les sites récents (Umbraco 10+, sur .NET moderne) tournent dans un conteneur Linux. Le pipeline construit une image Docker du site à partir d'un Dockerfile multistage, puis la déploie.

Tout le build vit dans le Dockerfile

Particularité de ces sites : tout le build est décrit dans le Dockerfile, pas sur le runner. Le frontend (assets Node/npm), le code C# et la publication finale sont chacun une étape du Dockerfile. On parle de build entièrement conteneurisé (ou build hermétique) : le pipeline n'a qu'à lancer docker build, et c'est le Dockerfile qui orchestre toute la chaîne. L'avantage est que le build est reproductible et indépendant de ce qui est installé sur le runner : il tourne de la même façon en CI et en local.

Le principe du multistage : le Dockerfile est découpé en plusieurs étapes, et seule la dernière constitue l'image finale.

  1. Étape frontend : à partir d'une image Node, on installe les dépendances npm et on build les assets (JS, CSS).
  2. Étape de build C# : à partir d'une image SDK .NET, on restaure les dépendances (restore), on compile et on publie l'application (publish). On y récupère aussi les assets produits par l'étape frontend.
  3. Étape runtime : à partir d'une image ASP.NET runtime, beaucoup plus légère, on copie uniquement le résultat publié des étapes précédentes.

L'image finale ne contient donc que ce qui est nécessaire à l'exécution : ni le SDK, ni Node, ni les sources, ni les paquets intermédiaires. Résultat : une image plus petite, plus rapide à pousser et à démarrer, et avec une surface d'attaque réduite.

Cache des couches au build

Chaque instruction du Dockerfile produit une couche identifiée par un hash (calculé à partir de l'instruction et des fichiers qu'elle copie). Tant que ni l'instruction ni son contenu ne changent, le hash reste le même et Docker peut réutiliser la couche du cache au lieu de la reconstruire.

Pour profiter de ce cache d'un pipeline à l'autre, le job pull l'image existante avant le build et la passe en --cache-from. Docker s'en sert comme base de cache : les étapes inchangées (par exemple le npm install si le package.json n'a pas bougé, ou le restore si les .csproj sont identiques) sont reprises telles quelles, et seules les couches réellement impactées par le commit sont reconstruites. Les builds sont ainsi nettement plus rapides.

Le millefeuille des couches

Une image Docker, c'est un millefeuille : un empilement de couches, chacune posée sur la précédente. Pour réutiliser une couche du cache, Docker vérifie qu'elle et toutes celles en dessous sont identiques (même hash). Dès qu'une couche change, elle et tout ce qui est posé par-dessus doivent être refaits ; ce qui est en dessous, déjà "cuit", est conservé. D'où l'intérêt de mettre en bas ce qui bouge rarement (restauration des dépendances) et plus haut ce qui change souvent (le code).

Exemple concret. Tu corriges uniquement un fichier du frontend (un SCSS, un bout de JS) et tu ne touches pas au backend. Au build suivant :

  • l'étape de build C# n'a pas changé (mêmes .csproj, même code backend) : son hash est identique, Docker reprend la couche du cache sans rien recompiler côté .NET ;
  • seules les couches frontend sont reconstruites, parce que ce sont les seules dont le contenu a bougé.

Tu gagnes tout le temps qu'aurait pris le restore et la compilation du backend, qui sont souvent les étapes les plus lentes. À l'inverse, si tu touches un .csproj ou le code C#, le build backend est refait, mais l'étape frontend reste, elle, servie par le cache si elle n'a pas bougé.

text
Scénario : seul un fichier frontend a changé (package.json inchangé)

  +-------------------------------------------+
  | Étape runtime : ASP.NET + site publié     |  REBUILD  (les assets ont changé)
  +-------------------------------------------+
             ^ copie le publish + les assets
  +-------------------------------------------+
  | Étape build C# : publish                  |  CACHE
  |                  build / compile          |  CACHE
  |                  restore (.csproj)        |  CACHE   <- hash backend identique
  +-------------------------------------------+
  +-------------------------------------------+
  | Étape frontend : build assets (JS/CSS)    |  REBUILD <- le fichier modifié est ici
  |                  npm install              |  CACHE   (package.json inchangé)
  |                  base Node                |  CACHE
  +-------------------------------------------+

  CACHE   = couche reprise telle quelle (hash inchangé)
  REBUILD = couche reconstruite (son contenu, ou une couche en dessous, a changé)

Avantages de l'approche

  • Reproductibilité. Le même Dockerfile produit le même build partout. En cas de souci, on peut reconstruire à l'identique une version donnée pour reproduire et diagnostiquer le problème, sans dépendre de l'état du runner ou d'une machine en particulier. Fini le "ça marche chez moi".
  • Portabilité. L'image embarque tout son nécessaire d'exécution ; elle tourne de la même façon sur n'importe quel hôte équipé de Docker (runner CI, serveur de prod, poste local).
  • Rapidité. Grâce au cache de couches, seules les parties réellement modifiées sont reconstruites (voir le millefeuille ci-dessus).
  • Runner propre. Rien à installer sur le runner (ni Node, ni SDK .NET) : tout l'outillage vit dans les étapes du Dockerfile. Pas de pollution ni de conflits de versions entre projets.
  • Image légère et plus sûre. L'image finale ne garde que le runtime et le site publié, ce qui réduit sa taille et sa surface d'attaque.
  • Rollback simple. Les images sont taggées et conservées : revenir en arrière revient à redéployer une image antérieure, sans rebuild.
  • Cohérence dev / CI / prod. Le même artefact (l'image) traverse toute la chaîne, donc ce qui est testé est exactement ce qui est déployé.

Les fichiers .config ne sont jamais écrasés

Sur ces sites, le déploiement n'écrase pas les fichiers .config déjà présents sur le serveur. Le pipeline met à jour le code, mais laisse en place la configuration propre à l'environnement.

Modifier un fichier de config : à faire à la main sur le serveur

La conséquence est la même que pour les StarterKit (voir StarterKit v2 et v3 : mettre à jour un site) : une modification d'un fichier .config faite dans le code ne part pas au déploiement.

Pour changer une valeur de config (chaîne de connexion, clé d'app, réglage IIS...), il faut l'éditer à la main directement sur le serveur. Modifier uniquement le fichier dans le dépôt et relancer un pipeline ne suffit pas : le serveur gardera l'ancienne valeur.

Contributors

The avatar of contributor named as Clément Favre Clément Favre

Changelog