ℹ️ À savoir · En bref

Terraform (HashiCorp) est un outil d'Infrastructure as Code déclaratif : on décrit en HCL l'infrastructure voulue (VM, réseaux, groupes Entra, catalogues Citrix…), Terraform compare avec l'existant puis appelle les API pour combler l'écart.

Il mémorise ce qu'il gère dans un fichier d'état (*state*). Sans state, pas de Terraform : c'est la pièce à protéger.

Licence BSL depuis la v1.6 (2023) ; OpenTofu est le fork open source compatible (commande tofu).

Objectifs de la fiche

  • Maîtriser le cycle init → plan → apply et savoir lire un plan.
  • Comprendre providers, ressources, data sources, variables et outputs.
  • Savoir pourquoi et comment stocker le state à distance.
  • Structurer du code réutilisable (modules, for_each) et éviter les erreurs courantes.

Le vocabulaire en 1 tableau

Bloc / termeRôleExemple
providerPlugin qui traduit le HCL en appels API d'une plateforme.hashicorp/azurerm
resourceUn objet géré (créé / modifié / détruit) par Terraform.azurerm_resource_group
dataLecture d'un objet existant non géré par ce code.data.azurerm_subnet
variableParamètre d'entrée, typé et validable.var.env
localsValeurs calculées internes (évite la répétition).local.tags
outputValeur exposée après apply (ou à un module parent).ID d'un réseau
moduleDossier de .tf réutilisable, appelé avec des variables.module "vnet"
stateCorrespondance code ↔ objets réels (IDs, attributs).terraform.tfstate
backendOù est stocké le state (+ verrouillage).Blob Azure, S3, HCP

Le workflow

Écrire → init → plan → apply ; le state fait le lien entre code et réalité
Écrire → init → plan → apply ; le state fait le lien entre code et réalité
ÉtapeCe qui se passeBon réflexe
terraform initTélécharge providers et modules, configure le backend, crée .terraform.lock.hcl.À relancer après tout changement de provider/backend.
terraform fmt / validateFormatage canonique ; contrôle syntaxique et de cohérence.Dans un hook pre-commit ou la CI.
terraform planRafraîchit l'état réel, calcule les différences, n'écrit rien.-out=tfplan pour figer ce qui sera appliqué.
terraform applyExécute le plan (graphe parallélisé), met à jour le state.apply tfplan = exactement le plan relu.
terraform destroyDétruit tout ce que gère cette configuration.Réservé au lab ; prevent_destroy en prod.

Lire un plan sans se tromper

SymboleActionRisque
+create : nouvelle ressourceFaible
~update in-place : modification sans recréationFaible à moyen
-/+replace : destruction puis recréation (« forces replacement »)ÉLEVÉ : perte de données / d'IP / de VM
-destroy : suppressionÉLEVÉ
<=read : lecture d'une data source pendant l'applyAucun

⚠️ Piège

Le plan affiche en bas Plan: 2 to add, 1 to change, 1 to destroy. Lis toujours la ligne « to destroy » et cherche # forces replacement : un simple renommage de ressource peut déclencher la recréation d'une VM.

Architecture

Le cœur Terraform construit un graphe ; chaque provider parle à son API ; le state est partagé via un backend
Le cœur Terraform construit un graphe ; chaque provider parle à son API ; le state est partagé via un backend

Un exemple complet commenté

versions.tf / providers.tf

terraform {
  required_version = ">= 1.6"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"            # accepte 4.x, refuse 5.0
    }
  }
  backend "azurerm" {               # state distant + verrou (lease)
    resource_group_name  = "rg-tfstate"
    storage_account_name = "sttfstatecontoso"
    container_name       = "tfstate"
    key                  = "lab-citrix.tfstate"
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id   # obligatoire en v4
}

main.tf

locals {
  tags = { owner = "alice", managed_by = "terraform", env = var.env }
}

resource "azurerm_resource_group" "citrix" {
  name     = "rg-${var.env}-citrix"
  location = var.location
  tags     = local.tags
}

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-${var.env}-citrix"
  address_space       = ["10.20.0.0/16"]
  location            = azurerm_resource_group.citrix.location   # référence
  resource_group_name = azurerm_resource_group.citrix.name       # = dépendance implicite
}

resource "azurerm_subnet" "snet" {
  for_each             = { vda = "10.20.1.0/24", infra = "10.20.2.0/24" }
  name                 = "snet-${each.key}"
  resource_group_name  = azurerm_resource_group.citrix.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = [each.value]
}

variables.tf / outputs.tf

variable "env" {
  type        = string
  description = "Environnement cible"
  validation {
    condition     = contains(["dev", "prd"], var.env)
    error_message = "env doit valoir dev ou prd."
  }
}
variable "location" {
  type    = string
  default = "francecentral"
}
variable "subscription_id" {
  type = string
}

output "subnet_ids" {
  value = { for k, s in azurerm_subnet.snet : k => s.id }
}

ℹ️ À savoir · Comment Terraform ordonne le travail

Dépendances implicites : dès qu'une ressource référence l'attribut d'une autre (azurerm_resource_group.citrix.name), Terraform sait qu'il doit créer la seconde d'abord. depends_on n'est utile que pour une dépendance invisible dans le code.

Les valeurs des variables viennent d'un fichier *.tfvars, de -var, ou de variables d'environnement TF_VAR_nom.

Le state : la pièce critique

  • Il contient les IDs réels et tous les attributs, secrets compris, en clair → à protéger comme un coffre.
  • Stocké à distance (Blob Azure, S3, HCP Terraform) pour être partagé, versionné et verrouillé : deux apply simultanés sont refusés.
  • Jamais dans Git, jamais modifié à la main. On le manipule avec les commandes terraform state ….
  • Drift : une modification faite dans le portail hors Terraform apparaît au prochain plan, qui proposera de la défaire.
BesoinSolution
Reprendre sous Terraform une ressource créée à la mainBloc import { to = … id = … } puis plan (ou terraform import)
Renommer une ressource sans la recréerBloc moved { from = … to = … }
Ne plus gérer une ressource sans la détruireBloc removed { from = … } ou terraform state rm
Lister / inspecter ce qui est géréterraform state list, terraform state show <adresse>

Méta-arguments à connaître

Méta-argumentUsageRemarque
countN copies identiquesIndexé 0..N : supprimer un élément décale les suivants
for_eachUne instance par clé d'une map / d'un setPréféré : clés stables, pas de recréation en cascade
depends_onForcer un ordre invisible dans le codeÀ utiliser avec parcimonie
lifecycleprevent_destroy, ignore_changes, create_before_destroyProtège la prod, tolère des champs gérés ailleurs
providerChoisir un alias de provider (autre souscription / région)provider = azurerm.hub

Modules

Un module est simplement un dossier de fichiers .tf. On l'appelle depuis la racine en lui passant des variables, et on récupère ses outputs :

appel d'un module

module "vnet_citrix" {
  source  = "./modules/vnet"        # ou registre : "Azure/avm-res-network-virtualnetwork/azurerm"
  env     = var.env
  cidr    = "10.20.0.0/16"
}

# ailleurs, dans une ressource : utilisation d'un output du module
#   subnet_id = module.vnet_citrix.subnet_ids["vda"]

Terraform ou Ansible ?

CritèreTerraformAnsible
Rôle principalProvisionner l'infrastructure (cycle de vie des objets)Configurer l'intérieur des machines
ApprocheDéclaratif, graphe calculéDéclaratif par module, exécution séquentielle
MémoireState : sait ce qu'il a créé, peut détruirePas d'état : vérifie à chaque passage
LangageHCLYAML + Jinja2
Destruction propreNative (destroy)À écrire soi-même (state: absent)

Ils sont complémentaires : Terraform crée VM, réseaux et objets Citrix, puis Ansible configure les OS (voir ANS-01).

Commandes essentielles

CommandeÀ quoi ça sert
terraform init -upgradeInitialiser / mettre à jour les providers dans les contraintes
terraform fmt -recursiveFormater tout le projet
terraform validateVérifier la configuration
terraform plan -out=tfplanCalculer et enregistrer le plan
terraform apply tfplanAppliquer exactement le plan enregistré
terraform plan -destroyPrévisualiser une destruction
terraform output -jsonLire les sorties (scripts, CI)
terraform state list / showInspecter le state
terraform consoleTester une expression HCL en interactif
terraform providers lock -platform=windows_amd64 -platform=linux_amd64Figer les empreintes multi-OS dans le lock file

Pièges classiques

SymptômeCauseParade
« Error acquiring the state lock »Un autre apply tourne, ou un run a plantéAttendre ; sinon terraform force-unlock <ID> en connaissance de cause
Ressources recréées après suppression d'un élément de listecount : les index se décalentfor_each sur une map
Le plan veut annuler un changement fait dans le portailDriftReporter le changement dans le code, ou ignore_changes
Comportement différent sur un autre posteVersion de provider non figéeContrainte ~> + commiter .terraform.lock.hcl
Secret exposéState local ou commité, outputs non marquésBackend chiffré + RBAC ; sensitive = true
« Cycle » dans le grapheDeux ressources se référencent mutuellementSéparer en ressource d'association / revoir le découpage

🔷 En contexte Citrix

Le provider officiel citrix/citrix pilote Citrix DaaS et CVAD on-premises récents : zones, connexions d'hébergement (citrix_azure_hypervisor, vSphere…), catalogues MCS (citrix_machine_catalog) et groupes de mise à disposition (citrix_delivery_group).

Chaîne type : Terraform crée réseau + VM « master » → Ansible prépare l'image → Terraform crée le catalogue MCS à partir du snapshot et le delivery group.

Bénéfice : un environnement de recette reconstruit à l'identique, et chaque changement de prod passe par un plan relu.

À retenir

  • On décrit l'état voulu ; plan montre l'écart, apply le comble.
  • Le state est la mémoire de Terraform : distant, verrouillé, chiffré, jamais dans Git.
  • Toujours lire « to destroy » et « forces replacement » avant d'appliquer.
  • for_each plutôt que count ; versions de providers figées + lock file commité.
  • Terraform provisionne, Ansible configure.

Auto-évaluation

Q1. Que fait terraform plan exactement ?

→ Il rafraîchit l'état réel via les API, le compare au code et au state, puis affiche les actions nécessaires — sans rien modifier.

Q2. Pourquoi stocker le state dans un backend distant ?

→ Pour le partager entre personnes / CI, le verrouiller pendant un apply, le versionner et le protéger (il contient des secrets).

Q3. Tu as 3 VM créées avec count = 3 et tu supprimes la 1re de la liste. Que se passe-t-il ?

→ Les index se décalent : Terraform veut modifier/recréer les VM suivantes. Avec for_each sur des clés stables, seule la VM retirée serait détruite.

Q4. Comment renommer azurerm_subnet.a en azurerm_subnet.vda sans recréer le sous-réseau ?

→ Avec un bloc moved { from = azurerm_subnet.a to = azurerm_subnet.vda }.

Pour aller plus loin