Cours NestJS · Module 9: Déploiement et infrastructure
Production Best Practices - les règles d'un vrai centurion
Dans cette leçon6
En production, le code ne change pas. Ce qui change, c'est qui regarde : personne. L'application tourne la nuit, le dimanche et les jours fériés, et tout ce que tu sais d'elle, ce sont les signaux qu'elle émet sur elle-même. Cette leçon porte sur la façon de les organiser.
Un centurion n'inspectait pas chaque légionnaire en personne. Il disposait de trois sources de savoir : des rapports chiffrés sur l'état des cohortes, des registres de garde sur les incidents isolés, et les dépêches des courriers montrant où un ordre s'était enlisé en chemin. Le même partage vaut encore aujourd'hui.
Les trois piliers de l'observabilité
Les trois piliers de l'observabilité sont metrics, logs et traces - métriques, journaux et traces. Non frontend, backend et base de données, qui sont des couches d'une application. Non CPU, mémoire et disque, qui sont des ressources - une sorte de métrique parmi d'autres. Ni développement, test et production, qui sont des environnements.
Chaque pilier répond à une question différente : aucun ne remplace les autres :
- Les métriques disent combien et à quelle fréquence - requêtes par seconde, temps de réponse, usage mémoire. Elles sont agrégées et peu coûteuses à conserver : tu les gardes des mois durant et tu fondes tes alertes dessus.
- Les logs disent ce qui s'est exactement passé dans un cas précis - avec le texte de l'erreur et l'identifiant de la requête. Ils coûtent cher, car ils sont nombreux, mais eux seuls répondent à la question « pourquoi cette requête-là ».
- Les traces disent où le temps est passé - elles montrent le trajet d'une requête à travers tous les services. Sans elles, avec trois services en chemin, tu sais seulement que la réponse a mis deux secondes ; tu ignores lequel les a consommées.
Une métrique te dit qu'il y a un problème. Un log te dit ce qui a cassé. Une trace te dit où.
Le health check
Un orchestrateur - Kubernetes, Docker Swarm ou un simple équilibreur de charge - doit savoir si l'on peut diriger du trafic vers cette instance. Il le demande par un endpoint de santé :
1@Controller('health')
2export class HealthController {
3 constructor(
4 private health: HealthCheckService,
5 private db: MongooseHealthIndicator,
6 private memory: MemoryHealthIndicator,
7 ) {}
8
9 @Get()
10 @HealthCheck()
11 check() {
12 return this.health.check([
13 () => this.db.pingCheck('database'),
14 () => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024),
15 ]);
16 }
17}Le paquet @nestjs/terminus fournit les pièces toutes faites : HealthCheckService rassemble les résultats et compose la réponse, MongooseHealthIndicator vérifie la connexion à la base, et MemoryHealthIndicator surveille l'usage du tas. Il existe d'autres indicateurs - pour Redis, pour le disque, pour n'importe quel service HTTP externe.
Un health check doit vérifier la disponibilité de la base de données, de Redis et des services dépendants. Non si le code source est à jour - ce n'est pas son rôle. Non le nombre d'utilisateurs connectés - c'est une métrique métier, non un signal de santé. Et certainement pas uniquement si le serveur HTTP répond.
Cette dernière distinction est le cœur de l'affaire. Une application dont la base est tombée répond encore en HTTP - le processus vit, le port est ouvert, le 200 OK revient sans délai. Un health check qui ne vérifie que cela annonce à l'orchestrateur « tout va bien » d'une instance incapable de servir une seule vraie requête. Vérifie les dépendances sans lesquelles l'application ne fera rien de toute façon.
La configuration de la surveillance
Un système de surveillance se met en place en quatre étapes, dans cet ordre :
- Installer l'agent de surveillance - c'est lui qui recueille les données de la machine et de l'application.
- Configurer la collecte des métriques - décider de ce que tu mesures et à quelle fréquence.
- Mettre en place les règles d'alerte - fixer les seuils au-delà desquels quelqu'un doit être prévenu.
- Créer les tableaux de bord de visualisation - des graphiques à consulter une fois qu'on sait que quelque chose se passe.
On inverse souvent cet ordre, et c'est l'erreur la plus fréquente en installant une surveillance. Les tableaux de bord viennent en dernier, car tant que les métriques ne circulent pas, tu ignores ce que tu y dessinerais. Les alertes viennent avant eux, parce qu'une alerte arrive d'elle-même tandis qu'un tableau de bord, il faut le regarder - et à trois heures du matin, personne ne regarde.
Les niveaux d'escalade des alertes
Tout franchissement de seuil ne signifie pas la même chose. Les alertes se rangent en quatre niveaux :
- Warning (80 % de capacité) - une notification. Rien n'est encore cassé, mais tu approches de la limite ; tu as le temps de réagir posément.
- Critical (95 % de capacité) - une réponse urgente. La marge est presque épuisée ; la panne est affaire d'heures ou de minutes.
- Emergency (au-delà de 100 %) - une intervention immédiate. La limite est franchie et les utilisateurs le voient déjà.
- Post-incident - analyse et conclusions. Une fois la situation maîtrisée, tu établis la cause et ce qu'il faut changer pour qu'elle ne revienne pas.
Remarque que les deux premiers seuils se situent en deçà de cent pour cent. C'est délibéré : une alerte à 100 % n'est plus un avertissement mais l'annonce d'une panne. Une surveillance sensée laisse le temps de réagir, elle ne commente pas l'incendie.
Le quatrième niveau est souvent négligé, et c'est le seul qui change quoi que ce soit pour l'avenir. Sans analyse après incident, la même alerte sonnera de nouveau le mois prochain.
L'arrêt en douceur
La dernière règle concerne l'extinction. Lors du déploiement d'une nouvelle version, l'ancienne instance reçoit un signal SIGTERM - et ce qu'elle fera dans les secondes suivantes décide si quelqu'un verra une erreur :
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.enableShutdownHooks();
5
6 await app.listen(3000);
7}enableShutdownHooks() fait intercepter le signal par NestJS qui, avant de fermer, appelle les méthodes onModuleDestroy et beforeApplicationShutdown des modules. L'application a ainsi le temps d'achever les requêtes en cours, de fermer les connexions à la base et de se désinscrire du registre des services.
Sans cela, un déploiement interrompt les requêtes en cours de traitement. À dix déploiements par jour, cela fait dix salves d'erreurs que personne ne rattache au déploiement - car dans les journaux, elles ressemblent à des coupures de connexion fortuites.
Récapitulatif
Le centurion lit des rapports au lieu d'inspecter chaque légionnaire :
- les trois piliers de l'observabilité sont metrics, logs et traces - non frontend/backend/base, non CPU/mémoire/disque, non les environnements,
- une métrique dit qu'il y a un problème, un log ce qui a cassé, une trace où le temps est passé,
- un health check vérifie la disponibilité de la base de données, de Redis et des services dépendants - non la fraîcheur du code, non le nombre d'utilisateurs, et non le seul fait que HTTP réponde,
- une application dont la base est morte renvoie encore un
200- d'où l'inutilité de ne contrôler que HTTP, @nestjs/terminus:HealthCheckServicecompose le résultat,MongooseHealthIndicatorteste la base,MemoryHealthIndicatorle tas,- configuration de la surveillance : installer l'agent → configurer la collecte des métriques → mettre en place les règles d'alerte → créer les tableaux de bord,
- niveaux d'escalade : Warning (80 %) → Critical (95 %) → Emergency (>100 %) → Post-incident,
- des seuils en deçà de 100 % laissent le temps de réagir ; une alerte à 100 % est déjà un constat de panne,
enableShutdownHooks()permet d'achever les requêtes en cours lors de l'arrêt d'une instance.
Ainsi s'achève notre tour des pratiques de production. En attendant, retiens ceci : une application en production ne vaut que par les signaux qu'elle émet sur elle-même - car personne ne devinera ce qui lui arrive.
Code de cette leçon : src/production-best-practices.ts
1// Bonnes pratiques de production - les principes du vrai capitaine
2import { Injectable, Logger } from '@nestjs/common';
3
4// 1. Structured Logging (pas de console.log !)
5@Injectable()
6class ProductionLogger {
7 private readonly logger = new Logger('RomanAPI');
8
9 logRequest(method: string, url: string, duration: number) {
10 this.logger.log(JSON.stringify({
11 type: 'request',
12 method,
13 url,
14 duration,
15 timestamp: new Date().toISOString(),
16 }));
17 }
18
19 logError(error: Error, context?: string) {
20 this.logger.error(JSON.stringify({
21 type: 'error',
22 message: error.message,
23 stack: error.stack,
24 context,
25 timestamp: new Date().toISOString(),
26 }));
27 }
28}
29
30// 2. Error Handling - filtre global des erreurs
31// @Catch()
32// class GlobalExceptionFilter implements ExceptionFilter {
33// catch(exception: any, host: ArgumentsHost) {
34// const ctx = host.switchToHttp();
35// const response = ctx.getResponse();
36// const status = exception.getStatus?.() || 500;
37//
38// // NE PAS exposer les erreurs internes !
39// response.status(status).json({
40// statusCode: status,
41// message: status === 500
42// ? 'Internal Server Error'
43// : exception.message,
44// timestamp: new Date().toISOString(),
45// });
46// }
47// }
48
49// 3. Security Checklist
50const securityConfig = {
51 helmet: true, // Sécuriser les en-têtes HTTP
52 cors: {
53 origin: ['https://roman-empire.com'],
54 credentials: true,
55 },
56 rateLimit: {
57 ttl: 60,
58 limit: 100,
59 },
60 validation: {
61 whitelist: true,
62 forbidNonWhitelisted: true,
63 },
64 // JAMAIS en production :
65 // - synchronize: true (base de données)
66 // - debug: true
67 // - console.log with sensitive data
68 // - hardcoded secrets
69};
70
71// 4. Graceful Shutdown
72// app.enableShutdownHooks();
73// @Injectable()
74// class CleanupService implements OnModuleDestroy {
75// async onModuleDestroy() {
76// // Close database connections
77// // Close Redis connections
78// // Fermer les streams ouverts
79// console.log('Graceful shutdown complete');
80// }
81// }
82
83// 5. Monitoring Endpoints
84const monitoringEndpoints = {
85 '/health': 'Health check de base',
86 '/health/ready': 'Readiness to handle traffic',
87 '/health/live': 'Is the application alive',
88 '/metrics': 'Métriques Prometheus',
89};
90Tu 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. Les trois piliers de l'observabilité sont :
2. Un endpoint de health check en production devrait vérifier :
Tâches pratiques dans le jeu
- Éditeur de code
Crée un HealthController qui vérifie la base de données et la mémoire
- Clique dans l'ordre
Ordonne les étapes de configuration d'un système de surveillance :
- Tri vertical
Ordonne les niveaux d'escalade des alertes de surveillance :