Question de l'exploitant : « je ne vois nulle part un MTU a 1450 ». Fondee. Le 1450 existait bien — MTU_OVERLAY_DEFAUT dans underlay.py, dont P23 derive sa garde (transport >= overlay + 50), pose par le devis SDN sur chaque zone, et verifie sur le cluster. Mais il ne se propage pas a la carte de l'invite : les 14 VM tournaient a 1500, et cloner_vm_debian.yml n'avait aucune occurrence de `mtu`. La VM emettait donc des trames que son propre chemin ne pouvait pas encapsuler — la panne que le registre des flux appelle « la plus couteuse a diagnostiquer ». Invisible parce que les 14 VM vivent sur asgard : deux VM du meme hyperviseur communiquent par le pont local, sans encapsulation. Le defaut serait apparu au premier eclatement de la flotte — c'est-a-dire quand il faudra heberger les deux tenants ensemble. Corrige en DEUX endroits, avec `mtu=1` (« herite du pont ») plutot que 1450 en dur : juste en SDN comme hors SDN, et encore juste le jour des trames jumbo. Le GABARIT le porte — proposition de l'exploitant, meilleure que la mienne : elle couvre les clonages qui ne passent pas par le playbook. Et le playbook le repose a chaque clone, parce qu'un gabarit se RECAPTURE et que ce qui n'est pas versionne se perd en silence. make mtu-mesurer (7e devis) rattache chaque hote a sa zone par son pont derive et lit le MTU attendu dans devis_sdn.py. Ecart trouve sur 14 hotes du premier coup, puis confirme corrige. ET J'AI CASSE LA FLOTTE EN CORRIGEANT : applique aux cartes de VM EN MARCHE, le changement detache et rebranche la carte sans que l'invite reconfigure son interface. 0/14 joignables pendant trois minutes. L'agent qemu, independant du reseau invite, a permis de constater et de redemarrer par l'API. Dans le playbook la meme tache s'execute AVANT le demarrage du clone : aucun risque. Sur une VM en service : poser la config, puis redemarrer — une seule d'abord. Verifie : mtu-mesurer CONFORME 14/14 a 1450, prouver.py 35 OK, ansible-lint production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
420 lines
22 KiB
YAML
420 lines
22 KiB
YAML
---
|
|
- name: Cloner une VM Debian depuis le modele Proxmox
|
|
hosts: localhost
|
|
connection: local
|
|
gather_facts: false
|
|
become: false
|
|
|
|
vars:
|
|
# Config Proxmox de l'instance : on cherche dans l'inventaire de l'instance,
|
|
# quel que soit son nom (lab > principal > production). Compatible « par instance ».
|
|
setops_inventaires: [lab, principal, production]
|
|
# Voute UNIQUE de l'instance : all/vault.yml porte TOUS les secrets, jeton
|
|
# d'hyperviseur compris (D-19). `proxmox.vault.yml` a ete retire le 2026-08-03 :
|
|
# tolere "en compatibilite", il restait le SEUL porteur du jeton chez un tenant,
|
|
# et il etait gitignore — une voute unique qui ne l'etait pas, dans un fichier
|
|
# qui ne voyageait avec aucun depot. P18 ne pouvait pas le voir : il verifie le
|
|
# gabarit, pas le contenu reel.
|
|
# `proxmox.local.yml` reste une surcharge locale volontaire, non versionnee.
|
|
setops_gv_proxmox: [proxmox.yml, proxmox.local.yml, all/vault.yml]
|
|
# Le CLUSTER appartient a l'HEBERGEUR (API, noeuds, stockages, ponts). Son fichier
|
|
# vit dans le depot de l'hebergeur, que le symlink `underlay.yml` designe deja
|
|
# (D-14/D-17) : on en DERIVE le chemin plutot que de redeclarer l'hebergeur. Absent
|
|
# (depot sans underlay monte), le `stat` echoue simplement et rien n'est charge.
|
|
setops_proxmox_hebergeur: "{{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/proxmox-hebergeur.yml"
|
|
|
|
tasks:
|
|
- name: Verifier les fichiers de variables Proxmox optionnels
|
|
ansible.builtin.stat:
|
|
path: "{{ playbook_dir }}/../../instance/inventories/{{ item.0 }}/group_vars/{{ item.1 }}"
|
|
loop: "{{ setops_inventaires | product(setops_gv_proxmox) | list }}"
|
|
loop_control:
|
|
label: "{{ item.0 }}/{{ item.1 }}"
|
|
register: proxmox_fichiers_variables
|
|
|
|
- name: Charger les variables Proxmox non sensibles disponibles
|
|
ansible.builtin.include_vars:
|
|
file: "{{ item.stat.path }}"
|
|
loop: "{{ proxmox_fichiers_variables.results }}"
|
|
when:
|
|
- item.stat.exists
|
|
- item.stat.path is not search('vault')
|
|
|
|
- name: Localiser le fichier Proxmox de l'hebergeur
|
|
ansible.builtin.stat:
|
|
path: "{{ setops_proxmox_hebergeur }}"
|
|
register: proxmox_fichier_hebergeur
|
|
|
|
- name: Charger le cluster de l'hebergeur (autoritaire sur ses propres cles)
|
|
ansible.builtin.include_vars:
|
|
file: "{{ setops_proxmox_hebergeur }}"
|
|
when: proxmox_fichier_hebergeur.stat.exists
|
|
|
|
- name: Charger les secrets Proxmox Vault
|
|
block:
|
|
- name: Charger les secrets de la voute Proxmox
|
|
ansible.builtin.include_vars:
|
|
file: "{{ item.stat.path }}"
|
|
loop: "{{ proxmox_fichiers_variables.results }}"
|
|
when:
|
|
- item.stat.exists
|
|
- item.stat.path is search('vault')
|
|
no_log: true
|
|
rescue:
|
|
- name: Expliquer l echec de chargement du Vault
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Impossible de charger la voute Proxmox (group_vars/all/vault.yml ou
|
|
proxmox.vault.yml). Si elle est chiffree avec Ansible Vault, fournir son
|
|
mot de passe ou definir ANSIBLE_VAULT_PASSWORD_FILE avant de relancer make creer-vm.
|
|
|
|
- name: Appliquer les valeurs Proxmox avec repli environnement
|
|
ansible.builtin.set_fact:
|
|
proxmox_api_host_effectif: "{{ proxmox_api_host | default(lookup('env', 'PROXMOX_API_HOST'), true) }}"
|
|
proxmox_api_user_effectif: "{{ proxmox_api_user | default(lookup('env', 'PROXMOX_API_USER'), true) }}"
|
|
# `split('!')[-1]` : proxmoxer recompose lui-meme `utilisateur!nom` a partir de
|
|
# `api_user` et `api_token_id`. Lui passer la forme COMPLETE produit
|
|
# `ansible@pve!ansible@pve!nom` et un 401 muet — le meme jeton fonctionne en
|
|
# HTTP direct, ce qui rend le diagnostic trompeur. Verifie contre le cluster le
|
|
# 2026-08-03 : forme complete = 401, forme courte = OK. Sans effet si la voute
|
|
# stocke deja le nom seul, donc les deux ecritures restent acceptees.
|
|
proxmox_api_token_id_effectif: "{{ (proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true)) | string | split('!') | last }}"
|
|
proxmox_api_token_secret_effectif: "{{ proxmox_api_token_secret | default(lookup('env', 'PROXMOX_API_TOKEN_SECRET'), true) }}"
|
|
proxmox_api_port_effectif: "{{ proxmox_api_port | default(lookup('env', 'PROXMOX_API_PORT'), true) }}"
|
|
no_log: true
|
|
|
|
- name: Valider les parametres obligatoires Proxmox
|
|
ansible.builtin.assert:
|
|
that:
|
|
- proxmox_api_host_effectif | length > 0
|
|
- proxmox_api_user_effectif | length > 0
|
|
- proxmox_api_token_id_effectif | length > 0
|
|
- proxmox_api_token_secret_effectif | length > 0
|
|
- proxmox_clone_nom is defined
|
|
- proxmox_clone_nom | length > 0
|
|
- proxmox_clone_vmid_modele is defined
|
|
- proxmox_clone_vmid is defined
|
|
- proxmox_clone_noeud is defined
|
|
- proxmox_clone_noeud | length > 0
|
|
- proxmox_clone_ipconfig0 is defined
|
|
- proxmox_clone_ipconfig0 | length > 0
|
|
fail_msg: "Parametres Proxmox incomplets. Verifier proxmox.yml, la voute (all/vault.yml ou proxmox.vault.yml) et les variables make propres a la VM."
|
|
|
|
- name: Lire la cle publique SSH depuis le fichier fourni
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_sshkeys: "{{ lookup('ansible.builtin.file', proxmox_clone_cle_publique_fichier) }}"
|
|
when:
|
|
- proxmox_clone_cle_publique is not defined
|
|
- proxmox_clone_cle_publique_fichier is defined
|
|
- proxmox_clone_cle_publique_fichier | length > 0
|
|
|
|
- name: Utiliser la cle publique SSH fournie directement
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_sshkeys: "{{ proxmox_clone_cle_publique }}"
|
|
when:
|
|
- proxmox_clone_cle_publique is defined
|
|
- proxmox_clone_cle_publique | length > 0
|
|
|
|
- name: Construire les serveurs DNS Cloud-Init
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_nameservers: >-
|
|
{{
|
|
(proxmox_clone_dns | default('') | split(','))
|
|
| map('trim')
|
|
| reject('equalto', '')
|
|
| list
|
|
}}
|
|
|
|
- name: Verifier la bibliotheque Python proxmoxer locale
|
|
ansible.builtin.command:
|
|
cmd: "{{ ansible_playbook_python }} -c 'import proxmoxer'"
|
|
changed_when: false
|
|
failed_when: false
|
|
register: proxmoxer_verification
|
|
|
|
- name: Refuser si proxmoxer est absent
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
La bibliotheque Python proxmoxer est absente pour {{ ansible_playbook_python }}.
|
|
Installer le paquet systeme approprie, par exemple
|
|
sudo apt install python3-proxmoxer, ou installer proxmoxer dans
|
|
l'environnement Python utilise par Ansible, puis relancer make creer-vm.
|
|
when: proxmoxer_verification.rc != 0
|
|
|
|
- name: S'assurer que le pool du tenant existe
|
|
# Un pool par tenant : la console Proxmox affiche le NOM, et onze serveurs
|
|
# portent le meme d'un tenant a l'autre. Le pool restitue l'appartenance sans
|
|
# rien renommer. Il doit exister AVANT le clone — `proxmox_kvm` echoue sur un
|
|
# pool inconnu, et l'API ne sait pas changer le pool d'une VM deja creee.
|
|
community.general.proxmox_pool:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
poolid: "{{ proxmox_clone_pool }}"
|
|
comment: "Tenant Set-OPS — genere, ne pas renommer a la main"
|
|
state: present
|
|
when: proxmox_clone_pool | default('', true) | length > 0
|
|
|
|
# LE CLONE SE CIBLE PAR VMID, JAMAIS PAR NOM — et c'est tout l'objet de ces trois
|
|
# taches. `community.general.proxmox_kvm` cherche d'abord une VM portant le `name`
|
|
# demande ; s'il en trouve une, il conclut « elle existe deja », rend `ok` et ne
|
|
# clone RIEN. Aucune tache n'apparait meme cote cluster.
|
|
#
|
|
# Or les noms courts sont VOLONTAIREMENT identiques d'un tenant a l'autre (meme
|
|
# fonction, meme nom) : c'est le pool qui restitue l'appartenance. Le premier clone
|
|
# de Technolibre — `backup-01` — est donc tombe sur le `backup-01` de Chezlepro et
|
|
# n'a rien fait, puis l'attente a expire sur une configuration qui n'existerait
|
|
# jamais. Mesure du 2026-08-10 : zero VM creee, zero tache `qmclone` au cluster.
|
|
#
|
|
# Le defaut etait invisible tant qu'un seul tenant existait. C'est exactement ce
|
|
# qu'une epreuve de portabilite doit trouver.
|
|
- name: Recenser les VM du cluster (pour cibler par VMID)
|
|
vars:
|
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
|
ansible.builtin.uri:
|
|
url: "{{ proxmox_api_racine }}/api2/json/cluster/resources?type=vm"
|
|
method: GET
|
|
headers:
|
|
Authorization: >-
|
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
status_code: 200
|
|
register: proxmox_inventaire_vm
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Retenir si le VMID cible est deja pris
|
|
ansible.builtin.set_fact:
|
|
proxmox_clone_deja_la: >-
|
|
{{ (proxmox_inventaire_vm.json.data | default([]))
|
|
| selectattr('vmid', 'defined')
|
|
| selectattr('vmid', 'equalto', proxmox_clone_vmid | int)
|
|
| list | length > 0 }}
|
|
|
|
- name: Cloner la VM depuis le modele (API directe, cible par VMID)
|
|
vars:
|
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
|
ansible.builtin.uri:
|
|
url: "{{ proxmox_api_racine }}/api2/json/nodes/{{ proxmox_clone_noeud }}/qemu/{{ proxmox_clone_vmid_modele | int }}/clone"
|
|
method: POST
|
|
headers:
|
|
Authorization: >-
|
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
|
body_format: form-urlencoded
|
|
# Corps ecrit en MAPPING YAML, pas assemble en Jinja : une expression `>-` rend
|
|
# une CHAINE, et le premier jet a ainsi perdu le `pool` en route — la VM est
|
|
# nee hors de son pool, sans un mot. `omit` retire proprement les cles vides.
|
|
body:
|
|
newid: "{{ proxmox_clone_vmid | int }}"
|
|
name: "{{ proxmox_clone_nom }}"
|
|
full: "{{ 1 if (proxmox_clone_complet | default(true) | bool) else 0 }}"
|
|
storage: "{{ proxmox_clone_stockage | default(omit, true) }}"
|
|
format: "{{ proxmox_clone_format | default(omit, true) }}"
|
|
# A la CREATION seulement : l'API ne sait pas changer le pool d'une VM
|
|
# existante par cet appel (il faut passer par le membre du pool).
|
|
pool: "{{ proxmox_clone_pool | default(omit, true) }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
status_code: 200
|
|
register: proxmox_clone_lance
|
|
# Le corps ne porte aucun secret, mais l'en-tete si : `no_log` masquerait alors
|
|
# l'erreur de l'API, qui est justement ce qu'on veut lire. On ne journalise donc
|
|
# que l'ECHEC, et sans les en-tetes.
|
|
failed_when: false
|
|
no_log: true
|
|
when: not proxmox_clone_deja_la
|
|
|
|
- name: Dire pourquoi le clonage a echoue, s'il a echoue
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
Le clonage de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }}) depuis le
|
|
gabarit {{ proxmox_clone_vmid_modele }} a ete refuse par l'API Proxmox
|
|
(HTTP {{ proxmox_clone_lance.status | default('?') }}).
|
|
Verifier que le gabarit existe sur {{ proxmox_clone_noeud }}, que le pool
|
|
{{ proxmox_clone_pool | default('(aucun)', true) }} existe, et que le VMID
|
|
cible est libre.
|
|
when:
|
|
- not proxmox_clone_deja_la
|
|
- proxmox_clone_lance.status | default(0) != 200
|
|
|
|
# L'API de CLONAGE ignore `cores` et `memory` : le clone herite du gabarit, et le
|
|
# plan est contredit sans un mot. Mesure du 2026-08-06 : plan 1 coeur / 1024 Mo,
|
|
# VM creee avec 2 / 2048 — ceux du gabarit. Il faut donc les reposer APRES le clone.
|
|
# Le clone COMPLET rend la main avant que sa configuration ne soit ecrite : sur
|
|
# un stockage lent, `qm clone` retourne, et le `.conf` n'existe pas encore. La
|
|
# tache suivante echouait alors sur « Configuration file ... does not exist ».
|
|
# Observe deux fois sur quinze clones (2026-08-07) — une course, pas un hasard.
|
|
- name: Attendre que la configuration du clone existe
|
|
vars:
|
|
# Assemblee dans une variable : un `>-` replie les retours en ESPACES, ce qui
|
|
# inserait une espace au milieu de l'URL. Le lint passait, la requete non.
|
|
proxmox_api_racine: "https://{{ proxmox_api_host_effectif }}:{{ proxmox_api_port_effectif | default(8006, true) }}"
|
|
ansible.builtin.uri:
|
|
url: "{{ proxmox_api_racine }}/api2/json/nodes/{{ proxmox_clone_noeud }}/qemu/{{ proxmox_clone_vmid | int }}/config"
|
|
method: GET
|
|
headers:
|
|
Authorization: >-
|
|
PVEAPIToken={{ proxmox_api_user_effectif }}!{{ proxmox_api_token_id_effectif }}={{ proxmox_api_token_secret_effectif }}
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
status_code: 200
|
|
register: proxmox_clone_conf
|
|
until: proxmox_clone_conf.status == 200
|
|
retries: 30
|
|
delay: 5
|
|
changed_when: false
|
|
failed_when: false
|
|
no_log: true
|
|
|
|
# `no_log` masque la cause au moment ou on en a besoin : le 2026-08-10, l'echec de
|
|
# cette attente s'est lu « the output has been hidden », et il a fallu interroger le
|
|
# cluster a la main pour comprendre que le clone n'avait jamais eu lieu. On rend
|
|
# donc le verdict lisible ici, sans reveler l'en-tete d'autorisation.
|
|
- name: Dire ce que l'attente du clone a constate
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
La configuration de {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})
|
|
n'existe toujours pas apres {{ 30 * 5 }} s sur {{ proxmox_clone_noeud }}.
|
|
Le clonage n'a donc pas abouti — verifier les taches du cluster
|
|
(`/cluster/tasks`) : si aucune tache de clonage n'apparait, c'est que rien
|
|
n'a ete demande a l'API.
|
|
when: proxmox_clone_conf.status | default(0) != 200
|
|
|
|
- name: Appliquer le gabarit de calcul du plan (le clonage ne le fait pas)
|
|
community.general.proxmox_kvm:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
node: "{{ proxmox_clone_noeud }}"
|
|
vmid: "{{ proxmox_clone_vmid | int }}"
|
|
cores: "{{ proxmox_clone_coeurs | int }}"
|
|
memory: "{{ proxmox_clone_memoire | int }}"
|
|
update: true
|
|
# Le noeud vient de finir un clone COMPLET : son API repond encore lentement, et
|
|
# la lecture expire a 5 s. Mesure du 2026-08-10 : « Read timed out » sur le tout
|
|
# premier clone, VM creee mais laissee aux valeurs du gabarit — 2 coeurs / 2 Go au
|
|
# lieu du plan. Une VM sous-dimensionnee en silence est pire qu'un echec franc.
|
|
register: proxmox_gabarit_calcul
|
|
until: proxmox_gabarit_calcul is succeeded
|
|
retries: 6
|
|
delay: 10
|
|
when:
|
|
- proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0
|
|
- proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0
|
|
|
|
- name: Ajuster le reseau de la VM clonee
|
|
community.general.proxmox_nic:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
vmid: "{{ proxmox_clone_vmid | int }}"
|
|
interface: "{{ proxmox_clone_interface | default('net0') }}"
|
|
model: virtio
|
|
bridge: "{{ proxmox_clone_pont }}"
|
|
tag: "{{ proxmox_clone_vlan | int if proxmox_clone_vlan is defined and proxmox_clone_vlan | string | length > 0 else omit }}"
|
|
firewall: "{{ proxmox_clone_parefeu_interface | default(false) | bool }}"
|
|
# `mtu: 1` est la valeur Proxmox pour « HERITE DU PONT » (virtio uniquement).
|
|
#
|
|
# Sans elle, l'invite nait a 1500 quel que soit le pont. En SDN EVPN la zone est a
|
|
# 1450 (VXLAN coute 50 octets) : la VM emet donc des trames que son propre chemin
|
|
# ne peut pas encapsuler. Ca ne casse pas franchement — la connexion s'etablit,
|
|
# les petites requetes passent, les grosses reponses restent suspendues. C'est
|
|
# exactement la panne que le registre des flux decrit comme « la plus couteuse a
|
|
# diagnostiquer », et pourquoi il declare l'ICMP « fragmentation necessaire ».
|
|
#
|
|
# Mesure du 2026-08-10 : zones `t11` et `t17` a 1450 sur le cluster, les 14 invites
|
|
# a 1500. Invisible tant que toutes les VM vivent sur le MEME hyperviseur — elles
|
|
# communiquent alors par le pont local, sans encapsulation. La panne apparaitrait
|
|
# au premier eclatement de la flotte sur plusieurs noeuds.
|
|
#
|
|
# HERITER plutot qu'ecrire 1450 : hors SDN le pont est a 1500 et la VM suit. Une
|
|
# seule source de verite — celle du pont auquel elle est reellement attachee — et
|
|
# la valeur reste juste le jour ou la fabric passera aux trames jumbo.
|
|
#
|
|
# Le GABARIT le porte aussi (`net0 ... mtu=1`), ce qui couvre les clonages qui ne
|
|
# passent pas par ce playbook — un clone fait a la main dans l'interface, par
|
|
# exemple. On garde neanmoins la ligne ici : un gabarit se RECAPTURE (fait le
|
|
# 2026-08-09), et ce qui n'est pas versionne se perd en silence. Le playbook est
|
|
# la garantie qui survit a la recapture ; `make mtu-mesurer` verifie le resultat.
|
|
#
|
|
# Cette tache s'execute AVANT le demarrage du clone (voir « Demarrer le clone »
|
|
# plus bas) : aucun risque de rebranchement a chaud. Applique a une VM EN MARCHE,
|
|
# le meme changement detache et rebranche la carte sans que l'invite reconfigure
|
|
# son interface — 14 machines coupees d'un coup le 2026-08-10, et il a fallu les
|
|
# redemarrer. Sur une VM deja en service : poser la config, puis redemarrer.
|
|
mtu: 1
|
|
state: present
|
|
when:
|
|
- proxmox_clone_pont is defined
|
|
- proxmox_clone_pont | length > 0
|
|
|
|
- name: Agrandir le disque principal du clone (grow-only)
|
|
community.general.proxmox_disk:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
vmid: "{{ proxmox_clone_vmid | int }}"
|
|
disk: "{{ proxmox_clone_disque | default('scsi0') }}"
|
|
size: "{{ proxmox_clone_taille_disque }}"
|
|
state: resized
|
|
register: proxmox_redim_disque
|
|
# Le disque derive du plan est un MINIMUM. Si le template est deja plus grand,
|
|
# Proxmox refuse de retrecir : on tolere ce cas (grow-only), la VM garde le
|
|
# disque du template. Toute autre erreur reste bloquante.
|
|
failed_when:
|
|
- proxmox_redim_disque is failed
|
|
- "'shrinking disks is not supported' not in (proxmox_redim_disque.msg | default(''))"
|
|
when:
|
|
- proxmox_clone_taille_disque is defined
|
|
- proxmox_clone_taille_disque | length > 0
|
|
|
|
- name: Configurer Cloud-Init sur le clone
|
|
community.general.proxmox_kvm:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
node: "{{ proxmox_clone_noeud }}"
|
|
vmid: "{{ proxmox_clone_vmid | int }}"
|
|
name: "{{ proxmox_clone_nom }}"
|
|
update: true
|
|
ciuser: "{{ proxmox_clone_ciuser | default(omit, true) }}"
|
|
sshkeys: "{{ proxmox_clone_sshkeys | default(omit) }}"
|
|
ipconfig:
|
|
ipconfig0: "{{ proxmox_clone_ipconfig0 }}"
|
|
nameservers: "{{ proxmox_clone_nameservers if proxmox_clone_nameservers | length > 0 else omit }}"
|
|
searchdomains: "{{ proxmox_clone_domaines_recherche | default(omit, true) }}"
|
|
agent: "enabled=1"
|
|
|
|
- name: Demarrer le clone
|
|
community.general.proxmox_kvm:
|
|
api_host: "{{ proxmox_api_host_effectif }}"
|
|
api_port: "{{ proxmox_api_port_effectif | int if proxmox_api_port_effectif | length > 0 else omit }}"
|
|
api_user: "{{ proxmox_api_user_effectif }}"
|
|
api_token_id: "{{ proxmox_api_token_id_effectif }}"
|
|
api_token_secret: "{{ proxmox_api_token_secret_effectif }}"
|
|
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
|
|
node: "{{ proxmox_clone_noeud }}"
|
|
vmid: "{{ proxmox_clone_vmid | int }}"
|
|
name: "{{ proxmox_clone_nom }}"
|
|
state: started
|
|
when: proxmox_clone_demarrer | default(true) | bool
|
|
|
|
- name: Afficher le resume du clone
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "VM creee: {{ proxmox_clone_nom }} (VMID {{ proxmox_clone_vmid }})"
|
|
- "Pool: {{ proxmox_clone_pool | default('(aucun)', true) }}"
|
|
- "Cloud-Init ipconfig0: {{ proxmox_clone_ipconfig0 }}"
|