Chapitre 1319 minIntermédiaire
Créer des VM Proxmox avec Ansible
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- créer un jeton d'API Proxmox aux droits limités ;
- cloner un modèle cloud-init avec
proxmox_kvm; - enchaîner création de la VM et configuration du système ;
- lister les VM avec l'inventaire dynamique de Proxmox.
3 exercices corrigés
Ansible peut créer, configurer, démarrer et supprimer des machines virtuelles sur Proxmox VE en appelant son API HTTPS depuis le nœud de contrôle, avec les modules de la collection community.proxmox. Ce chapitre crée un jeton d'API aux droits limités, clone un modèle Debian 13 avec cloud-init pour obtenir le serveur de recette de Sorbier, enchaîne la création et la configuration du système dans un même playbook, et présente l'inventaire dynamique qui liste les VM du cluster.
Deux façons de travailler avec Proxmox
- Configurer les hyperviseurs eux-mêmes. Un nœud Proxmox VE est un Debian 13 : on peut le gérer en SSH comme un serveur Linux (paquets, dépôts, NTP, comptes). Prudence : Proxmox gère lui-même une grande partie de sa configuration (réseau, cluster, stockage), qu'il ne faut pas réécrire derrière lui.
- Piloter les VM par l'API. Les modules
community.proxmox.*s'exécutent sur le nœud de contrôle et appellent l'API REST de Proxmox (port 8006) : créer, cloner, modifier, démarrer, arrêter, supprimer des VM et des conteneurs. C'est l'objet de ce chapitre.
Les modules Proxmox ont longtemps fait partie de community.general ; ils vivent désormais dans la collection community.proxmox (version 2.1 en novembre 2026). Les anciens noms (community.general.proxmox_kvm) ne sont plus que des redirections dépréciées, qui disparaîtront avec community.general 15 : écris community.proxmox.proxmox_kvm. Les modules ont besoin des bibliothèques Python proxmoxer et requests sur le nœud de contrôle (pipx inject ansible-core proxmoxer requests, chapitre 2).
Un jeton d'API aux droits limités
Ansible ne doit pas se connecter avec le compte root@pam de Proxmox. On crée un utilisateur dédié et un jeton d'API qui n'a que les droits nécessaires : gérer les VM, utiliser le stockage partagé et le pont réseau. Sur pve1, en root :
# Un utilisateur du royaume Proxmox (pve), sans mot de passe : il n'utilisera que son jeton
pveum user add ansible@pve --comment "Automatisation Ansible"
# Un jeton à privilèges séparés : il n'a que les droits qu'on lui donne
pveum user token add ansible@pve automatisation --privsep 1
# Les droits du jeton : les VM, le stockage partagé, les ponts réseau
pveum acl modify /vms --tokens 'ansible@pve!automatisation' --roles PVEVMAdmin
pveum acl modify /storage/truenas-nfs --tokens 'ansible@pve!automatisation' --roles PVEDatastoreUser
pveum acl modify /sdn/zones/localnetwork --tokens 'ansible@pve!automatisation' --roles PVESDNUser
pveum user token add affiche le secret du jeton une seule fois : range-le immédiatement dans le coffre d'Ansible (vault_token_proxmox). L'identifiant complet du jeton est ansible@pve!automatisation.
| Chemin | Rôle | Ce qu'il permet |
|---|---|---|
/vms | PVEVMAdmin | Créer, cloner, configurer, démarrer, supprimer toutes les VM, modèles compris |
/storage/truenas-nfs | PVEDatastoreUser | Allouer de l'espace pour les disques sur le stockage partagé du cluster |
/sdn/zones/localnetwork | PVESDNUser | Brancher une VM sur les ponts réseau locaux (vmbr0) |
Le stockage truenas-nfs, le modèle 9000 modele-debian13 et sa configuration cloud-init sont ceux du cours Virtualisation, qui détaille aussi les utilisateurs, les rôles et les jetons de Proxmox. Si une tâche échoue sur un droit, le message de l'API nomme le privilège manquant (par exemple Permission check failed (/storage/local-lvm, Datastore.AllocateSpace)) : ajoute l'ACL correspondante, sans élargir les droits au-delà du besoin.
Le playbook de création d'une VM
Sorbier a besoin d'un serveur de recette, srv-recette, pour tester les playbooks avant la production : VM 186 (la convention de Sorbier donne aux VM le numéro de leur dernier octet), 4 cœurs, 8 Gio de mémoire, 40 Gio de disque, adresse 192.168.21.186 dans le VLAN 21 APPLIS (192.168.21.0/24, passerelle 192.168.21.254), que Sorbier a ouvert pour les applications internes et la recette quand le VLAN 20 des serveurs a été plein. Le playbook vm_recette.yml la crée à partir du modèle, puis lui applique le rôle socle :
---
- name: Créer la VM de recette sur Proxmox VE
hosts: localhost
gather_facts: false
vars:
vm_id: 186
vm_nom: srv-recette
vm_ip: 192.168.21.186
vm_noeud: pve1
module_defaults:
group/community.proxmox.proxmox:
api_host: 192.168.10.213
api_user: ansible@pve
api_token_id: automatisation
api_token_secret: "{{ vault_token_proxmox }}"
validate_certs: false
tasks:
- name: Cloner le modèle Debian 13
community.proxmox.proxmox_kvm:
node: "{{ vm_noeud }}"
clone: modele-debian13
vmid: 9000
newid: "{{ vm_id }}"
name: "{{ vm_nom }}"
full: true
storage: truenas-nfs
timeout: 300
- name: Régler le processeur, la mémoire et cloud-init
community.proxmox.proxmox_kvm:
node: "{{ vm_noeud }}"
vmid: "{{ vm_id }}"
cores: 4
memory: 8192
ciuser: ansible
sshkeys: "{{ lookup('ansible.builtin.file', '~/.ssh/ansible_ed25519.pub') }}"
net:
net0: virtio,bridge=vmbr0,tag=21,firewall=1
ipconfig:
ipconfig0: ip={{ vm_ip }}/24,gw=192.168.21.254
nameservers:
- 192.168.10.177
- 192.168.10.181
searchdomains:
- ad.sorbier.example
update: true
- name: Agrandir le disque à 40 Gio
community.proxmox.proxmox_disk:
vmid: "{{ vm_id }}"
disk: scsi0
size: 40G
state: resized
- name: Démarrer la VM
community.proxmox.proxmox_kvm:
node: "{{ vm_noeud }}"
vmid: "{{ vm_id }}"
state: started
- name: Attendre que SSH réponde
ansible.builtin.wait_for:
host: "{{ vm_ip }}"
port: 22
delay: 10
timeout: 300
- name: Ajouter la VM à l'inventaire de cette exécution
ansible.builtin.add_host:
name: "{{ vm_nom }}"
ansible_host: "{{ vm_ip }}"
groups:
- linux
- name: Appliquer le socle à la nouvelle VM
hosts: srv-recette
become: true
roles:
- socle
Ce qu'il faut comprendre :
hosts: localhost: les modules Proxmox parlent à l'API depuis le nœud de contrôle ; aucune connexion SSH n'est faite vers l'hyperviseur.module_defaultsdonne les paramètres de connexion à tous les modules du groupecommunity.proxmox.proxmox, au lieu de les répéter dans chaque tâche. Le secret vient du coffre.validate_certs: falseaccepte le certificat auto-signé que Proxmox génère à l'installation. En production, installe sur les nœuds un certificat de l'AC interne (ou Let's Encrypt) et retire cette ligne.- Le clonage puis la configuration sont deux tâches : lors d'un clonage, le module ne prend en compte que les paramètres du clone.
update: truemodifie ensuite la VM. Les paramètresci*,ipconfig,nameservers,searchdomainsetsshkeysremplissent le lecteur cloud-init du modèle : au premier démarrage, cloud-init crée le compteansibleavec la clé du bastion et le droitsudosans mot de passe, et pose l'adresse. wait_forattend que le port 22 réponde : la VM démarre, cloud-init s'exécute, puis SSH est prêt.add_hostajoute la VM à l'inventaire en mémoire, pour la suite de cette exécution seulement, dans le groupelinux(elle reçoit doncgroup_vars/linux.yml). Le second play lui applique le rôlesocle. Pour la gérer ensuite, ajoute-la danshosts.yml(et son nom dans le DNS, chapitre 11) ou utilise l'inventaire dynamique ci-dessous.
Relancé, le playbook est idempotent : le clone voit que la VM 186 existe et ne fait rien, la configuration n'est modifiée que si une valeur diffère, le disque n'est pas agrandi s'il fait déjà 40 Gio, la VM est déjà démarrée.
Supprimer une VM
Pour supprimer la VM de recette à la fin d'un projet, il faut d'abord l'arrêter :
- name: Arrêter la VM de recette
community.proxmox.proxmox_kvm:
vmid: 186
state: stopped
force: true
timeout: 120
- name: Supprimer la VM et ses disques
community.proxmox.proxmox_kvm:
vmid: 186
state: absent
purge: true
purge: true retire aussi la VM des tâches de sauvegarde, de réplication et de haute disponibilité où elle figurait. Pour les états autres que present, le nœud est retrouvé automatiquement ; node n'est pas nécessaire.
L'inventaire dynamique de Proxmox
Plutôt que d'ajouter chaque VM à la main dans hosts.yml, le plugin d'inventaire community.proxmox.proxmox interroge l'API et construit la liste des VM à chaque exécution. Le fichier doit se terminer par .proxmox.yml, par exemple inventaire/cluster.proxmox.yml :
---
plugin: community.proxmox.proxmox
url: https://192.168.10.213:8006
user: ansible@pve
token_id: automatisation
token_secret: "{{ lookup('ansible.builtin.env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: false
want_facts: true
want_proxmox_nodes_ansible_host: false
keyed_groups:
- key: proxmox_tags_parsed
separator: ""
prefix: etiquette_
compose:
ansible_host: (proxmox_ipconfig0.ip | default('')).split('/') | first
- Le plugin crée des groupes automatiquement, préfixés par
proxmox_:proxmox_all_qemu(toutes les VM),proxmox_all_running(celles qui tournent),proxmox_pve1_qemu(les VM du nœudpve1)… keyed_groupsajoute un groupe par étiquette Proxmox : une VM étiquetéewebdans l'interface (Tags) tombe dans le groupeetiquette_web. Les étiquettes deviennent ainsi un moyen de ranger les VM pour Ansible.composecalcule des variables : ici,ansible_hostest l'adresse posée par cloud-init (192.168.21.186/24devient192.168.21.186). Pour une VM sans configuration cloud-init, il faut une autre source (le DNS, ou l'agent invité QEMU).- Le secret est lu dans une variable d'environnement,
PROXMOX_TOKEN_SECRET, pour ne pas l'écrire dans le fichier ; une valeur chiffrée paransible-vault encrypt_stringest aussi acceptée.
Si ansible.cfg désigne le dossier inventaire/ comme inventaire, Ansible fusionne hosts.yml et l'inventaire dynamique :
ansible-inventory -i inventaire/ --graph proxmox_all_running
Exercices
Exercice 1 sur 3 Deux VM d'un coup Réussi À revoir
Sorbier veut deux serveurs web de test, srv-test1 (VM 187, 192.168.21.187) et srv-test2 (VM 188, 192.168.21.188), dans le VLAN 21 APPLIS, clonés du modèle 9000, 2 cœurs et 2 Gio chacun. Écris les tâches de clonage et de configuration avec une boucle sur une liste de dictionnaires. Vérifie que les adresses sont dans le réseau du VLAN 21 et qu'elles sont libres.
Voir la réponseMasquer la réponse de l'exercice 1
vars:
vms_test:
- id: 187
nom: srv-test1
ip: 192.168.21.187
- id: 188
nom: srv-test2
ip: 192.168.21.188
tasks:
- name: Cloner le modèle pour chaque VM de test
community.proxmox.proxmox_kvm:
node: pve1
clone: modele-debian13
vmid: 9000
newid: "{{ item.id }}"
name: "{{ item.nom }}"
full: true
storage: truenas-nfs
timeout: 300
loop: "{{ vms_test }}"
loop_control:
label: "{{ item.nom }}"
- name: Configurer chaque VM de test
community.proxmox.proxmox_kvm:
node: pve1
vmid: "{{ item.id }}"
cores: 2
memory: 2048
ciuser: ansible
sshkeys: "{{ lookup('ansible.builtin.file', '~/.ssh/ansible_ed25519.pub') }}"
net:
net0: virtio,bridge=vmbr0,tag=21,firewall=1
ipconfig:
ipconfig0: ip={{ item.ip }}/24,gw=192.168.21.254
update: true
loop: "{{ vms_test }}"
loop_control:
label: "{{ item.nom }}"
Le VLAN 21 est 192.168.21.0/24 : les hôtes vont de .1 à .254, et la passerelle est .254, sur le pare-feu. .187 et .188 sont libres (.182 est le conteneur ct-horaires, .186 la VM srv-recette). Dans le VLAN 20 des serveurs, ces deux adresses auraient été un piège : .187 est la dernière adresse libre, et .188 et .189 sont celles des deux pare-feu pfSense en cluster. Le modèle est branché sur le VLAN 20 : sans net0 avec l'étiquette 21, les clones seraient dans le mauvais réseau et ne joindraient pas leur passerelle.
Avec la boucle, ajouter une troisième VM revient à ajouter trois lignes à vms_test, sans toucher aux tâches.
Tu avais trouvé ?
Exercice 2 sur 3 Les bons droits Réussi À revoir
Le playbook de création échoue sur la tâche de clonage avec : Permission check failed (/storage/local-lvm, Datastore.AllocateSpace). Le jeton a les trois ACL de ce chapitre. Explique et propose deux corrections.
Voir la réponseMasquer la réponse de l'exercice 2
Le clone est créé sur le stockage local-lvm, pour lequel le jeton n'a aucun droit : le paramètre storage est absent ou mal écrit, et Proxmox utilise un autre stockage que truenas-nfs. Corrections possibles : préciser storage: truenas-nfs dans la tâche de clonage (la bonne solution, puisque les VM du cluster doivent être sur le stockage partagé pour pouvoir migrer), ou, si local-lvm est vraiment voulu, ajouter pveum acl modify /storage/local-lvm --tokens 'ansible@pve!automatisation' --roles PVEDatastoreUser. Ne pas donner un rôle d'administrateur sur / pour « faire marcher » : le jeton aurait alors tous les droits sur le cluster.
Tu avais trouvé ?
Exercice 3 sur 3 Ranger les VM par étiquette Réussi À revoir
Avec l'inventaire dynamique de ce chapitre, Moussa étiquette recette les VM 186, 187 et 188 dans l'interface de Proxmox. Écris la commande qui applique le playbook site.yml uniquement à ces VM, et dis à quoi sert --list-hosts ici.
Voir la réponseMasquer la réponse de l'exercice 3
ansible-playbook -i inventaire/ site.yml --limit etiquette_recette --list-hosts
ansible-playbook -i inventaire/ site.yml --limit etiquette_recette
Le plugin crée le groupe etiquette_recette (préfixe etiquette_, séparateur vide, étiquette recette). --list-hosts vérifie d'abord quelles machines seront touchées : avec un inventaire dynamique, la liste dépend de l'état du cluster au moment de l'exécution, et une étiquette posée par erreur sur une VM de production l'ajouterait à la cible.
Tu avais trouvé ?