Blog ·
Scegliere un intervallo di VMID per l’hosting
Un VMID è un’identità, non un contatore. L’intervallo decide che cosa la piattaforma può creare, che cosa può leggere e che cosa succede quando si riempie.
Di Provibr
Ogni installazione di Proxmox parte da 100 e conta in avanti. Funziona benissimo finché la macchina non smette di essere soltanto sua: finché non c’è un pannello di controllo che vi crea guest, un collega che ne crea a mano, una libreria di template e un lavoro di backup che alle tre di notte ripristina qualcosa.
A quel punto il VMID smette di essere un contatore e diventa un’identità, e l’intervallo che affida alla sua automazione smette di essere una comodità e diventa un confine. Vale la pena sceglierlo con cura, perché due delle tre cose che un intervallo decide restano invisibili finché non vanno storte.
Che cosa consente davvero Proxmox
Lo spazio completo è più ampio di quanto si supponga:
| Valore | |
|---|---|
| VMID più basso accettato da Proxmox | 100 |
| VMID più alto accettato da Proxmox | 999999999 |
| Uso manuale tipico | da 100 a qualche centinaio |
Nove cifre. Non è un invito a usarle tutte, ma significa che la preoccupazione consueta, «finiranno i numeri», è quella sbagliata. Un intervallo scelto male si esaurisce molto prima dei numeri.
Quei due estremi sono gli unici che l’hypervisor impone. Tutto il resto, cioè la parte che conta, è una sua decisione.
Un intervallo è un confine di autorizzazione prima ancora che un allocatore
Ecco la parte che sfugge facilmente. Se a una piattaforma si dice «allochi da 5000 a 8999», le si sono dette due cose insieme:
- Che cosa può creare. I nuovi guest ricevono un numero da quella finestra e da nessun’altra parte.
- Che cosa non le appartiene. Tutto ciò che sta fuori da quella finestra è stato fatto da qualcun altro, e la piattaforma non ha alcun motivo di toccarlo.
La seconda merita di essere scritta sul muro. Quando abbiamo costruito l’integrazione con Proxmox, l’intervallo di vmid nelle impostazioni dell’integrazione è descritto nel codice esattamente così: la piattaforma alloca solo dentro [vmidRangeStart, vmidRangeEnd], quindi un’integrazione non può, per costruzione, raggiungere una VM al di fuori. Chi tiene le proprie VM di produzione fuori dall’intervallo le tiene fuori portata.
Questa impostazione cambia il modo di scegliere i numeri. Non sta scegliendo da dove la sua automazione comincia a contare. Sta tracciando una linea attorno alle macchine che è disposto a lasciar cancellare a un programma.
Il lato della lettura è una questione a parte, e morde più tardi
Creare è la metà evidente. Leggere è la metà che ci ha sorpreso.
Il nostro agente interroga il cluster una volta per ciclo con una sola GET /cluster/resources?type=vm, che non tocca alcun guest. Ma per ogni guest in esecuzione presente in quella risposta faceva poi altre due chiamate al guest agent QEMU, network-get-interfaces e get-fsinfo, per conoscere indirizzi e uso dei dischi. Quell’elenco usciva direttamente dalla query sul cluster, senza filtri.
Su un hypervisor di prova che ospita anche carichi di produzione, quell’elenco comprendeva le macchine di produzione. Le letture sono innocue nel senso che non scrivono nulla. Non lo sono nel senso che conta, perché «sola lettura» non è una risposta alla domanda perché il suo agente parla con il guest agent di una macchina che non è sua.
La correzione è che ora lo stesso intervallo limita anche le letture. Se sta costruendo qualcosa di simile, la lezione si generalizza: un confine che copre solo le scritture è mezzo confine, e la metà mancante è quella di cui nessuno nota l’assenza.
Dove deve stare l’intervallo
Abbiamo dovuto rispondere una volta e vale la pena esporre il ragionamento, perché la risposta ovvia è sbagliata.
La risposta ovvia è mettere l’intervallo accanto alle credenziali, nel blocco che la piattaforma invia all’agente quando l’integrazione viene creata. È un solo messaggio, arriva insieme a tutto il resto, fatto.
Non regge a un riavvio. Le credenziali vengono inviate una sola volta; dopo un riavvio l’agente ricostruisce le sue integrazioni dal vault locale, senza alcun invio. Un confine consegnato per quella strada sparisce in silenzio al primo riavvio del processo, e un confine che smette di applicarsi senza dirlo è peggio di nessun confine, perché nessuno va a cercarlo.
Quindi l’intervallo viaggia in un messaggio suo, inviato a ogni handshake di sessione, con lo stato completo. Due percorsi di consegna, una sola fonte di verità. Esiste anche una variabile d’ambiente sull’host come ripiego, e l’ordine fra le due è voluto:
| Origine | Quando vince |
|---|---|
| L’impostazione della piattaforma | Ogni volta che ne ha inviata una |
| Variabile d’ambiente dell’host | Solo quando la piattaforma non ha detto nulla |
| Nessun limite | Quando nessuna delle due ha detto nulla |
L’ordine allettante è quello inverso, sulla teoria che una cintura allacciata sull’host sia più solida. Produce uno stato che nessuno vede: un amministratore che una volta ha impostato una variabile conserva in silenzio un confine che non corrisponde più a ciò che mostra il pannello, e un cliente che allarga l’intervallo nell’interfaccia non riesce a capire perché non sia cambiato nulla.
Un piccolo dettaglio con un grande modo di guastarsi: un valore illeggibile viene trattato come «nessuno», non come «tutti». Chi chiede un confine e fa un refuso non ottiene alcuna lettura, più una riga di log che ne dice il motivo. Il costo è estetico: un indirizzo e una cifra di disco mancanti nel pannello. L’alternativa, ripiegare su «è permesso tutto», è il classico fallimento di una allowlist: fallisce in apertura, in silenzio, esattamente nel caso in cui qualcuno stava cercando di essere prudente.
I template stanno fuori dall’intervallo. Sempre.
Questa è una regola e non una preferenza, ed è l’errore che divora se stesso.
Un allocatore distribuisce numeri presi dall’intervallo. Metta un template dentro l’intervallo e un giorno l’allocatore ci arriverà: su quel numero viene creato un nuovo ordine e il template diventa la VM di un cliente. L’ordine successivo clonerà da una macchina che non è più un template. Perciò il validatore rifiuta un VMID di template Proxmox che cada dentro l’intervallo dell’integrazione, e in più l’allocatore considera occupato ogni template collegato, nel caso qualcuno abbia allargato l’intervallo dopo averne collegato uno.
Fuori dall’intervallo un template può stare ovunque. L’intervallo limita ciò che la piattaforma crea, non il posto in cui è lecito tenere il materiale di partenza.
Mai riutilizzare un numero, nemmeno quando la macchina non c’è più
L’allocatore distribuisce il VMID libero più basso dell’intervallo, e «libero» è più severo di quanto ci si aspetti: ogni VMID che l’integrazione ha distribuito anche una sola volta conta come occupato, compresi quelli dei server eliminati.
Sembra uno spreco per circa cinque secondi, finché non si considera che cosa comporterebbe il riutilizzo. Un evento di stato tardivo della vecchia VM finisce sulla riga nuova. Le cronologie di audit di due server diversi si mescolano in un’unica linea temporale. Qualcuno ripristina il backup di ieri su un numero che ora appartiene a un altro cliente.
Curiosamente, nello stesso prodotto facciamo la scelta opposta per gli indirizzi IP, e per una buona ragione: un indirizzo è un indirizzo, e non riutilizzarne mai nessuno esaurirebbe per sempre un /24 dopo 254 macchine. Un VMID è il nome della storia di una macchina. Gli intervalli costano poco; la confusione no.
C’è una terza categoria di «occupato» che si manifesta solo quando più di una cosa crea macchine. Un ripristino ancora in corso ha già rivendicato il VMID di destinazione ma non ha ancora una riga di server, quindi un secondo ripristino nello stesso minuto riceverebbe altrimenti lo stesso numero «libero». Tutto ciò che prenota un VMID prima che la riga esista va conteggiato.
Che cosa succede quando l’intervallo si riempie
Due risposte, ed entrambe devono esistere.
Quella gentile è aritmetica: l’allocatore scorre l’intervallo, non trova nulla di libero e restituisce un errore specifico che nomina l’inizio e la fine dell’intervallo. Il cliente legge «questo intervallo è pieno, da 5000 a 8999» invece di un errore dell’hypervisor quattro livelli più sotto.
Quella vera è il database. Il vincolo di unicità su (integrazione, id esterno) è l’arbitro: due ordini inoltrati nello stesso istante possono vedere entrambi lo stesso numero libero più basso, e sopravvive esattamente un inserimento. Chi perde riprova, fino a un piccolo numero fisso di tentativi. Controllare prima e inserire dopo apre proprio la corsa che il vincolo chiude, quindi il vincolo è il meccanismo e il messaggio gentile è una cortesia che gli sta sopra.
Noti che cosa non usiamo: Proxmox ha un endpoint per il prossimo id libero, ed è lo strumento sbagliato in questo caso. Risponde alla domanda «quale numero è libero su questo cluster», che è diversa da «quale numero è libero e dentro la finestra che mi appartiene». Se lo chiede all’hypervisor, prima o poi le verrà consegnato un numero fuori dal suo confine.
Dunque, in pratica
- Parta in alto e lasci stare i numeri bassi. Da 100 a qualche centinaio è dove vivono le macchine fatte a mano in ogni installazione di Proxmox, compresa la sua fra tre anni, quando avrà dimenticato questa decisione.
- Scelga un intervallo dalla forma evidente. Da 5000 a 8999 si legge come «nostro» a colpo d’occhio nell’interfaccia web. Un intervallo di 4000 numeri sono quattromila macchine su una sola integrazione.
- Tenga i template fuori, in un blocco a parte, ben lontano.
- Scriva l’intervallo dove la piattaforma può vederlo, non solo in un file di configurazione sull’host, e si assicuri che venga inviato di nuovo anziché una volta sola.
- Verifichi che anche le letture siano limitate, non soltanto le creazioni e le eliminazioni.
Il numero in sé conta poco. Conta che il confine sia una cosa sola, visibile in un posto solo, applicata allo stesso modo a letture e scritture, e che fallisca in chiusura quando non si riesce a leggerlo.
Questo testo nasce dalla costruzione dell’integrazione Proxmox per Provibr, dove l’agente gira dentro la rete del cliente e si connette sempre e solo verso l’esterno.