Cours NestJS · Module 9: Déploiement et infrastructure

CI/CD - la forge automatique des légions

6 min de lecture
Dans cette leçon6

Un déploiement manuel se présente toujours de la même façon : quelqu'un se connecte au serveur, récupère le code, lance la construction, redémarre l'application. Cela fonctionne - tant que ce quelqu'un se rappelle toutes les étapes, les accomplit dans le même ordre et ne déploie pas un vendredi à dix-sept heures.

Une forge légionnaire ne reposait pas sur la mémoire du forgeron. Elle avait un ordre établi : le même acier, la même température, la même trempe, la même marque sur chaque lame. C'est pourquoi une épée forgée en Gaule entrait dans un fourreau fabriqué à Rome. Le CI/CD, c'est cet ordre consigné dans un fichier.

Ce que signifie ce sigle

CI/CD signifie Continuous Integration / Continuous Deployment - intégration continue et déploiement continu. Non « Container », non « Component », non « Code » : les deux mots se rapportent au processus, non à ce qui est traité.

Chaque moitié résout un problème distinct. L'intégration continue, c'est la construction et le test automatiques du code après chaque changement - afin qu'un défaut se révèle en quelques minutes, et non une semaine plus tard, quand plus personne ne se souvient de quel changement l'a introduit. Le déploiement continu, c'est la mise en production automatique d'un changement ayant passé la vérification - afin que le chemin du code achevé jusqu'à la production ne dépende pas de la disponibilité de quelqu'un.

GitHub Actions

Les outils sont nombreux ; nous nous en tiendrons à un seul. GitHub Actions automatisent le build, les tests et le déploiement après chaque push et chaque pull request (PR) - c'est là tout leur rôle.

Trois malentendus valent d'être écartés d'emblée. Ils ne servent pas qu'à gérer les issues - c'est une fonction distincte de GitHub. Ils ne sont pas réservés aux dépôts privés ; les dépôts publics en usent gratuitement et sans limite de minutes. Et ils ne remplacent pas Docker - au contraire, c'est dans une étape du workflow que tu appelles docker build et docker push : l'un se sert de l'autre.

La configuration est un fichier YAML placé dans le répertoire .github/workflows/ :

1# .github/workflows/deploy.yml
2name: Deploy
3
4on:
5  push:
6    branches:
7      - main

Le déclencheur, ou quand cela doit s'exécuter

Le fragment ci-dessus est le déclencheur - la mention de l'événement qui lance le workflow. Il s'écrit en quatre imbrications, toujours dans cet ordre : on: ouvre la section des événements, push: nomme l'événement précis, branches: le restreint à certaines branches, et - main est un élément de la liste de ces branches.

En YAML, l'indentation porte le sens - chaque niveau se décale de deux espaces vers la droite, et c'est elle qui bâtit la structure. Le tiret devant main marque un élément de liste : les branches suivantes s'ajoutent aux lignes d'en dessous.

Il existe plus d'un événement. pull_request: lancera les vérifications à chaque PR - et cela vaut d'ordinaire mieux que push, car on attrape ainsi un défaut avant que le changement n'atteigne la branche principale.

Les quatre étapes du pipeline

Le travail proprement dit se déroule dans un job, et ses étapes ont un ordre établi :

1jobs:
2  build:
3    runs-on: ubuntu-latest
4    steps:
5      - uses: actions/checkout@v4
6
7      - uses: actions/setup-node@v4
8        with:
9          node-version: 20
10
11      - run: npm ci
12      - run: npm test
13      - run: npm run build
14      - run: docker push registry.imperium.rome/legion-api:latest

L'ordre des étapes n'est pas affaire de goût : installer les dépendances (npm ci) → exécuter le linter et les tests (npm test) → construire l'application (npm run build) → déployer en production (docker push).

Un principe le gouverne : plus une étape est bon marché, plus tôt elle vient. Un linter s'achève en quelques secondes, les tests en quelques minutes, la construction d'une image en un quart d'heure. Si la construction précédait les tests, la moindre faute de frappe coûterait le temps complet d'un build - pour finir en échec malgré tout.

Les deux premières étapes relèvent de la préparation, non du pipeline. actions/checkout récupère le code du dépôt sur la machine où le workflow s'exécute - sans elle, le répertoire est vide. actions/setup-node installe la version indiquée de Node.js et tranche par là même la querelle du « chez moi ça marche » : la version est inscrite dans le fichier, et chaque exécution reçoit donc la même.

Remarque npm ci plutôt que npm install. Le premier installe exactement ce que consigne package-lock.json et s'arrête en cas de désaccord avec package.json. Le second peut relever silencieusement la version d'une dépendance - et la CI teste alors autre chose que ce que tu as en local.

Quand une étape échoue

Les étapes s'exécutent l'une après l'autre, et le premier échec arrête les suivantes. C'est le comportement par défaut, et le bon : si les tests ne passent pas, construire une image et l'envoyer vers un registre ne ferait que perdre du temps, et au pire déploierait une version cassée.

D'où un partage pratique. Lance les vérifications - installation, linter, tests - à chaque push et à chaque PR, sur toutes les branches. Restreins le déploiement à la branche principale, par le branches: - main de la section précédente. Le travail sur une fonctionnalité est ainsi vérifié dès son premier commit, tandis que rien n'atteint la production avant la fusion du changement.

Récapitulatif

La forge trempe chaque lame de la même façon :

  • CI/CD signifie Continuous Integration / Continuous Deployment - non Container, non Component, non Code,
  • la CI est la construction et le test automatiques après chaque changement, le CD la mise en production automatique de ce qui a passé la vérification,
  • GitHub Actions automatisent le build, les tests et le déploiement après chaque push et PR - ils ne servent pas qu'aux issues, ne sont pas réservés aux dépôts privés et ne remplacent pas Docker,
  • la configuration réside dans .github/workflows/ et forme un fichier YAML dont l'indentation bâtit la structure,
  • la syntaxe du déclencheur : on: → push: → branches: → - main ; pull_request: vérifie un changement avant sa fusion,
  • l'ordre des étapes : npm ci → npm test → npm run build → docker push, du moins coûteux au plus coûteux,
  • actions/checkout récupère le code, actions/setup-node fixe une version de Node.js identique à chaque exécution,
  • npm ci installe exactement ce que contient package-lock.json ; npm install peut changer les versions en silence,
  • la première étape en échec arrête les autres - d'où des vérifications partout et un déploiement depuis la seule branche principale.

Nous reviendrons au CI/CD dans le module consacré à l'infrastructure, avec les builds sur plusieurs versions à la fois, les artefacts et la mise en cache des dépendances. En attendant, retiens ceci : un pipeline n'existe pas pour déployer plus vite. Il existe pour déployer de la même façon - et la vitesse vient de surcroît.

Code de cette leçon : .github/workflows/ci.yml
1# Pipeline CI/CD - le chantier naval automatique de l'Empire
2
3name: Roman Empire CI/CD
4
5on:
6  push:
7    branches: [main, develop]
8  pull_request:
9    branches: [main]
10
11# Environment variables for the entire workflow
12env:
13  NODE_VERSION: '18'
14  REGISTRY: ghcr.io
15
16jobs:
17  # === JOB 1: Tests ===
18  test:
19    name: Run Tests
20    runs-on: ubuntu-latest
21
22    steps:
23      - name: Checkout code
24        uses: actions/checkout@v4
25
26      - name: Setup Node.js
27        uses: actions/setup-node@v4
28        with:
29          node-version: '18'
30          cache: 'npm'
31
32      - name: Install dependencies
33        run: npm ci
34
35      - name: Run linter
36        run: npm run lint
37
38      - name: Run unit tests with coverage
39        run: npm run test:cov
40
41      - name: Run E2E tests
42        run: npm run test:e2e
43
44  # === JOB 2: Build ===
45  build:
46    name: Build Application
47    needs: test
48    runs-on: ubuntu-latest
49
50    steps:
51      - uses: actions/checkout@v4
52
53      - name: Build Docker image
54        run: docker build -t roman-api:latest .
55
56      - name: Run container health check
57        run: |
58          docker run -d --name test-api -p 3000:3000 roman-api:latest
59          sleep 10
60          curl -f http://localhost:3000/health || exit 1
61          docker stop test-api
62
63  # === JOB 3: Deploy ===
64  deploy:
65    name: Deploy to Production
66    needs: build
67    runs-on: ubuntu-latest
68    if: github.ref == 'refs/heads/main'
69
70    steps:
71      - uses: actions/checkout@v4
72
73      - name: Deploy
74        run: echo "Deploying Roman Empire API..."
75
76      - name: Verify health
77        run: echo "Checking health endpoint..."
78
79# CI = Continuous Integration (tests à chaque push)
80# CD = Continuous Deployment (déploiement automatique après merge)
81

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. CI/CD signifie :

  2. 2. GitHub Actions dans le contexte du CI/CD :

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée .github/workflows/deploy.yml avec les étapes : install, lint, test, build, deploy

  • Tri vertical

    Ordonne les étapes du pipeline CI/CD, de la première à la dernière :

  • Tri vertical

    Ordonne la syntaxe du déclencheur d'un workflow GitHub Actions :