Pyinfra/Implementazione deploy VM linea per linea

Da GazziNet.
Vai alla navigazione Vai alla ricerca

Pyinfra/Implementazione deploy VM linea per linea

Questa pagina documenta l'implementazione del deploy automatico di macchine virtuali pubblicata su bot.gazzi.net/pyinfra/.

Obiettivo della pagina:

  • far capire il flusso a una persona umana
  • spiegare il ruolo di ogni file
  • descrivere il codice in ordine logico
  • collegare il comportamento osservabile alle righe principali dell'implementazione

Non vengono riportati segreti runtime.

Vista d'insieme

I file che contano sono tre:

  • app.py: web UI, login LDAP, API, job queue
  • deploys/create_vm.py: entry point pyinfra
  • lib/proxmox_ssh.py: logica concreta di creazione VM su Proxmox

Sequenza completa:

  1. l'utente invia la form
  2. app.py valida il payload e crea il job
  3. il job esegue pyinfra @local deploys/create_vm.py -y
  4. create_vm.py richiama lib/proxmox_ssh.py
  5. proxmox_ssh.py esegue i comandi qm, gestisce cloud-init e cleanup

File 1: deploys/create_vm.py

Questo file e volutamente piccolo. Il suo scopo non e contenere logica complessa, ma fare da ponte tra pyinfra e l'helper specializzato.

Lettura riga per riga

#!/usr/bin/env python3
import os
import shlex
from pathlib import Path

from pyinfra.operations import server

Spiegazione:

  • shebang Python
  • import minimi
  • import dell'operation server.shell
BASE_DIR = Path(__file__).resolve().parents[1]
python_bin = os.environ.get("PYTHON_BIN", str(BASE_DIR / "venv" / "bin" / "python"))
helper = BASE_DIR / "lib" / "proxmox_ssh.py"
cmd = f"{shlex.quote(python_bin)} {shlex.quote(str(helper))}"

Spiegazione:

  • calcola la base del progetto
  • legge il Python del virtualenv da variabile ambiente oppure usa il default
  • individua l'helper vero da eseguire
  • costruisce un comando shell sicuro con quoting
server.shell(
    name=f"Create Proxmox VM {os.environ.get('VM_NAME', '')}",
    commands=[cmd],
)

Spiegazione:

  • pyinfra vede una singola operation
  • il nome dell'operation include il nome VM per leggibilita
  • l'operation esegue localmente il comando helper

Idea pratica:

  • pyinfra orchestra
  • l'helper specializzato realizza la logica

File 2: app.py

Questo file ha quattro responsabilita:

  • leggere la configurazione
  • fare autenticazione LDAP
  • servire UI e API
  • creare e monitorare i job

= Blocco 1: costanti e caricamento environment

Righe utili: 16-64

Che cosa succede:

  • viene definita la base del progetto
  • viene caricato /etc/pyinfra-webui/pyinfra-webui.env
  • viene inizializzata la directory stato
  • vengono lette le variabili principali di app, LDAP e Proxmox
  • Flask viene configurato con secret key e cookie di sessione
  • vengono preparate strutture dati e lock per i job

Perche e importante:

  • tutta la logica runtime dipende da questo blocco
  • separa configurazione e codice
  • permette di non versionare i segreti

= Blocco 2: autenticazione e URL dietro proxy

Righe utili: 67-140

Funzioni:

  • now_iso(): timestamp uniforme dei job
  • require_token(): token opzionale per API
  • forwarded_prefix() e url_with_prefix(): supporto a /pyinfra dietro reverse proxy
  • is_authenticated() e require_login(): controllo sessione
  • validate_username(): sanifica lo username LDAP
  • ldap_authenticate(): bind LDAP con template DN

Traduzione umana:

  • l'app sa stare dietro Nginx senza rompere i path
  • l'utente deve autenticarsi via LDAP
  • eventuali API possono richiedere un token aggiuntivo se configurato

= Blocco 3: lettura dati da Proxmox

Righe utili: 121-189

Qui trovi:

  • proxmox_run(): apre una connessione SSH a Proxmox per eseguire comandi
  • discover_vlans(): parsifica l'output di bridge vlan show
  • discover_bridges(): legge i bridge con ip -o link show type bridge
  • discover_used_vmids(): legge i VMID esistenti con qm list
  • first_free_vmid(): cerca il primo VMID libero da un valore iniziale

Perche serve:

  • la UI non usa valori statici
  • l'utente puo interrogare Proxmox prima del submit

= Blocco 4: validazione input

Righe utili: 192-263

Qui l'app controlla:

  • range di interi
  • formato nome VM
  • formato utente Linux
  • lunghezza minima password
  • modalita rete
  • formato bridge
  • VLAN opzionale
  • IP, gateway e CIDR se la rete e statica

Risultato:

  • l'input viene normalizzato in spec
  • i segreti vengono separati in secrets

Questa separazione e importante:

  • spec puo essere esposto nel job
  • secrets va trattato con piu cautela

= Blocco 5: costruzione environment job

Righe utili: 266-290

build_job_env() converte il dizionario del job in variabili ambiente da passare al runner.

Questo blocco e il ponte tra:

  • input raccolto dalla UI
  • variabili ambiente lette da proxmox_ssh.py

Esempi:

  • spec["vmid"] diventa VM_ID
  • spec["bridge"] diventa VM_BRIDGE
  • la password admin diventa VM_ADMIN_PASSWORD

= Blocco 6: job queue e logging

Righe utili: 293-347

Componenti:

  • append_job(): inserisce il job nelle strutture in memoria
  • write_job_log(): salva il log su file in APP_STATE_DIR
  • run_job(): lancia pyinfra in subprocess
  • create_job(): impedisce job concorrenti e avvia il thread

Punto chiave:

  • active_job_lock fa da semaforo per evitare due creazioni VM contemporanee

Comando usato:

pyinfra @local deploys/create_vm.py -y

Blocco 7: interfaccia HTML

Righe utili: 350-560

La parte HTML/CSS contiene:

  • layout pagina
  • campi del form
  • sezione job recenti

Campi operativi importanti:

  • VMID
  • nome VM
  • utente sudo
  • password sudo
  • CPU, RAM, disco
  • rete, bridge, VLAN
  • gateway, CIDR, IP statico
  • checkbox avvio automatico

Nota:

  • le righe di stile puro servono a presentazione e leggibilita, non alla logica di deploy

Blocco 8: JavaScript della UI

Righe utili: 563-726

Funzioni principali:

  • syncNetworkFields(): disabilita i campi IP/gateway/CIDR quando la rete e DHCP
  • renderJobs(): mostra i job recenti con stato, timing e output
  • loadVlans(): chiama /api/network/vlans
  • loadBridges(): chiama /api/network/bridges
  • loadFreeVmid(): chiama /api/proxmox/free-vmid?start=100
  • validateNetwork(): chiama /api/network/validate prima del submit
  • loadJobs(): ricarica i job recenti
  • submit handler della form: costruisce il payload JSON e lo invia a /api/jobs/create-vm

Traduzione pratica:

  • il browser non parla direttamente con Proxmox
  • parla solo con le API dell'app
  • la UI ferma l'utente prima se bridge/VLAN non sono validi

Blocco 9: login e API backend

Righe utili: 733-938

Route principali:

  • /api/health
  • /login
  • /logout
  • /api/jobs
  • /api/network/vlans
  • /api/network/bridges
  • /api/network/validate
  • /api/proxmox/free-vmid
  • /api/jobs/create-vm

Punto critico:

  • /api/jobs/create-vm fa il passaggio da richiesta web a job reale

Il flusso della route finale e:

  1. richiede login
  2. opzionalmente richiede token
  3. legge il JSON
  4. valida il payload
  5. crea il job
  6. restituisce 202 Accepted

File 3: lib/proxmox_ssh.py

Questo file e il cuore del provisioning.

= Blocco 1: lettura environment

Righe utili: 11-29

Funzioni:

  • env_required()
  • env_int()
  • env_bool()

Servono a:

  • leggere parametri obbligatori
  • convertire interi
  • interpretare flag booleani

= Blocco 2: classe ProxmoxSSH

Righe utili: 32-75

Questa classe:

  • apre la sessione SSH
  • esegue comandi
  • stampa output e errori nel log del job
  • scrive file remoti via SFTP

Perche e utile:

  • centralizza tutte le interazioni con Proxmox

= Blocco 3: attesa guest agent ed esecuzione comandi guest

Righe utili: 77-116

Funzioni:

  • wait_for_guest_agent(): esegue poll su qm agent VMID ping
  • guest_exec(): esegue un comando dentro la VM via guest agent e aspetta l'exit status

Uso concreto nel progetto:

  • attendere che cloud-init abbia davvero finito prima di fare cleanup

= Blocco 4: helper di quoting

Righe utili: 119-124

  • q() usa shlex.quote per shell quoting
  • jq() usa json.dumps per serializzare stringhe nel cloud-config

Questo evita errori banali di quoting e parsing.

= Blocco 5: lettura parametri del job

Righe utili: 127-151

Il file legge dall'environment:

  • VMID, nome, utente, password
  • CPU, RAM, disco
  • rete, bridge, VLAN
  • IP, gateway, CIDR
  • flag avvio automatico
  • storage, immagine cloud-init e default runtime

Qui il job passa da "payload web" a "parametri concreti di provisioning".

= Blocco 6: costruzione rete

Righe utili: 153-164

Se la rete e statica:

  • richiede IP, gateway e CIDR
  • costruisce ipconfig0 nel formato Proxmox

Se la rete e DHCP:

  • usa ip=dhcp

Poi costruisce net0:

  • NIC virtio
  • bridge obbligatorio
  • firewall abilitato
  • eventuale tag VLAN

= Blocco 7: verifica collisione VMID

Righe utili: 166-169

Prima di creare la VM, il file esegue:

qm config <VMID>

Se il comando funziona, il VMID esiste gia e il job viene fermato.

= Blocco 8: generazione snippet cloud-init

Righe utili: 171-191

Questo e uno dei punti piu importanti:

  • crea un file pyinfra-user-<VMID>.yaml
  • lo salva in /var/lib/vz/snippets/
  • definisce l'utente richiesto
  • assegna gruppi adm e sudo
  • imposta shell bash
  • abilita sudo senza password
  • inserisce la password iniziale
  • abilita password auth SSH
  • disabilita root

Perche esiste:

  • il deploy deve creare un utente iniziale specificato dall'operatore

= Blocco 9: lista comandi qm

Righe utili: 193-224

Questo array contiene i passi di creazione macchina:

  • qm create con nome, BIOS, machine type, RAM, CPU, NIC e seriale
  • qm set --efidisk0
  • qm set --scsihw
  • qm set --scsi0 import-from=IMAGE
  • qm set --ide2 CLOUDINIT
  • qm set --boot order=scsi0
  • qm set --ciuser
  • qm set --ipconfig0
  • qm resize

Idea pratica:

  • la VM nasce come cloud image Proxmox correttamente preparata

= Blocco 10: password cloud-init, cicustom e avvio

Righe utili: 226-238

Passi:

  • se c'e una password, esegue qm set --cipassword
  • applica lo snippet custom con qm set --cicustom user=...
  • se esiste un default PROXMOX_CICUSTOM_USER, lo ignora e lo segnala a log
  • se richiesto, avvia la VM con qm start

= Blocco 11: attesa primo boot e cleanup

Righe utili: 240-251

Se la VM parte subito:

  • aspetta il guest agent
  • esegue dentro la VM cloud-init status --wait
  • rimuove il riferimento cicustom
  • cancella lo snippet da /var/lib/vz/snippets/

Se la VM non parte subito:

  • scrive un warning
  • lo snippet non puo essere eliminato immediatamente

Questo blocco esiste per ridurre il tempo in cui la password resta scritta nel file cloud-init sul nodo Proxmox.

= Blocco 12: messaggio finale

Riga utile: 253

Se tutto va bene, il log termina con il messaggio di creazione VM completata.

Esempio di flusso completo

Caso:

  • VMID 120
  • nome demo.gazzi.local
  • utente gazzinet
  • rete DHCP
  • bridge vmbr1

Flusso:

  1. l'utente compila la form
  2. JavaScript invia JSON a /api/jobs/create-vm
  3. parse_payload() valida tutti i campi
  4. create_job() apre il job
  5. run_job() lancia pyinfra
  6. create_vm.py richiama l'helper
  7. proxmox_ssh.py costruisce la VM
  8. se la VM parte subito, il sistema aspetta cloud-init e rimuove lo snippet

Cosa leggere se devi fare modifiche

Se devi cambiare la UI:

  • app.py blocchi HTML e JavaScript

Se devi cambiare validazioni:

  • app.py blocchi parse_payload() e API network

Se devi cambiare il provisioning Proxmox:

  • lib/proxmox_ssh.py

Se devi cambiare il modo in cui pyinfra lancia l'helper:

  • deploys/create_vm.py

Limiti di questa documentazione

Questa pagina e molto piu dettagliata del normale, ma continua a privilegiare:

  • chiarezza
  • leggibilita umana
  • raggruppamento per blocchi logici

Non descrive ogni singola riga di CSS decorativo, perche quella parte non governa il deploy automatico VM. La logica operativa invece e coperta integralmente.

Collegamenti