revision.devops

Terraform

Terraform décrit l'infrastructure dans des fichiers .tf (langage HCL). Il compare ce que décrit le code, ce que contient le state, et ce qui existe réellement, puis calcule les changements.

Le cycle de travail

terraform init        # télécharge providers et modules, configure le backend
terraform fmt -recursive
terraform validate    # syntaxe et cohérence
terraform plan -out=tfplan
terraform apply tfplan   # applique exactement le plan revu
terraform destroy

Toujours appliquer un plan sauvegardé en CI : on applique ce qui a été relu, pas un nouveau calcul qui pourrait différer.

Le state

Le fichier terraform.tfstate associe chaque ressource du code à son ID réel dans le cloud. Sans lui, Terraform ne sait pas ce qu'il gère.

Règles :

  • En équipe, jamais de state local. Backend distant avec verrouillage, pour que deux personnes n'appliquent pas en même temps.
  • Le state contient souvent des secrets en clair (mots de passe de base générés, clés). Il faut restreindre l'accès au bucket.
  • Versioning activé sur le bucket pour pouvoir revenir en arrière.
  • Un state par environnement et par périmètre. Un state géant, c'est des plans lents et un rayon d'explosion énorme.
terraform {
  backend "gcs" {
    bucket = "renault-dbb-tfstate"
    prefix = "kafka/prod"
  }
}

Le backend GCS gère le verrouillage nativement.

Commandes de state

terraform state list
terraform state show google_compute_instance.web
terraform state mv google_compute_instance.web module.web.google_compute_instance.this
terraform state rm google_storage_bucket.legacy   # Terraform l'oublie, sans le détruire
terraform import google_storage_bucket.logs mon-projet/logs-bucket
terraform force-unlock <LOCK_ID>

force-unlock : uniquement après avoir vérifié que personne n'est en train d'appliquer (un job CI planté a pu laisser le verrou).

Depuis Terraform 1.5, on peut importer en déclaratif, ce qui passe en revue de code :

import {
  to = google_storage_bucket.logs
  id = "mon-projet/logs-bucket"
}

Et moved {} remplace state mv pour les refactorings :

moved {
  from = google_compute_instance.web
  to   = module.web.google_compute_instance.this
}

La dérive (drift)

Quelqu'un a modifié une ressource à la main dans la console. Le plan suivant montre la différence et propose de revenir à ce que dit le code.

Deux cas :

  • La modif manuelle était une erreur ou un dépannage temporaire : on applique, le code reprend la main.
  • La modif était légitime : on la reporte dans le code, puis le plan doit être vide.

terraform plan -refresh-only montre ce qui a changé côté cloud sans proposer de modifications. Un job planifié qui lance un plan chaque nuit et alerte s'il n'est pas vide détecte la dérive tôt.

Variables, outputs, locals

variable "env" {
  type        = string
  description = "Environnement cible"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.env)
    error_message = "env doit valoir dev, staging ou prod."
  }
}

variable "db_password" {
  type      = string
  sensitive = true
}

locals {
  name_prefix = "dbb-${var.env}"
  labels = {
    env   = var.env
    owner = "digital-backbone"
  }
}

output "vm_ip" {
  value = google_compute_instance.web.network_interface[0].network_ip
}

Ordre de priorité des valeurs, du plus faible au plus fort : valeur par défaut, variables d'environnement TF_VAR_x, terraform.tfvars, *.auto.tfvars, puis -var et -var-file en ligne de commande (le dernier cité gagne).

sensitive = true cache la valeur dans l'affichage du plan, pas dans le state.

count contre for_each

# count : index numériques
resource "google_compute_instance" "broker" {
  count = 3
  name  = "broker-${count.index}"
}

# for_each : clés stables
resource "google_compute_instance" "broker" {
  for_each = toset(["a", "b", "c"])
  name     = "broker-${each.key}"
}

Avec count, si tu retires l'élément du milieu d'une liste, tous les suivants changent d'index : Terraform les détruit et les recrée. Avec for_each, chaque ressource est identifiée par sa clé, on ne touche qu'à celle qu'on retire.

count reste utile pour un booléen : count = var.enable_monitoring ? 1 : 0.

Modules

Un module = un dossier de .tf réutilisable avec des variables en entrée et des outputs en sortie.

module "network" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = var.project_id
  network_name = "${local.name_prefix}-vpc"
  subnets      = [...]
}

Toujours épingler la version d'un module distant. Un module interne à l'entreprise se référence souvent par tag Git : source = "git::https://gitlab.renault.com/infra/modules.git//vpc?ref=v1.4.0".

Providers et versions

terraform {
  required_version = ">= 1.6"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}

Le fichier .terraform.lock.hcl fige les versions exactes : à commiter.

lifecycle

resource "google_sql_database_instance" "main" {
  lifecycle {
    prevent_destroy       = true   # refuse tout plan qui détruirait la base
    create_before_destroy = true   # crée la nouvelle avant de supprimer l'ancienne
    ignore_changes        = [settings[0].tier]  # ne pas revenir sur un changement fait ailleurs
  }
}

Data sources

Lire une ressource existante sans la gérer :

data "google_compute_network" "shared" {
  name    = "shared-vpc"
  project = "host-project"
}

Organiser plusieurs environnements

ApprochePourContre
Un dossier par environnement (envs/dev, envs/prod) appelant les mêmes modulesClair, states séparés, différences explicitesUn peu de duplication
Workspaces TerraformPas de duplicationFacile de se tromper de workspace, même backend
TerragruntÉvite la duplication de config backendUn outil de plus

En entreprise, le dossier par environnement est le plus courant.

Terraform et OpenTofu

OpenTofu est le fork open source de Terraform, créé après le passage de Terraform sous licence BSL en 2023. Syntaxe quasi identique. Bon à savoir si on te pose la question.

Questions d'entretien à préparer

  • Différence entre Terraform et Ansible ?
  • C'est quoi le state, où le mets-tu, pourquoi le verrouiller ?
  • Le state contient-il des secrets ? (oui, d'où l'accès restreint au bucket)
  • Quelqu'un a modifié une ressource à la main, que se passe-t-il ?
  • count ou for_each, pourquoi ?
  • Comment importer une ressource existante ?
  • Le verrou du state est bloqué après un job CI planté, que fais-tu ?
  • Comment tu structures plusieurs environnements ?
Faire le QCMExercicesAnsible →