Aller au contenu

Terraform / OpenTofu avec Proxmox

Day 2 · ExploiterNiveau 3 · MaîtriserProcédureRédigé · à valider en lab

Objectif : décrire vos VM Proxmox VE dans du code (Terraform ou OpenTofu), les créer par clonage d'un template cloud-init, et les modifier ou les supprimer de façon reproductible et relue, avec un jeton API limité à un pool dédié.

Choisir un provider

Proxmox ne publie pas de provider officiel. Deux providers communautaires dominent ; ils fonctionnent à l'identique avec Terraform et OpenTofu.

bpg/proxmox Telmate/proxmox
Statut Communautaire, développement actif Communautaire, historique
Ressource VM proxmox_virtual_environment_vm proxmox_vm_qemu
Couverture VM, CT, fichiers/snippets, réseau, utilisateurs, firewall, HA… Surtout VM et CT
Authentification Jeton API, plus SSH pour certaines opérations (upload de snippets, import de disques) Jeton API ou mot de passe
Recommandation MSP Choix par défaut pour un nouveau projet Projets existants ; vérifier la compatibilité PVE 9 de la version utilisée

Étant communautaires, ces providers ne sont pas couverts par le support Proxmox ni garantis par le MSP : épinglez une version validée en lab et relisez les notes de version avant chaque montée.

Avant de toucher la prod

  • Compte de service svc-terraform@pve, rôle TerraformProv, pool terraform et jeton svc-terraform@pve!ci créés : voir Jetons API et comptes de service.
  • Template cloud-init prêt (VMID 9000, agent QEMU installé), placé dans le pool terraform : voir Templates cloud-init.
  • Plage de VMID et plage d'IP réservées à Terraform (ex. 1200-1299, 10.20.0.120-199).
  • État (state) stocké dans un backend distant avec verrouillage (GitLab, S3 compatible), jamais sur un poste.
  • Secret du jeton dans le coffre ou la CI, exposé en variable d'environnement TF_VAR_pve_api_token.

Étapes

1. Déclarer le provider

providers.tf :

terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "0.111.0" # version validée en lab, à faire évoluer après test
    }
  }
}

variable "pve_api_token" {
  type      = string
  sensitive = true # format : svc-terraform@pve!ci=<secret>
}

provider "proxmox" {
  endpoint  = "https://pve01.corp.local:8006/"
  api_token = var.pve_api_token
  insecure  = false
}
terraform {
  required_providers {
    proxmox = {
      source  = "Telmate/proxmox"
      version = "<version validée en lab>"
    }
  }
}

provider "proxmox" {
  pm_api_url          = "https://pve01.corp.local:8006/api2/json"
  pm_api_token_id     = "svc-terraform@pve!ci"
  pm_api_token_secret = var.pve_api_token_secret
  pm_tls_insecure     = false
}

2. Décrire une VM clonée (bpg)

web01.tf :

resource "proxmox_virtual_environment_vm" "web01" {
  name      = "web01"
  node_name = "pve01"
  vm_id     = 1201
  pool_id   = "terraform"
  tags      = ["terraform", "web"]

  clone {
    vm_id = 9000
    full  = true
  }

  agent { enabled = true }
  cpu { cores = 2 }
  memory { dedicated = 4096 }

  disk {
    datastore_id = "ceph-vm"
    interface    = "scsi0"
    size         = 40
  }

  network_device {
    bridge  = "vmbr0"
    vlan_id = 20
  }

  initialization {
    datastore_id = "ceph-vm"
    ip_config {
      ipv4 {
        address = "10.20.0.121/24"
        gateway = "10.20.0.1"
      }
    }
    user_account {
      username = "exploit"
      keys     = [file("~/.ssh/id_ed25519.pub")]
    }
  }
}

3. Planifier, relire, appliquer

export TF_VAR_pve_api_token='svc-terraform@pve!ci=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
tofu init            # ou terraform init
tofu plan -out plan.bin
tofu apply plan.bin

Le plan est relu par un pair (merge request) avant tout apply en production. Une ligne must be replaced sur une VM existante signifie destruction puis recréation : on s'arrête et on analyse.

Vérifications

  • tofu plan juste après l'apply affiche No changes. Your infrastructure matches the configuration.
  • qm config 1201 sur pve01 : agent: 1, net0 sur vmbr0 avec tag=20, ipconfig0: ip=10.20.0.121/24,gw=10.20.0.1.
  • Datacenter → Pools → terraform contient la VM 1201.
  • ssh exploit@10.20.0.121 hostname répond web01.

Rollback

Revenir au commit précédent puis tofu plan / tofu apply. Pour retirer une VM du code sans la détruire : tofu state rm proxmox_virtual_environment_vm.web01. tofu destroy détruit réellement les VM et leurs disques : à protéger, et sauvegarder les VM concernées avant (vzdump 1201 --storage pbs-local).

Pièges vus en support

  • Modification faite à la main dans la GUI sur une VM gérée par Terraform : le plan suivant veut l'annuler, parfois en recréant la VM. Toute modification passe par le code.
  • Changement d'un attribut qui force le remplacement (clone source, certains paramètres de disque) : la VM et ses données sont détruites. Lire le plan.
  • VM.GuestAgent.Audit absent du rôle (PVE 9) : création OK puis erreur 403 en lisant l'IP.
  • Deux personnes qui appliquent en parallèle avec un state local : VMID en conflit, état divergent. Backend distant avec verrou obligatoire.
  • Jeton root@pam utilisé « pour aller vite » : un destroy mal ciblé peut supprimer n'importe quelle VM du cluster. Le pool dédié limite les dégâts.
  • Snippets cloud-init personnalisés avec bpg : nécessitent un stockage acceptant le contenu snippets et un accès SSH au nœud, en plus du jeton.

Quand ouvrir un ticket iTop

VM détruite par erreur : P2, restauration selon RB-12. Revue d'un code Terraform, conception des rôles et pools, intégration CI : demande P4. Le MSP ne débogue pas les providers communautaires eux-mêmes, mais vérifie droits, templates et API côté Proxmox.

Ouvrir un ticket iTop

Pour aller plus loin