Terraform — Provisioners

De wiki.nexiat.fr
Aller à la navigation Aller à la recherche
Fiche express
Type Mécanisme Terraform d'exécution de scripts
Blocs provisioner "local-exec" · provisioner "remote-exec"
Ressource associée null_resource / terraform_data
Position HashiCorp dernier recours
Voir aussi Terraform — Concepts fondamentaux · Terraform — Providers on-prem · Frontière IaC — Terraform, Ansible, GitOps

Les provisioners sont un mécanisme Terraform permettant d'exécuter des scripts au moment de la création (ou, plus rarement, de la destruction) d'une ressource. C'est le point d'entrée le plus direct pour « faire quelque chose de plus » après un apply — et c'est aussi, de l'aveu même de HashiCorp, la fonctionnalité à utiliser en dernier recours.

local-exec

Exécute une commande sur la machine qui lance Terraform (le poste local ou le runner CI), pas sur la ressource créée.

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> inventory.txt"
  }
}

Usage typique : générer un fichier d'inventaire Ansible, notifier un webhook, déclencher un script local après provisionnement.

remote-exec

Exécute une commande sur la ressource distante elle-même, via une connexion SSH ou WinRM ouverte par Terraform.

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  connection {
    type        = "ssh"
    host        = self.public_ip
    user        = "admin"
    private_key = file("~/.ssh/id_ed25519")
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx",
    ]
  }
}

Ce motif ressemble en apparence à ce que ferait Ansible, mais sans son idempotence déclarée, son inventaire, ni son écosystème de modules — voir l'avertissement plus bas.

null_resource et terraform_data

Un provisioner s'attache normalement à une ressource « réelle » (une VM, par exemple). Pour exécuter une action qui ne correspond à aucune ressource d'infrastructure (appeler une API, régénérer un fichier), on l'attache à une ressource sans effet propre :

resource "null_resource" "post_deploy" {
  triggers = {
    cluster_id = rancher2_cluster_v2.rke2.id
  }

  provisioner "local-exec" {
    command = "curl -X POST https://exemple.invalid/webhooks/deploy"
  }
}
  • null_resource (provider hashicorp/null) — historique, toujours largement utilisé.
  • terraform_data — ressource native (Terraform ≥ 1.4, à vérifier selon version) qui remplace progressivement null_resource sans dépendance à un provider externe.
  • triggers — map de valeurs qui, en changeant, force la réexécution du provisioner (par défaut, un provisioner ne s'exécute qu'à la création de la ressource).

Avertissement : dernier recours

La documentation HashiCorp elle-même qualifie les provisioners de « dernier recours » (last resort). Les raisons :

  • Pas de modèle déclaratif. Terraform ne peut ni planifier, ni prévisualiser, ni rejouer proprement le contenu d'un inline ou d'un script — c'est une boîte noire impérative dans un outil déclaratif.
  • Pas d'idempotence garantie. Rien n'empêche un script remote-exec d'échouer différemment selon l'état déjà présent sur la machine ; contrairement à un module Ansible, il n'y a pas de contrat « rejouable sans effet de bord ».
  • Échecs difficiles à récupérer. Un provisioner qui échoue peut laisser la ressource dans un état incertain (« tainted » dans les anciennes versions) ; le diagnostic se fait dans les logs bruts du script, pas dans le plan.
  • Dépendance réseau/SSH au moment de l'apply. remote-exec suppose que la machine est joignable en SSH/WinRM immédiatement après création — fragile face aux délais de boot, groupes de sécurité, bastions.
  • Aucune notion de convergence continue. Un provisioner s'exécute une fois (sauf triggers) ; il ne corrige pas un drift ultérieur, contrairement à un outil de configuration exécuté régulièrement.

Alternatives recommandées, par ordre de préférence

  1. Images pré-construites — cuire la configuration dans l'image (AMI via Packer, image Compute Engine, template vSphere) plutôt que la poser après coup.
  2. cloud-init / user-data — passer une configuration déclarative au démarrage de l'instance (user_data côté AWS, metadata_startup_script côté GCP, custom_data côté Azure), traitée par le cloud lui-même, pas par Terraform.
  3. Outil de configuration dédiéAnsible (ou équivalent), déclenché séparément après l'apply (typiquement en pipeline, cf. le motif déjà exposé sur Terraform — Providers on-prem : « Terraform provisionne, Ansible configure »).
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  user_data = <<-EOT
    #cloud-config
    packages:
      - nginx
  EOT
}

Ce motif cloud-init évite tout provisioner : la configuration est déclarative, rejouée par le cloud à chaque boot si nécessaire, et ne dépend pas d'une connexion SSH ouverte au moment précis de l'apply.

Quand un provisioner reste défendable

Quelques cas restent raisonnables : un local-exec purement local et idempotent (génération d'un fichier de sortie, appel d'un webhook de notification), ou un besoin ponctuel en environnement de test/démo sans enjeu de reproductibilité stricte. Pour tout ce qui touche à la configuration durable d'une machine en production, préférer systématiquement une des alternatives ci-dessus.

Voir aussi