Cours NestJS · Module 12: Conteneurs et CI/CD

Infrastructure as Code avec Terraform

6 min de lecture
Dans cette leçon5

Le serveur de production a été mis en place il y a un an, en cliquant dans la console du fournisseur. Il fonctionne. Mais personne ne se rappelle quelle taille de machine a été choisie au juste, quels ports ont été ouverts, ni si le disque fait 50 ou 100 gigaoctets. Monter un environnement de test identique représente désormais quelques heures de devinettes - et il sortira quand même différent.

Les Romains ne bâtissaient pas les aqueducs de mémoire. Chacun commençait par un plan architectural : un dessin à partir duquel n'importe quelle équipe pouvait reproduire la même construction dans une autre province. L'Infrastructure as Code est la même idée : une infrastructure définie dans du code, non par des clics.

Ne la confonds pas avec des outils au nom voisin mais au rôle tout autre : ce n'est pas un framework de test d'infrastructure, pas un système de surveillance de serveurs, pas un outil de compression d'images Docker. C'est une façon de décrire ce qui doit exister.

Le plan dans un fichier

Terraform lit des fichiers portant l'extension .tf. Une description se compose de quelques sortes de blocs :

1provider "aws" {
2  region = "eu-central-1"
3}
4
5variable "app_name" {
6  type    = string
7  default = "legion-api"
8}
9
10variable "app_port" {
11  type    = number
12  default = 3000
13}
14
15resource "aws_instance" "legion_server" {
16  ami           = "ami-0abcdef1234567890"
17  instance_type = "t3.micro"
18
19  tags = {
20    Name = var.app_name
21  }
22}
23
24output "server_ip" {
25  value = aws_instance.legion_server.public_ip
26}

provider dit chez qui nous bâtissons - ici AWS dans la région eu-central-1. variable est un paramètre du plan ; grâce à lui, la même description peut monter un environnement de test et un de production ne différant que par les valeurs. resource décrit une chose qui doit exister - une machine, un réseau, une base. output désigne ce que Terraform doit afficher à la fin ; une adresse IP ne se connaît d'ordinaire qu'une fois le serveur créé.

Note le fonctionnement des références : var.app_name va chercher une variable, et aws_instance.legion_server.public_ip un champ d'une ressource créée. De ces références, Terraform déduit lui-même l'ordre de création : puisque l'output a besoin de l'adresse de la machine, la machine doit venir d'abord.

Quatre commandes

Travailler avec Terraform, c'est toujours la même séquence :

  1. terraform init - initialise le projet ; il télécharge les providers nommés dans les fichiers.
  2. terraform plan - un aperçu des changements sans les appliquer.
  3. terraform apply - applique les changements.
  4. terraform output - vérifie les résultats.

La deuxième étape est le cœur de l'outil, alors nommons-la franchement : terraform plan est un DRY RUN. Il compare ta description à l'état réel et énumère ce qu'il compte créer, modifier et supprimer - sans rien toucher. Il ne crée pas de ressources, ne supprime pas d'infrastructure et ne télécharge pas de providers ; le téléchargement, c'est le rôle d'init.

Cet aperçu est le seul moment où tu vois l'intention de l'outil avant qu'elle ne devienne un fait. Lis-le - surtout les lignes commençant par un moins, car Terraform peut juger qu'un changement est impossible sur place et supprimer une ressource pour la recréer. Sur une machine de test, c'est un désagrément ; sur une base contenant des données, un désastre.

Un plan écrit dans un fichier

En équipe et en CI, on emploie une variante en deux temps, pour être sûr de déployer exactement ce qu'on a relu :

1terraform init
2terraform plan -out=tfplan
3terraform apply tfplan
4terraform output

-out=tfplan écrit le plan calculé dans un fichier. apply tfplan applique exactement ce plan au lieu d'en calculer un nouveau.

La différence compte quand quelques minutes séparent la relecture de l'approbation et que quelqu'un modifie l'infrastructure entre-temps. Un apply ordinaire recalculerait le plan et ferait autre chose que ce que tu as lu à l'écran.

L'état qu'il ne faut pas committer

Terraform doit retenir ce qu'il a déjà créé - sinon chaque exécution rebâtirait tout de zéro. Il le consigne dans un fichier nommé terraform.tfstate.

Ce fichier n'a pas sa place dans un dépôt Git, et ce pour deux raisons à la fois. D'abord, il contient des données sensibles - mots de passe de bases, clés, adresses - écrites en clair, car Terraform doit s'en souvenir d'une exécution à l'autre. Ensuite, Git n'offre aucun verrouillage pour le travail en équipe : quand deux personnes lancent apply en même temps, deux versions de l'état apparaissent et un conflit surgit qu'aucune fusion ne peut trancher, car ce n'est pas un fichier destiné à la lecture humaine.

Les raisons qu'on entend parfois - que le fichier serait trop gros pour Git, ou que Terraform le supprimerait après apply - sont tout simplement fausses.

La réponse est un backend distant : un emplacement partagé qui détient l'état et le verrouille pendant la durée d'une opération.

1terraform {
2  backend "s3" {
3    bucket         = "imperium-terraform-state"
4    key            = "legion-api/terraform.tfstate"
5    region         = "eu-central-1"
6    dynamodb_table = "terraform-locks"
7  }
8}

La table nommée dans dynamodb_table fournit précisément le verrou - une deuxième personne lançant apply verra un message disant que l'état est occupé, au lieu de casser l'infrastructure commune.

Récapitulatif

Le plan architectural est prêt et les provinces sont reproductibles :

  • l'Infrastructure as Code est l'approche où l'infrastructure est définie dans du code - ni framework de test, ni surveillance, ni compression d'images,
  • les fichiers .tf contiennent des blocs : provider (chez qui), variable (paramètres), resource (ce qui doit exister), output (ce qu'il faut afficher à la fin),
  • Terraform déduit lui-même l'ordre de création, à partir des références entre ressources,
  • quatre étapes : terraform init → terraform plan → terraform apply → terraform output,
  • terraform plan est un DRY RUN - il montre un aperçu des changements sans les appliquer ; il ne crée rien, ne supprime rien et ne télécharge aucun provider,
  • lis le plan, surtout les suppressions - Terraform peut recréer une ressource au lieu de la modifier sur place,
  • en équipe : plan -out=tfplan, puis apply tfplan - tu déploies exactement ce que tu as relu,
  • terraform.tfstate reste hors de Git : il contient des données sensibles et n'offre aucun verrouillage pour le travail en équipe,
  • la réponse est un backend distant qui verrouille l'état pendant l'opération.

Dans la prochaine leçon, tu rassembleras tout ce module en un projet - conteneurisation, pipeline et déploiement. En attendant, retiens ceci : cliquer dans une console crée un serveur que personne ne sait reproduire ; un plan dans un fichier en crée un que tout le monde peut reproduire.

Code de cette leçon : src/terraform-iac.ts
1// Infrastructure as Code - Plans architecturaux de l'Empire
2console.log("=== TERRAFORM - INFRASTRUCTURE AS CODE ===\n");
3
4interface TerraformCommand {
5  command: string;
6  description: string;
7  analogy: string;
8}
9
10const commands: TerraformCommand[] = [
11  {
12    command: 'terraform init',
13    description: 'Initialisation - télécharger les providers et modules',
14    analogy: 'Rassembler les matériaux de construction',
15  },
16  {
17    command: 'terraform plan',
18    description: 'Aperçu des changements (DRY RUN)',
19    analogy: 'Examiner les plans architecturaux',
20  },
21  {
22    command: 'terraform apply',
23    description: 'Appliquer les changements à l\'infrastructure',
24    analogy: 'Commencer la construction',
25  },
26  {
27    command: 'terraform destroy',
28    description: 'Suppression de toute l\'infrastructure',
29    analogy: 'Démolition du bâtiment',
30  },
31];
32
33console.log("Commandes Terraform :\n");
34commands.forEach((c, i) => {
35  console.log(`${i + 1}. ${c.command}`);
36  console.log(`   Description : ${c.description}`);
37  console.log(`   Analogie : ${c.analogy}\n`);
38});
39
40// Comparaison manuel vs IaC
41console.log("=== MANUEL vs IaC ===\n");
42const comparison = {
43  'Reproductibilité': ['Chaque environnement différent', 'Environnements identiques'],
44  'Historique des changements': ['Aucun', 'Historique complet dans Git'],
45  'Code review': ['Aucune', 'L\'équipe vérifie l\'infrastructure'],
46  'Disaster recovery': ['Jours', 'Minutes'],
47};
48
49console.log("Critère          | Manuel               | IaC (Terraform)");
50console.log("-".repeat(65));
51Object.entries(comparison).forEach(([key, [manual, iac]]) => {
52  console.log(`${key.padEnd(17)}| ${manual.padEnd(20)}| ${iac}`);
53});
54
55// Ressources clés
56console.log("\n=== RESSOURCES TERRAFORM POUR NestJS ===\n");
57const resources = [
58  'aws_instance - serveur EC2 pour l\'API',
59  'aws_docdb_cluster - MongoDB (DocumentDB)',
60  'aws_security_group - pare-feu',
61  'aws_s3_bucket - backend de state',
62];
63resources.forEach(r => console.log(`  - ${r}`));
64

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. Qu'est-ce que l'Infrastructure as Code (IaC) ?

  2. 2. Que fait la commande « terraform plan » ?

Voici 2 des 8 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Définissez le provider AWS (région eu-central-1), les variables (environment, app_name, app_port), la ressource aws_instance

  • Tri vertical

    Ordonnez les étapes du workflow Terraform du premier au dernier :

  • Clique dans l'ordre

    Ordonnez les éléments du workflow terraform plan + apply dans le bon ordre :

  • Éditeur de code

    Mettez done=true pour tous les éléments requis de la checklist : Docker, CI/CD, Kubernetes, Observabilité

  • Tri vertical

    Ordonnez les éléments d'une définition de ressource EC2 dans Terraform (de l'extérieur vers l'intérieur) :

  • Tri vertical

    Ordonnez les étapes de déploiement de l'application du début à la fin :

  • Clique dans l'ordre

    Ordonnez les éléments de configuration du healthcheck dans Docker :

  • Tri horizontal

    Ordonnez les éléments de l'instruction de copie depuis l'étape builder :

  • Tri vertical

    Ordonnez les fournisseurs de secrets par priorité (de la plus élevée) :

  • Clique dans l'ordre

    Ordonnez les éléments de la commande de déploiement d'une ressource Kubernetes :

  • Tri horizontal

    Ordonnez les éléments de la commande pour mettre à l'échelle un Deployment à 5 répliques :