De Aanname Die Ansible Doorgaans Mag Maken
Bijna elke Ansible-module die je gebruikt hebt werkt zo: Ansible verbindt met de host die in de inventory genoemd wordt, kopieert er een klein Python-programma naartoe, draait het, en leest het resultaat terug. De host is het ding dat veranderd wordt en het ding dat het werk doet.
Een virtuele machine maken breekt dat op de meest basale manier mogelijk. De host die je bouwt bestaat niet. Hij heeft geen IP, geen SSH-daemon, geen Python, en geen besturingssysteem. Er is niks om mee te verbinden.
Dus community.proxmox is geen configuratie-agent. Het is een API-client die toevallig als Ansible-collectie geleverd wordt. Alles in deze post volgt dan ook uit dat ene feit — waar de tasks draaien, hoe je credentials binnenkrijgt, waarom herdraaien niet doet wat je verwacht, en waarom --check je niet de waarheid vertelt.
De voorbeelden hier zijn ingekort uit een playbook die Windows- en Linux-VM’s bouwt vanuit NetBox-records: proxmox-create-vms.yml. Ik heb de node-, storage- en bridge-namen gegenericeerd voor leesbaarheid — het echte ding staat in die repository.
Alles wat ik hieronder over modulegedrag beweer, is gecontroleerd tegen community.proxmox 1.6.0, de versie die ik geïnstalleerd heb:
$ ansible-galaxy collection list community.proxmox
# /home/damien/.ansible/collections/ansible_collections
Collection Version
----------------- -------
community.proxmox 1.6.0
Eerst: De Collectie Is Verhuisd
Als je een oudere playbook of een ouder antwoord leest, heetten de modules community.general.proxmox_kvm. Ze leven nu in een toegewijde collectie, en dat is waar de ontwikkeling gebeurt. Versie 1.6.0 levert 47 modules, die Ceph, SDN, firewall, HA-regels en cluster-join dekken, waarvan geen bestond in het community.general-tijdperk.
ansible-galaxy collection install community.proxmox
pip install 'proxmoxer>=2.0' requests
De Python-afhankelijkheid is niet optioneel en niet gebundeld: de collectie declareert requirements: ["proxmoxer >= 2.0", "requests"], en die moeten geïnstalleerd worden waar de module werkelijk uitvoert — wat, zoals de volgende sectie uitlegt, niet de Proxmox-node is.
requirements.yml, als je het liever vastpint:
---
collections:
- name: community.proxmox
version: ">=1.6.0"
De collectie is getest tegen ansible-core 2.17 tot en met 2.20. Je community.general.proxmox_*-tasks hernoemen naar community.proxmox.proxmox_* is het grootste deel van de migratie.
Elke Proxmox-Task Draait op localhost
Twee regels bovenaan de play doen het zware werk, en beide zien eruit alsof ze iets nuttigs uitschakelen:
- name: Create Proxmox virtual machines
hosts: "{{ target_hosts | default('cluster_pve:&status_planned') }}"
gather_facts: false
serial: 1
gather_facts: false is geen optimalisatie. Fact gathering verbindt met de inventory-host, en de inventory-host is een VM die nog niet gebouwd is. Laat het aan en de play faalt vóór de eerste task.
Dan draagt elke Proxmox-task delegate_to: localhost:
- name: Create Proxmox VM
delegate_to: localhost
register: created_vm
community.proxmox.proxmox_kvm:
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
api_host: "{{ proxmox_api_ip }}"
name: "{{ inventory_hostname }}"
node: "{{ proxmox_api_host }}"
...
De inventory-host is nu slechts een naam en een zak variabelen. inventory_hostname wordt de naam van de VM; zijn variabelen beschrijven de machine die je wilt. Niets verbindt ermee. De task draait op de control-node, die een HTTPS-sessie opent naar api_host en een VM-definitie post.
Je zult dit ook geschreven zien als local_action:, wat de oudere syntaxis is voor hetzelfde. De teardown-playbook in die repository gebruikt het overal. Ze zijn equivalent. delegate_to is de huidige spelling.
Het Noodluik Wijst Naar De Node
Sommige dingen moeten werkelijk op een Proxmox-host gebeuren, en die tasks delegeren ergens anders naartoe:
- name: Fail if no ISO file exists for the OS
delegate_to: "{{ proxmox_api_host }}"
ansible.builtin.stat:
path: "{{ iso | replace('isos:', '/mnt/pve/isos/template/') }}"
register: iso_file
failed_when: not iso_file.stat.exists
Dat is een echte SSH-verbinding naar een echte node, die een echt pad op gedeelde storage controleert, want de API accepteert graag een ISO-referentie die niet naar een bestand resolveert en je komt het liever nu te weten dan bij boot. Let op de string-chirurgie die een PVE-storage-referentie (isos:iso/debian.iso) vertaalt naar een filesystem-pad. De storage-abstractie is niet beschikbaar voor stat.
Dus een enkele play heeft tasks die op drie verschillende plekken uitvoeren, en ze door elkaar halen is de meest voorkomende manier waarop deze playbooks falen:
qm-noodluik is het enige deel dat SSH naar een hypervisor nodig heeft.Credentials, en een Standaard Die Op Het Punt Staat Te Veranderen
De auth-opties worden door elke module in de collectie gedeeld via een documentatiefragment, dus ze zijn overal hetzelfde: api_host, api_user, en dan ofwel api_password of het paar api_token_id / api_token_secret. Ze vallen allemaal terug op omgevingsvariabelen — PROXMOX_HOST, PROXMOX_USER, PROXMOX_PASSWORD, PROXMOX_TOKEN_ID, PROXMOX_TOKEN_SECRET, PROXMOX_VALIDATE_CERTS — wat de schoonste manier is om secrets helemaal buiten de play te houden.
Een API-token is de betere standaard. Het is scoped, het is intrekbaar zonder het wachtwoord van een mens te veranderen, en het kan precies de rechten krijgen die de playbook nodig heeft in plaats van die welke een persoon toevallig heeft:
- name: Create Proxmox VM
delegate_to: localhost
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
validate_certs: true
ca_path: /etc/ssl/certs/pve-cluster-ca.pem
Zet validate_certs expliciet, vandaag. De eigen documentatie van de collectie zegt het ronduit:
Currently defaults to
falseand changes default totruewith community.proxmox 2.0.0.
Wat betekent dat een playbook die het nooit noemt op dit moment geen TLS valideert, en zal beginnen te valideren — en dus zal beginnen te falen tegen het zelfondertekende certificaat dat elke verse Proxmox-installatie levert — op het moment dat iemand --upgrade draait. Beter om die beslissing met opzet te maken dan hem midden in een build te laten landen. Als je het zelfondertekende certificaat houdt, zeg validate_certs: false en neem de bevinding; als je een echte keten hebt, wijs ca_path ernaar. Hoe dan ook is het opgeschreven.
De Minimaal Levensvatbare Create
Kleed de productie-task uit tot wat werkelijk een machine definieert en het is leesbaar:
- name: Create Proxmox VM
delegate_to: localhost
register: created_vm
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
validate_certs: true
node: pve1
name: "{{ inventory_hostname }}"
cores: "{{ vcpus | int }}"
memory: "{{ memory }}"
machine: q35
bios: ovmf
ostype: l26
scsihw: virtio-scsi-single
scsi:
scsi0: "vmdata:32,format=qcow2,discard=on,ssd=1"
sata:
sata0: "isos:iso/debian-13-netinst.iso,media=cdrom"
net:
net0: "virtio,bridge=vmbr0"
efidisk0:
storage: vmdata
format: raw
efitype: 4m
pre_enrolled_keys: true
boot: "order=scsi0;sata0"
agent: "enabled=1,fstrim_cloned_disks=1"
onboot: true
tags:
- production
Een paar dingen over die vorm zijn de moeite waard te weten voordat je je eigen schrijft.
De apparaatopties zijn PVE-syntaxis binnen YAML. scsi, sata, net, virtio, ide zijn allemaal getypeerd dict, gekeyed scsi0, net0 enzovoort, en de waarden zijn de komma-gescheiden optiestrings rechtstreeks uit man qm — <storage>:<size>,option=value voor een schijf, [model=]<enum>,option=value voor een NIC. De module modelleert ze niet; hij stuurt ze door. Wanneer iets geweigerd wordt, staat het antwoord in de PVE-optiereferentie, niet in de Ansible-docs.
Je zult deze ook geschreven zien als een JSON-string in plaats van een YAML-mapping:
net: '{"net0":"virtio,bridge={{ vlan_bridge }}"}'
Beide werken. Ansible dwingt de string af voor een dict-getypeerde parameter. De JSON-vorm bestaat omdat het makkelijker is een hele structuur in één Jinja-expressie te templaten. De mapping-vorm is zes maanden later makkelijker te lezen.
boot heeft twee generaties syntaxis. De module accepteert de legacy-letters, waar boot: "cdn" betekent “probeer schijf, dan cd-rom, dan netwerk”. Huidig PVE wil een expliciete geordende lijst — boot: "order=scsi0;sata0;net0" — wat ondubbelzinnig is over welke schijf. De legacy-vorm werkt nog. De expliciete vorm is wat je wilt in nieuw werk. Eén echte valstrik verstopt in de moduledocs: netwerkboot vereist het zetten van rng0 sinds PVE 8.3.5.
numa en numa_enabled zijn verschillende parameters. numa_enabled is de boolean die NUMA aanzet. numa is een dict die een topologie beschrijft (cpus, hostnodes, memory, policy). numa: true zetten is een type-fout, en het is een makkelijk uur om te verliezen. Als je erom geeft waarom iets hiervan uitmaakt, NUMA-uitlijning op Proxmox behandelt het onderliggende probleem.
machine: q35 en bios: ovmf zijn de juiste standaarden, geen decoratie — dat argument in het geheel.
vmid weglaten betekent dat de module de API om de volgende vrije ID vraagt. Handig, en de directe oorzaak van het volgende.
Waarom serial: 1
“Haal de volgende beschikbare ID op, maak dan een VM ermee” is twee API-aanroepen met een gat in het midden. Twee workers die dat gelijktijdig doen kunnen dezelfde vrije ID lezen, en de verliezer krijgt een fout of, erger, een verrassing.
serial: 1 maakt de create-fase één host per keer. Het is niet snel en het hoeft niet snel te zijn. Het dure deel van een VM bouwen gebeurt nadat deze playbook overdraagt. De tegenhanger-play die de voltooide VM’s start gebruikt serial: 5, want starten heeft geen gedeelde teller om op te racen.
Als je liever de parallelliteit hebt, wijs de VMID zelf toe vanuit je bron van waarheid en geef hem expliciet door. Dan is er geen read-modify-write en geen race.
Idempotentie Is Niet Wat Je Verwacht
Dit is de sectie om twee keer te lezen, want proxmox_kvm gedraagt zich niet als ansible.builtin.package.
name is geen identiteit. VM-namen zijn niet uniek over een Proxmox-cluster, en de module zegt het. Met state: present en geen vmid, als een VM met die naam al bestaat, exit de module changed=false met msg: "VM with name <x> already exists" en doet niks. Hij vergelijkt je parameters niet met de werkelijkheid. Hij convergeert niet. Hij weigert.
update staat standaard op false. Dus memory: bewerken in je playbook en herdraaien is een no-op. De VM houdt het geheugen waarmee hij gebouwd werd, de task meldt succes, en niets ergens vertelt je dat de twee uiteenlopen.
update: true weigert nog steeds de interessante parameters. Uit de moduledocumentatie:
Because of the operations of the API and security reasons, I have disabled the update of the following parameters
net,virtio,ide,sata,scsi. Per example updatingnetupdate the MAC address andvirtiocreate always new disk…
update_unsafe: true heft die beperking op, en de waarschuwing is niet voor de show:
Use this option with caution because an improper configuration might result in a permanent loss of data (for example disk recreated).
Dus de schijf waarvan je dacht dat je hem vergrootte kan vervangen worden door een nieuwe lege. Grijp hier niet naar — de geweigerde parameters hebben hun eigen modules, en dat is de volgende sectie.
En --check dekt niets hiervan. De collectie declareert check-mode-ondersteuning per module, en die is inconsistent in precies de verkeerde richting:
| Module | check_mode | diff_mode |
|---|---|---|
proxmox_kvm | geen | geen |
proxmox_disk | geen | geen |
proxmox_template | geen | geen |
proxmox_snap | volledig | geen |
proxmox_nic | volledig | geen |
proxmox_pool | volledig | geen |
De splitsing is niet “read-only-modules kunnen het, write-modules niet” — proxmox_nic maakt en verwijdert interfaces en honoreert check-mode prima. Het is dat de drie modules die met storage en VM-levenscyclus omgaan dat niet doen. Een --check-run van een build-playbook slaat de VM-aanmaak stilletjes over en rapporteert dan over een wereld waarin de VM nooit gemaakt werd, dus elke task erna redeneert over de verkeerde toestand. Op een build-playbook is --check geen vangnet, en het als zodanig behandelen is erger dan het niet draaien.
Maak Dus Bestaan De Poort
Gezien dat alles is het werkbare patroon om te stoppen de module te vragen idempotent te zijn en zelf te beslissen of je bouwt. De productie-playbook doet het zo:
- name: Check if VM is present or manually built
delegate_to: localhost
community.proxmox.proxmox_vm_info:
api_host: "{{ proxmox_api_ip }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
name: "{{ inventory_hostname }}"
config: current
register: existing_vm
ignore_errors: true
failed_when: (existing_vm.proxmox_vms | length) == 0
- name: Configure Proxmox VM
when: existing_vm is failed
block:
- name: Create Proxmox VM
...
proxmox_vm_info met config: current retourneert de VM en zijn live configuratie, of een lege lijst. failed_when verandert “lege lijst” in een falen, ignore_errors: true stopt dat falen ervan de play te beëindigen, en when: existing_vm is failed wordt “de VM is er niet, bouw hem”.
Een opzettelijk gefaalde task als boolean gebruiken leest slecht, en ik ga niet doen alsof anders. Het alternatief is when: (existing_vm.proxmox_vms | default([]) | length) == 0, wat eerlijk is over een lengtecontrole te zijn en geen ignore_errors nodig heeft. Beide werken. De versie hierboven is wat in productie staat, en zijn ene echte voordeel is dat het geregistreerde resultaat de bestaande configuratie draagt voor latere tasks om te lezen.
Het belangrijke deel is de vorm, niet de spelling: controleer, vertak dan, en behandel aanmaak als een eenmalige gebeurtenis. De doorlopende configuratie van een VM is een ander probleem dan het bestaan van een VM, en deze module is alleen goed in de tweede.
Schijven en NIC’s Hebben Hun Eigen Modules
Hier is het ding dat ik hierboven overheen praatte, en het verandert het hele beeld: de parameters die proxmox_kvm weigert te updaten zijn geen gat in de collectie. Ze zijn gedelegeerd. community.proxmox.proxmox_disk en community.proxmox.proxmox_nic voegen precies de dingen toe, veranderen en verwijderen die de create-module niet aanraakt — gekeyed op dezelfde scsi0- en net0-namen die je gebruikte toen je de VM bouwde.
Beide gedragen zich beter dan proxmox_kvm, en een ervan is de enige module in deze workflow die dry-run kan.
proxmox_nic — Een Interface Toevoegen, Hertaggen of Verwijderen
- name: Move the VM's primary NIC to a new bridge and VLAN
delegate_to: localhost
community.proxmox.proxmox_nic:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
vmid: "{{ created_vm.vmid }}"
interface: net0
bridge: vmbr1
tag: 120
model: virtio
mtu: 1
queues: 4
firewall: true
state: present
interface is de enige vereiste optie naast auth — net[n] waar n 0 tot 31 is — en state: present of absent geeft je toevoegen en verwijderen. model staat standaard op virtio, wat het juiste antwoord is tenzij een gast er niet mee omkan.
De reden dat deze module bestaat is het MAC-adres. Herinner je waarom proxmox_kvm weigert net te updaten: “updating net update the MAC address”. proxmox_nic repareert dat expliciet:
When not specified this module will keep the MAC address the same when changing an existing interface.
Dus je kunt een VLAN hertaggen, een bridge verplaatsen, de MTU veranderen of de firewall aanzetten zonder dat de NIC-identiteit van de gast eronder verandert. Dat doet er meer toe dan het klinkt: een nieuwe MAC ongeldig maakt DHCP-reserveringen, breekt alles wat aan een NIC gelicentieerd is, en desynchroniseert het NetBox-interface-record dat de create-playbook zo zorgvuldig schreef. Dit is de module die een dag-2-netwerkwijziging saai laat zijn.
Een paar opties de moeite waard te kennen voordat je ze nodig hebt:
rateis in MBps — MegaBytes per seconde, geen bits. De documentatie is expliciet en de factor-acht-fout is heel makkelijk te maken.link_down: truekoppelt de interface los, in de docs beschreven als “like pulling the plug”. Een schone manier om een verdachte VM te isoleren zonder hem te stoppen of de gast aan te raken.trunksneemt een lijst van VLAN-ID’s om door te laten, voor een gast die zijn eigen tagging doet.mtu: 1is geen typefout en geen MTU van 1 byte — het betekent “erf de bridge-MTU”, en het geldt alleen voorvirtio.queueszet multiqueue, 0 tot 16. De moeite waard af te stemmen op vCPU-aantal op alles dat echt verkeer duwt.
En het ondersteunt check-mode volledig. --check op een proxmox_nic-task vertelt je de waarheid, wat het het ene deel van deze workflow maakt dat je veilig kunt repeteren. Zijn berichten zijn ook netjes idempotent. Een onveranderde interface meldt Nic net0 unchanged on VM with vmid 103 in plaats van een wijziging te claimen.
proxmox_disk — De Hele Schijf-Levenscyclus
proxmox_disk is de grootste module van de drie, en zijn state doet vijf verschillende klussen:
state | Wat er gebeurt | Omkeerbaar? |
|---|---|---|
present | maak de schijf, of update opties op een bestaande | n.v.t. |
resized | vergroot hem — PVE kan niet krimpen, en de docs zeggen doe dat handmatig | nee |
detached | wordt unused[n]; het volume en zijn data blijven | ja |
moved | verander backing-storage, of geef de schijf aan een andere VM | origineel behouden tenzij delete_moved |
absent | verwijderd uit backing-storage | nee |
Het gat tussen detached en absent is het vangnet dat proxmox_kvm je nooit geeft. Loskoppelen is een configwijziging; verwijderen vernietigt data. Twee verschillende woorden, twee verschillende gevolgen.
Een tweede schijf toevoegen aan een VM die al bestaat:
- name: Add a data disk
delegate_to: localhost
community.proxmox.proxmox_disk:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
vmid: "{{ created_vm.vmid }}"
disk: scsi1
storage: vmdata
size: 200
format: qcow2
iothread: true
aio: io_uring
discard: "on"
ssd: true
backup: true
state: present
create is de knop die proxmox_kvm had moeten hebben. Het bepaalt wat state: present mag doen:
regular(de standaard) — maak de schijf als hij ontbreekt, update anders zijn opties.disabled— update alleen opties, en maak nooit aan. Dit is degene om naar te grijpen wanneer jecacheofiothreadverandert op een schijf die al moet bestaan. Hij kan je niet verrassen door een nieuw volume tevoorschijn te toveren omdat een key verkeerd gespeld werd.forced— maak altijd aan. Een bestaande schijf wordt losgekoppeld en ongebruikt gelaten, niet verwijderd.
Dat laatste gedrag is het belangrijke detail. create: forced is de destructief-ogende optie, en hij vernietigt nog steeds niets: het oude volume overleeft als unusedN en je kunt het opnieuw koppelen. Vergelijk dat met proxmox_kvm plus update_unsafe, wiens gedocumenteerde faalmodus een herschapen schijf is. Dezelfde ruwe operatie, veel betere blast radius.
Een schijf vergroten:
- name: Grow the data disk by 100 GiB
delegate_to: localhost
community.proxmox.proxmox_disk:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
vmid: "{{ created_vm.vmid }}"
disk: scsi1
size: "+100G"
state: resized
Let op de eenheden, want size verandert van betekenis met state. Met state: present is het GiB als kaal getal (size: 200). Met state: resized neemt het een suffix — +100G om aan de huidige grootte toe te voegen, of 500G als absoluut doel. Eén parameter, twee conventies, en het falen is stil als je fout raadt.
Een schijf naar andere storage verplaatsen, het live-migratie-van-één-volume-geval:
- name: Move the disk to NVMe storage
delegate_to: localhost
community.proxmox.proxmox_disk:
api_host: "{{ proxmox_api_ip }}"
api_user: ansible@pve
api_token_id: automation
api_token_secret: "{{ proxmox_token_secret }}"
vmid: "{{ created_vm.vmid }}"
disk: scsi1
target_storage: nvme-pool
bwlimit: 200000
delete_moved: true
timeout: 3600
state: moved
target_storage verplaatst binnen één VM; target_vmid geeft de schijf aan een andere VM en vereist dezelfde storage op beide. Ze sluiten elkaar uit. delete_moved staat standaard op false, dus standaard eindig je met twee kopieën en het origineel dat er ongebruikt bij staat — veilig, en een goede manier om een storage-pool te vullen als je er nooit op terugkomt.
timeout staat hier standaard op 600, tegen 30 in proxmox_kvm. Dezelfde-ogende parameter, twintigvoudig verschil, want deze operaties kopiëren data. Verhoog hem voor grote images of trage storage — de docs zeggen het voor zowel moved als import_from.
Wat de optie oproept die deze module het V2V- en cloud-image-pad maakt:
import_from: "vmdata:9000/base-debian13.qcow2"
import_from bouwt de schijf vanuit een bestaand volume in plaats van een leeg toe te wijzen — <STORAGE>:<VMID>/<NAME>, of <STORAGE>:import/<NAME> met de storage-import-directory op PVE 9.x en later. Het sluit elkaar uit met size, en alleen root kan absolute filesystem-paden gebruiken.
De rest van de parameterlijst is de reden om schijven met deze module te koppelen in plaats van inline in de create-aanroep: cache, aio, iothread, discard, ssd, backup, detect_zeroes, en de volledige throttling-familie — iops, iops_rd, iops_wr, hun _max- en _max_length-varianten, en de bps_*_max_length-burst-controles. Niets daarvan is bereikbaar via proxmox_kvm na aanmaak.
Twee kanttekeningen, beide uit de eigen documentatie van de module:
- Sommige optiewijzigingen hebben een reboot nodig. “Some updates on options (like
cache) are not being applied instantly and require VM restart.” Een groene task betekent dat de config geschreven werd, niet dat de draaiende VM zich anders gedraagt. - Het ondersteunt geen check-mode.
check_mode: none, hetzelfde alsproxmox_kvm. Dus de collectie splitst in het midden: NIC-wijzigingen kunnen gerepeteerd worden met--check, schijfwijzigingen niet.
De Verdeling Van Het Werk
| Om dit te doen | Gebruik |
|---|---|
| De VM maken | proxmox_kvm, één keer, gepoortd op bestaan |
| Cores, geheugen, tags, agent, onboot veranderen | proxmox_kvm met update: true |
| Een NIC toevoegen, hertaggen, loskoppelen of verwijderen | proxmox_nic |
| Een schijf toevoegen, vergroten, verplaatsen, loskoppelen of verwijderen | proxmox_disk |
| Snapshot | proxmox_snap (ook volledige check-mode) |
| Alles wat geen ervan blootlegt | qm set over SSH |
Een schijf of NIC via proxmox_kvm veranderen | niets — dit is waar update_unsafe voor is, en het is waarom je het niet zou moeten gebruiken |
Bouw de VM met een minimale proxmox_kvm-aanroep, koppel dan de schijven en interfaces met hun eigen modules. Het zijn meer tasks, en het is de versie waar dag-2-wijzigingen een route hebben die geen optie betrekt wiens gedocumenteerde risico een schijf verliezen is.
Waar De Module Ophoudt
proxmox_kvm heeft een enorme parameterlijst en dekt nog steeds niet alles wat qm kan. In plaats van te wachten, zakt de productie-playbook naar de CLI op de node:
- name: Set RNG source and better SPICE quality
delegate_to: "{{ proxmox_api_host }}"
become: true
ansible.builtin.command:
cmd: >-
/usr/sbin/qm set {{ created_vm.vmid }}
--rng0 source=/dev/urandom
--spice_enhancements videostreaming=all
Er is niets mis met dit. Het is niet idempotent in enige betekenisvolle zin — qm set is een write, en het zal elke run changed melden — maar het is expliciet, het is leesbaar, en het doet niet alsof. Als een module de parameter later krijgt, verwijder je de task.
Andere dingen hebben een tweede doorgang door de module met update: true nodig, want ze kunnen niet gezet worden in dezelfde aanroep die de VM maakt:
- name: Add SPICE-compatible USB device
delegate_to: localhost
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_ip }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
node: "{{ proxmox_api_host }}"
vmid: "{{ created_vm.vmid }}"
usb:
usb0: "spice,usb3=1"
update: true
when: spice_usb | default(false)
Merk op dat het vmid doorgeeft, geen name. Zodra je de ID hebt, gebruik hem. Het is de enige identifier die de API als uniek behandelt.
En voor de dingen die QEMU kan die PVE geen optie voor heeft, is er args, dat verbatim aan de QEMU-commandoregel wordt doorgegeven:
args: >-
-global scsi-hd.physical_block_size=4k
-global scsi-hd.logical_block_size=4096
Die presenteert de virtuele schijf als 4Kn in plaats van 512e, wat er meer toe doet dan het klinkt — blokgroottes, 4Kn en 512e. De module labelt args “for experts only”, en de reden is dat PVE het niet valideert en een slechte flag de VM stopt met booten met een fout die van QEMU komt in plaats van van Proxmox.
Het Cluster Is Niet Direct Consistent
- name: Let registration complete on cluster
ansible.builtin.pause:
seconds: 5
when: created_vm.changed
Een pause in een playbook is doorgaans een luchtje, en deze is dragend. De create-aanroep keert terug wanneer de API de definitie heeft geaccepteerd, wat niet hetzelfde is als elke node die het erover eens is dat de VM bestaat — en de allereerstvolgende task wil een ACL zetten op /vms/<vmid>. Vijf seconden geduld is goedkoper dan een retry-lus rond een fout die alleen onder belasting opduikt.
De teardown-playbook heeft dezelfde vorm om dezelfde reden: stop, wacht, verwijder dan.
Teruglezen Wat Je Bouwde
proxmox_kvm documenteert drie returnwaarden: vmid, status en msg. In de praktijk wil je een vierde, en die staat niet in de documentatie.
- name: Get MAC address of VM
ansible.builtin.set_fact:
primary_mac_addr: "{{ created_vm.mac.net0 }}"
created_vm.mac is echt — de module bouwt het in get_vminfo() en splat het in het resultaat — maar het is afwezig uit het gedocumenteerde RETURN-blok, wat betekent dat niets belooft dat het zal blijven werken. De moeite waard precies te weten hoe het zich gedraagt, want er zitten twee vallen in:
- Het verschijnt alleen wanneer de module de VM werkelijk maakte.
macwordt alleen samengesteld op het create-and-deploy-pad. Neem de “bestaat al”-vertakking en het resultaat heeftvmidenmsgen niets anders. - Het bevat alleen de interfaces die je doorgaf. De code doorloopt de parameters die jij leverde en kiest degene die matchen op
net[0-9], en leest dan de opgeslagen config van elk terug uit de API. Geennet-parameter, geenmac-key.
Wat is waarom de productie-playbook beide helften nodig heeft, en de tweede is lelijk:
- name: Get MAC address of VM
ansible.builtin.set_fact:
primary_mac_addr: >-
{{ created_vm.mac.net0 if created_vm is defined and created_vm.changed
else ((existing_vm.proxmox_vms[0].config.net0 | split(','))[0] | split('='))[1] }}
Wanneer de VM al bestond, is er geen mac, dus het MAC moet uit de raw config-string gegraven worden. net0 komt terug van de API als virtio=AE:AE:5C:A8:89:85,bridge=vmbr0, dus: splits op komma’s, neem het eerste veld, splits op =, neem de tweede helft. Het is string-chirurgie op een API-response, en het is de eerlijke kostenpost van een module wiens returnvorm afhangt van welke vertakking hij nam.
Als je het MAC betrouwbaar nodig hebt in beide gevallen, haal het onvoorwaardelijk uit proxmox_vm_info en parseer één vorm in plaats van twee.
Het Aansturen Vanuit Een Bron Van Waarheid
Kijk nog eens naar de regel die de play opent:
hosts: "{{ target_hosts | default('cluster_pve:&status_planned') }}"
Dat is de werkelijke architectuur, en het is de moeite waard ronduit te stellen: de VM’s om te bouwen zijn geen lijst in een vars-bestand. Het zijn de hosts in je inventory waarvan de vastgelegde status zegt dat ze zouden moeten bestaan en dat nog niet doen.
De inventory hier is NetBox. Een VM wordt aangevraagd door een NetBox-record met status planned te maken, dat zijn CPU, geheugen, schijf, VLAN, eigenaar en platform draagt. De playbook selecteert planned-machines, bouwt ze, wijst een IP toe, schrijft DNS, en zet dan het record op staged — op welk punt die host niet langer matcht met het host-patroon van de play, en een handler de inventory ververst zodat de volgende play de nieuwe toestand ziet:
handlers:
- name: Refresh inventory
ansible.builtin.meta: refresh_inventory
Het statusveld is een toestandsmachine, de playbook is één overgang erin, en het geheel is herdraaibaar omdat een host die al verder is gegaan niet langer geselecteerd wordt. Dat is een veel betere eigenschap dan enige hoeveelheid idempotentie op moduleniveau, en het is de reden dat de create-task ermee wegkomt een one-shot te zijn.
community.proxmox levert ook zijn eigen inventory-plugin, die een inventory bouwt vanuit het cluster — de juiste keuze wanneer Proxmox de bron van waarheid is. Hier is het andersom: NetBox is gezaghebbend en Proxmox is waar zijn intentie gerealiseerd wordt. Dat is een hele post op zichzelf en die schrijf ik apart.
Het Weer Weghalen
Aanmaak zonder teardown is een halve levenscyclus, en het verwijderingspad heeft zijn eigen val — je kunt geen draaiende VM verwijderen:
- name: Force stop the VM if it is running
delegate_to: localhost
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_ip }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
name: "{{ inventory_hostname }}"
state: stopped
force: true
timeout: 10
- name: Allow the cluster to stop the VM before removing it
ansible.builtin.pause:
seconds: 10
- name: Remove the VM from the cluster
delegate_to: localhost
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_ip }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
name: "{{ inventory_hostname }}"
state: absent
force: true
timeout: 10
state: stopped is een gracieuze afsluiting, en de interactie met timeout is gedocumenteerd en de moeite waard te onthouden: als de timeout wordt bereikt met force: true wordt de VM hard uitgeschakeld; met force: false faalt de task in plaats daarvan. Een gracieus venster van tien seconden gevolgd door een ruk aan de stekker is een redelijk beleid voor een machine die afgebroken wordt, en een verschrikkelijk voor iets anders.
De volledige teardown (proxmox-remove-vms.yml) ontrafelt dan de rest van het record: DNS, het toegewezen IP, de NetBox-interfaces, de NetBox-VM, en de verouderde entries in known_hosts. Het wikkelt het blok in ignore_errors: true, wat verdedigbaar is in een teardown. Je verwijdert dingen die mogelijk al weg zijn, en een half-verwijderde machine is erger dan een luidruchtige log.
Wat Ik Zou Veranderen In Een Verse Build
Nadat ik de modulebron heb gelezen in plaats van alleen zijn documentatie, vier dingen:
- Gebruik een API-token, geen
api_userplusapi_password. Scoped, intrekbaar, en het behoort nooit toe aan een persoon. - Zet
validate_certsexpliciet, voordat 2.0.0 het onder je verandert. - Wijs de VMID zelf toe vanuit de bron van waarheid. Het verwijdert de read-modify-write-race, laat je
serial: 1laten vallen, en geeft elke latere task een stabiele identifier in plaats van een naam die niet uniek is. - Maak de VM kaal, koppel dan zijn schijven en NIC’s met
proxmox_diskenproxmox_nic. Meer tasks, maar elke schijf en interface heeft dan een module die het later kan veranderen — inclusiefcreate: disabledvoor optie-only-bewerkingen enstate: detachedin plaats van verwijdering — in plaats van een config die alleen viaupdate_unsafeveranderd kan worden. - Haal het MAC uit
proxmox_vm_infoop één plek, zodat er één vorm te parseren is in plaats van een conditionele over een gedocumenteerde en een ongedocumenteerde returnwaarde.
En grijp niet naar --check op een build-playbook. De module die ertoe doet kan het niet honoreren.
Een dry-run die altijd ja zegt is erger dan helemaal geen dry-run, want je zult hem geloven.
Referenties
- community.proxmox collection docs — het volledige 47-module-oppervlak
- community.proxmox op GitHub — waar de bron hierboven leeft;
plugins/modules/proxmox_kvm.pyis het bestand om te lezen wanneer de docs dubbelzinnig zijn proxmox_kvm-moduledocumentatie — de parameterlijst, en deupdate/update_unsafe-waarschuwingen hierboven geciteerdproxmox_vm_info-moduledocumentatie —config: currentenconfig: pending- PVE
qm-optiereferentie — de echte specificatie voor elkescsi[n]-,net[n]- enboot-string die je doorgeeft - proxmoxer — de Python-client waarop de collectie is gebouwd
- damo2929/ansible-example — de playbooks waar deze fragmenten vandaan komen, inclusief de NetBox-aangestuurde inventory, DHCP-generatie en hypervisor-build