Projet · Phase 7.4

IaC (4/6) — Terraform en pratique : VM & cloud-init

On passe au concret : créer une vraie VM sur Proxmox à partir d'un template cloud-init, tout paramétré par des variables, avec des outputs. Le code réutilisable pour toute une flotte — ici comme sur n'importe quel cloud.

⏱ ~2 hNiveau avancéPrérequis : IaC (3/6)Pratique · TP
L’idée reçue

« Terraform crée une VM, donc elle est prête à l’emploi. »

Terraform crée la coquille : CPU, RAM, disque, réseau. Mais une VM vide n’a ni utilisateur, ni clé SSH, ni IP configurée. C’est le rôle de cloud-init — le standard d’initialisation au premier démarrage. Terraform + cloud-init = une VM qui naît déjà accessible. Et le tout, paramétré pour être reproductible à l’infini.

À la fin de ce module, tu sauras…

  • Comprendre le rôle d’un template cloud-init
  • Écrire une ressource VM paramétrée par des variables
  • Injecter utilisateur, clé SSH et IP via cloud-init
  • Récupérer des informations avec des outputs, et industrialiser avec count/for_each

01Le template cloud-init

Expérience 1

Préparer une image « clonable »

Sur l’hôte Proxmox, on prépare une fois un template basé sur une image cloud Debian (avec cloud-init intégré). Idée générale :

Manipule
wget https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
qm create 9000 --name debian13-cloud --memory 2048 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci
qm set 9000 --scsi0 local-lvm:0,import-from=/chemin/debian-13-genericcloud-amd64.qcow2
qm set 9000 --ide2 local-lvm:cloudinit --boot order=scsi0 --serial0 socket --vga serial0
qm template 9000
Le VM 9000 devient un template : une image de référence, prête à être clonée. Terraform partira de là pour créer des VMs identiques et reproductibles.
Comprends pourquoi un template, et cloud-init

Une image cloud embarque cloud-init : au premier boot, elle lit une configuration (utilisateur, clés SSH, réseau, paquets) et s’auto-configure. C’est le standard d’initialisation des VMs dans tout le cloud (AWS l’appelle « user data », Azure « custom data » — même principe). Le template, lui, garantit que chaque VM part d’une base identique. Terraform clone le template et fournit la config cloud-init : la VM naît prête.

02Les variables : décrire une VM générique

Expérience 2

Paramétrer plutôt que coder en dur

Dans variables.tf, ajoute de quoi décrire une VM sans rien figer :

Manipule
variable "vm_name"    { type = string }
variable "vm_cores"   { type = number, default = 2 }
variable "vm_memory"  { type = number, default = 2048 }
variable "vm_ip"      { type = string }
variable "target_node" { type = string, default = "pve" }
variable "ssh_public_key" { type = string }
Comprends pourquoi tout paramétrer

En externalisant nom, CPU, RAM, IP en variables, le même fichier décrit n’importe quelle VM : il suffit de changer les valeurs. C’est la base de la réutilisation — une seule définition, des dizaines de machines. Le code décrit un gabarit, les variables le spécialisent. Principe identique sur tout provider.

03La ressource VM

Expérience 3

Cloner le template, injecter cloud-init

Dans un fichier vm.tf :

Manipule
resource "proxmox_virtual_environment_vm" "soria_vm" {
name      = var.vm_name
node_name = var.target_node

clone {
  vm_id = 9000
}

cpu    { cores = var.vm_cores }
memory { dedicated = var.vm_memory }

initialization {
  ip_config {
    ipv4 {
      address = "${var.vm_ip}/24"
      gateway = "192.168.10.1"
    }
  }
  user_account {
    username = "soria"
    keys     = [var.ssh_public_key]
  }
}

network_device {
  bridge = "vmbr1"
}
}
Comprends chaque bloc, et le lien cloud-init

clone part du template 9000. cpu/memory dimensionnent. Le bloc initialization = la config cloud-init : ip_config (IP statique sur ton LAN), user_account (utilisateur + clé SSH). network_device raccorde la VM au bon bridge (ton réseau interne de la Phase 2). Résultat : une VM clonée, avec IP fixe, accessible en SSH par ta clé — dès le premier boot.

04Les outputs : récupérer l’information

Expérience 4

Faire ressortir ce dont tu as besoin

Dans outputs.tf :

Manipule
output "vm_id" {
value = proxmox_virtual_environment_vm.soria_vm.vm_id
}
output "vm_ssh" {
value = "ssh soria@${var.vm_ip}"
}
Manipule
terraform apply -var vm_name="test-tf" -var vm_ip="192.168.10.50" -var ssh_public_key="$(cat ~/.ssh/id_ed25519.pub)"
Observe
Apply complete! Resources: 1 added.

Outputs:
vm_id  = 100
vm_ssh = "ssh soria@192.168.10.50"
La VM est créée sur Proxmox, et Terraform t’affiche directement les infos utiles. Teste : ssh soria@192.168.10.50 — tu entres, grâce à la clé injectée par cloud-init.
Comprends à quoi servent les outputs

Les outputs exposent des valeurs calculées après création (ID, IP, URL…). Utile pour toi (récupérer une info), mais surtout pour chaîner : ces valeurs peuvent alimenter un autre outil — par exemple générer l’inventaire Ansible (module 7.6). L’output est le point de sortie structuré de Terraform.

05Industrialiser : plusieurs VMs

Expérience 5

De une VM à une flotte, avec for_each

La vraie puissance : décrire plusieurs machines sans dupliquer le code. Exemple de définition d’un parc :

Manipule
variable "vms" {
type = map(object({ ip = string, cores = number, memory = number }))
default = {
  "worker-1" = { ip = "192.168.10.61", cores = 2, memory = 4096 }
  "worker-2" = { ip = "192.168.10.62", cores = 2, memory = 4096 }
}
}

Puis for_each sur la ressource :

Manipule
resource "proxmox_virtual_environment_vm" "fleet" {
for_each  = var.vms
name      = each.key
node_name = var.target_node
clone { vm_id = 9000 }
cpu    { cores = each.value.cores }
memory { dedicated = each.value.memory }
# ... initialization avec each.value.ip ...
}
Observe
Un terraform plan montrerait 2 VMs à créer d’un coup. Ajoute une entrée à la map → une VM de plus. Supprime-la → Terraform détruit la bonne. Toute une flotte, décrite en quelques lignes.
Comprends count vs for_each

count crée N copies indexées (0,1,2…) — simple mais fragile si l’ordre change. for_each crée une ressource par clé d’une map/set — chaque VM a une identité stable (son nom), idéale pour un parc nommé. C’est ainsi qu’on gère des dizaines de machines sans copier-coller : la description reste courte, le state suit chaque instance. Concept transposable à tout provider.

Ce que tu retiens

Terraform crée la VM ; cloud-init l’initialise (utilisateur, clé SSH, IP) dès le premier boot, à partir d’un template. Tout est paramétré par variables (un gabarit réutilisable), les outputs exposent les infos utiles, et for_each industrialise une flotte entière. Tu sais désormais provisionner de l’infrastructure reproductible — le geste est le même sur Proxmox, AWS ou Azure.

Mini-projet

Provisionne, et vois plus grand

À faire

  1. Prépare (ou vérifie) un template cloud-init Debian 13 sur Proxmox.
  2. Écris la ressource VM paramétrée + variables + outputs.
  3. Crée une VM, prouve l’accès SSH par la clé injectée (cloud-init a fonctionné).
  4. Passe à for_each : décris 2 VMs, applique, puis ajoute-en une 3e en une ligne.
  5. Fais un destroy ciblé et observe le plan.
  6. Défi (indépendance du provider) : sur un cloud gratuit de ton choix (beaucoup offrent un palier gratuit), écris un petit Terraform qui déploie une VM avec le provider correspondant (ex. aws_instance, azurerm_linux_virtual_machine, ou une autre plateforme). Constate que init/plan/apply et la logique de state sont identiques — seuls le provider et les noms de ressources changent.
Fil rouge — Terraform a bâti les machines. Mais elles sont presque vides : ni Docker, ni K3s, ni config. Il faut maintenant les équiper, de façon reproductible. C’est le rôle d’Ansible — au module suivant.

?Auto-évaluation

1. Que fait cloud-init, et pourquoi Terraform seul ne suffit pas pour une VM prête ?

Terraform crée la coquille (CPU/RAM/disque/réseau) ; cloud-init l’initialise au premier boot (utilisateur, clé SSH, IP). Sans cloud-init, la VM serait vide et inaccessible.

2. À quoi servent les outputs ?

À exposer des valeurs calculées après création (ID, IP…), utiles pour l’utilisateur ou pour chaîner vers un autre outil (ex. inventaire Ansible).

3. Pourquoi for_each plutôt que du copier-coller pour une flotte ?

Une seule définition crée une ressource par entrée, chacune avec une identité stable. On gère des dizaines de VMs sans dupliquer le code, et le state suit chaque instance.

Connecte-toi pour enregistrer ta progression.

Suite → IaC (5/6) — Ansible : inventaire, playbook & idempotence