Terraform + Proxmox
Déploiement de VMs sur proxmox avec terraform à partir d'un template CloudInit
Cette doc permet le déploiement automatisé de machines virtuelles (VM) sur un cluster Proxmox VE à l’aide de Terraform et du provider bpg/proxmox. Bonus : Petite configuration Ansible
Prérequis
Un serveur Proxmox VE en version 8.x
Une machine Linux avec Terraform (>= 1.5) ou OpenTofu (>= 1.6) installé
Un utilisateur dédié sur Proxmox avec un API Token configuré
Un templaye de vm
Les accès SSH configurés si nécessaire pour la provision des VM
Création du rôle et de l'utilisateur Proxmox pour Terraform (via CLI)
Docs officielle : https://registry.terraform.io/providers/bpg/proxmox/latest/docs#api-token-authentication
Créer un rôle Terraform avec les droits nécessaires
pveum role add TerraformProv -privs "Datastore.AllocateSpace Datastore.AllocateTemplate Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Migrate VM.Monitor VM.PowerMgmt SDN.Use"
Ce rôle regroupe toutes les permissions recommandées pour piloter des VM, disques, réseaux, cloud-init, migration, etc.
Créer l'utilisateur Terraform
pveum user add terraform-prov@pve --password <votre_mot_de_passe>
Remplacer <votre_mot_de_passe>
par un mot de passe fort.
Associer le rôle à l'utilisateur sur tout le cluster
pveum aclmod / -user terraform-prov@pve -role TerraformProv
Le /
cible l’ensemble du cluster, ajustez si besoin pour limiter à un nœud ou un pool spécifique.
Créer un token API pour l’utilisateur
pveum user token add terraform-prov@pve terraform -expire 0 -privsep 0 -comment "Terraform token"
-expire 0
: pas d’expiration automatique-privsep 0
: désactive la séparation de privilèges, nécessaire pour TerraformNotez immédiatement le secret du token affiché : il ne sera plus visible ensuite.
Génération du Token API
Créer un token API pour l’utilisateur (ici nommé terraform
) et accorder les roles necessaire au token :
pveum user token add terraform-prov@pve terraform -comment "Token Terraform"
#pveum aclmod / --roles TerraformProv --token 'terraform@pve!terraform' --propagate 1
Notez bien le token secret affiché : il ne sera plus visible ensuite.
Configuration des variables d’environnement
Ajouter ces variables à votre shell avant d’utiliser Terraform :
A faire apres chaque fermerture de la session
export PROXMOX_VE_ENDPOINT="https://proxmox.lab.prod:8006/"
export PROXMOX_VE_INSECURE=true
export PROXMOX_VE_USERNAME="terraform-uer-proxmox@pve"
export PROXMOX_VE_PASSWORD="user-proxmox-x"
export PROXMOX_VE_API_TOKEN="API_Token"
Création du template cloudinit:
ID : 9000
nom de la VM : debian-cloudinit
RAM : 2Go
Interface réseau : vmbr1
✅1. Télécharger une ISO Debian Cloud-Init Ready
Utiliser une ISO minimale, par exemple via netinst
, ou directement une image cloud-init-ready comme :
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
✅ 2. Créer une VM dans Proxmox (sans ISO) en CLI
qm create 9000 --name debian-cloudinit --memory 2048 --net0 virtio,bridge=vmbr1
✅ 3. Importer l’image disque téléchargée
qm importdisk 9000 debian-12-genericcloud-amd64.qcow2 local
Où local représente le nom du Datastore. Ensuite, attacher le disque au contrôleur :
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local:9000/vm-9000-disk-0.raw
✅ 4. Ajouter le disque Cloud-Init
qm set 9000 --ide2 local:cloudinit
✅ 5. Configurer le boot & autres options
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --serial0 socket --vga serial0
✅ 6. Convertir en template
qm template 9000
🔁 Ensuite dans Terraform
On pourra directement l’utiliser comme vm_id = 9000
dans ta config clone
.
🧼 Bonus : nettoyage au besoin
rm debian-12-genericcloud-amd64.qcow2
Création d'une clé ssh
Cette clé ssh sera déployée sur les vms qu'on créera
ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa
Cela génèrera deux fichiers :
id_rsa
→ Clé privée (à garder secrète).id_rsa.pub
→ Clé publique (à injecter dans les VMs).
Place la clé publique dans le dossier Terraform :
Le fichier id_rsa.pub
doit se trouver dans le dossier contenant ton projet Terraform.
Création des fichiers de configs terraform
proxmox-bpg-terraform/ ├── README.md ├── main.tf ├── data.tf ├── variables.tf ├── outputs.tf ├── vm.tf Tout ça est disponible sur mon repo Github
main.tf
Ce fichier initialise le projet Terraform et définit le provider principal utilisé : bpg/proxmox (version 0.66.2). Cette configuration est nécessaire pour permettre à Terraform d’interagir avec l’API Proxmox.
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.66.2"
}
}
}
data.tf
Ce fichier permet à terrafom de récupérer une clé publique en data source et de lire le contenu du fichier id_rsa.pub
et de l’utiliser dans la configuration via data.local_file.ssh_public_key.content
C’est la méthode recommandée pour importer une clé publique locale dans Terraform
data "local_file" "ssh_public_key" {
filename = "${path.module}/id_rsa.pub"
}
local_file
est un data source qui lit le contenu d’un fichier local.trimspace(...)
est utilisé ensuite dans levm.tf
pour s'assurer qu'aucun saut de ligne inutile ne soit injecté dans le cloud-init.
Vous pouvez remplacer ${path.module} par le chemin où se trouve la clé. Moi je l'ai dans le répertoire de mon projet. donc mon data.tf devient :
data "local_file" "ssh_public_key" {
filename = ".id_rsa_pub_deploy"
}
variables.tf
Ce fichier définit l’ensemble des variables d’entrée utilisées dans le projet Terraform pour la création de machines virtuelles sur Proxmox. Modifiez ces variables selon votre environnement et vos besoins.
variable "node_name" {
description = "proxmox "
default = "prox"
}
variable "template_debian_id" {
description = " template debian cloudinit"
default = "9000"
}
variable "nb_vms" {
description = "nombre de vms à créer"
}
variable "vm_names" {
description = "nom des vm"
type = list(string)
default = ["vm1", "vm2", "vm3"]
}
Le bloc suivant :
variable "vm_names" {
description = "nom des vm"
type = list(string)
default = ["vm1", "vm2", "vm3"]
}
définit une variable appelée vm_names
, qui est une liste de chaînes de caractères (donc un tableau de noms). Chaque élément de cette liste représente le nom d'une VM à créer avec Terraform.
Ce format est essentiel car il permet d'utiliser for_each
dans le fichier vm.tf, ce qui offre un meilleur contrôle sur chaque ressource individuellement (contrairement à count
, qui fonctionne avec des index numériques sans lien direct avec le nom).
Par exemple :
for_each = toset(var.vm_names)
name = each.key
Cela permet de créer une VM pour chaque nom fourni dans la liste, avec des paramètres propres (nom, IP, ID, etc.).
⚠️ Important : cette liste reflète l’état réel des VMs gérées par Terraform. Si on supprime un nom de la liste, Terraform considérera que la VM correspondante doit être détruite. Donc, pour ajouter de nouvelles VMs sans toucher aux existantes, il faut ajouter les nouveaux noms à la liste sans retirer les anciens
outputs.tf
Ce fichier définit les sorties (outputs) de Terraform pour ce projet. Ici, on expose les adresses IPv4 de toutes les VMs créées, ce qui permet de les retrouver facilement après un apply.
output "vms_ipv4" {
description = "IPv4 des VMs créées"
value = {
for vm_name, vm in proxmox_virtual_environment_vm.vm :
vm_name => vm.ipv4_addresses
}
}
vm.tf
Ce fichier définit la ressource principale de machine virtuelle (VM) à déployer sur Proxmox via Terraform. Il s’appuie sur les variables du projet et la clé publique SSH importée en data source.
Dans Terraform, le mot de passe fourni à user_account
doit être hashé .
Génération du mot de passe chiffré
sudo apt install openssl
sudo dnf install openssl # pour le gestionnaire de paquet dnf
sudo yum install openssl # pour le gestionnaire de paquet yum
openssl passwd -6 le-mot-de-passe-à-chiffré
Exemple de résultat
imad@:/mnt/c/Users/imad$ openssl passwd -6 password
$6$tHVVDVCWxyr0H0kf$AXm7J6O/nGKMpKK1qzx/uMMRKw7IawWPHWbhu0lYIkql/I/chCaX9nAdisUJ65tuY1RBb4WqqqNMErQiE725n0
Le mot de passe haché est à utiliser dans :
user_account {
username = "imad"
password = "<mot_de_passe_hashé>"
keys = [trimspace(data.local_file.ssh_public_key.content)]
}
Le fichier vm.tf devrait ressembler à ca
resource "proxmox_virtual_environment_vm" "vm" {
for_each = toset(var.vm_names)
name = each.key
node_name = var.node_name
vm_id = 900 + index(var.vm_names, each.key)
clone {
vm_id = 9000
full = true
}
initialization {
datastore_id = "local"
user_account {
username = "imad"
password = "mot-de-passe-haché"
keys = [trimspace(data.local_file.ssh_public_key.content)]
}
dns {
domain = "local"
servers = ["1.1.1.1"]
}
ip_config {
ipv4 {
address = "10.10.10.${50 + index(var.vm_names, each.key)}/24"
gateway = "10.10.10.1"
}
}
}
lifecycle {
ignore_changes = [
# ignore toute mise à jour sur ces attributs pour les VMs existantes
initialization[0].ip_config[0].ipv4[0].address,
initialization[0].ip_config[0].ipv4[0].gateway,
network_device,
disk,
memory,
cpu
]
}
# prevent_destroy = true
}
Pourquoi for_each
?
Contrairement à count
, for_each
permet de :
Nommer chaque ressource individuellement (via une clé explicite),
Ajouter/retirer des ressources sans destruction des autres,
Gérer une configuration plus flexible.
Exemple :
variable "vm_names" {
default = ["vm1", "vm2", "vm3"]
}
resource "proxmox_virtual_environment_vm" "vm" {
for_each = toset(var.vm_names)
name = each.key
Ce que ça fait :
Crée une VM nommée
vm1
, une autrevm2
, etc.Chaque VM est traitée indépendamment.
Génération automatique des vm_id
et adresses IP
vm_id
et adresses IP🔢 vm_id unique
vm_id = 900 + index(var.vm_names, each.key)
→ Cela génère :
vm1
→900
vm2
→901
vm3
→902
🌐 IPs fixes
address = "10.10.10.${50 + index(var.vm_names, each.key)}/24"
→ IPs attribuées :
bastion
→10.10.10.50
kube-master
→10.10.10.51
kube-worker
→10.10.10.52
🔄 Utilisation de lifecycle.ignore_changes
lifecycle.ignore_changes
lifecycle {
ignore_changes = [
initialization[0].ip_config[0].ipv4[0].address,
initialization[0].ip_config[0].ipv4[0].gateway,
network_device,
memory,
cpu,
disk
]
}
Pouvoir modifier manuellement ces paramètres (RAM, CPU, IP, etc.) via l’interface Proxmox sans que Terraform essaie de les remettre à leur état initial à chaque apply
.
Prevent destroy
Le champ prevent_destroy
dans Terraform est une protection qui empêche la suppression accidentelle d'une ressource, même si elle n’existe plus dans le code ou si tu fais un terraform destroy
.
prevent_destroy = true
Déploiement
terraform init
→ Prépare l’environnementterraform fmt
→ Formate le codeterraform validate
→ Vérifie la validité de la configurationterraform plan
→ Prévisualise les changementsterraform apply
→ Applique les changements sur l’infrastructure
Pour ajouter de nouvelles VMs à défaut de modifier le fichier variables.tf :
terraform apply -var='vm_names=["vm1", "vm2", "vm3", "dns"]'
Une fois les Vms créées on peut leur appliquer des configs spécifiques avec ansible. ICI
Last updated
Was this helpful?