Blog ·
Reinstallare un container è una sostituzione
Un container non ha un disco template né cloud-init: reinstallarlo significa distruggerlo e ricostruirlo. Cambia ciò che si deve al cliente prima di iniziare.
Di Provibr
Reinstallare una macchina virtuale su Proxmox è quasi noioso. La si ferma, la si elimina, si clona il template sullo stesso ID e cloud-init fa il resto. Il template porta un disco e il disco porta un sistema operativo, quindi «reinstallare» in realtà significa «fare una nuova copia di una cosa che si sa buona».
Un container Linux non ha né l’uno né l’altro. Viene scompattato da un archivio tar, e tutto ciò che è da lì in avanti, i core, la memoria, lo swap, la dimensione del root filesystem, l’interfaccia di rete, il resolver, il nome host, vive in un file di configurazione sull’hypervisor e da nessun’altra parte. Non c’è un’immagine da clonare sopra. Quindi la reinstallazione di un container è una distruzione seguita da una creazione, e appena lo si dice ad alta voce ne segue una serie di obblighi che una reinstallazione di VM non ha mai.
Ecco come l’abbiamo costruita, e la parte che ha richiesto più tempo non è stata la distruzione.
Due fasi, perché la piattaforma non sa che cosa è montato
Il flusso è diviso. La prima fase legge soltanto e non cambia nulla: l’agente recupera la configurazione del container e la riconsegna. La seconda fase ferma, distrugge, crea e avvia, usando come precondizione ciò che la prima ha letto.
L’alternativa ovvia è una fase sola, con un’anteprima costruita su ciò che la piattaforma già sa. Qui non è disponibile. L’ispezione dell’hardware per i container non è implementata, perché il parser che userebbe legge chiavi di configurazione QEMU, quindi nel nostro database non esiste da nessuna parte un’istantanea dei punti di mount di un container. Un’anteprima che etichetta ogni punto di mount come «unknown» non è un’anteprima, è una clausola di esclusione di responsabilità.
Cinque parole per ciò che succede ai dati
Ogni punto di mount nella configurazione riceve esattamente un’etichetta, e l’etichetta riguarda i dati, non il mount.
| Etichetta | Significato |
|---|---|
erased | il volume viene distrutto insieme al container; il root filesystem, sempre |
excluded | il volume appartiene a un altro guest, quindi Proxmox lo salta e non viene toccato nulla |
externally_managed | un bind mount, un percorso dell’host; la directory sopravvive, il mount non torna |
unknown | la riga non è stata leggibile, ed è l’unica etichetta che blocca l’intera operazione |
preserved | i dati restano e il mount torna |
L’ultima riga è nell’elenco e non viene mai prodotta. Conservare un volume attraverso una distruzione significa staccarlo, eliminare il container senza raccogliere i dischi non referenziati e riattaccarlo dopo: tre passi il cui secondo, se fallisce, lascia un volume che non appartiene più a nessuno. È un lavoro a sé, con un proprio ciclo di pulizia. Il valore esiste nel vocabolario perché una versione futura non debba archiviarlo in silenzio sotto un altro nome, una funzione dichiara che non è ancora offerto e un test verifica che il classificatore non lo restituisca mai.
La distinzione importante è quella della prima frase. Un punto di mount può vedere i propri dati sopravvivere e comunque non tornare, perché la chiamata di creazione che inviamo imposta soltanto il root filesystem. Ogni altro mount sparisce dal nuovo container a prescindere da che cosa sia successo ai suoi byte. Invertire questo produce la frase più pericolosa di tutta questa funzionalità: «preserved» accanto a un mount che dopo non esisterà.
La proprietà sta nell’identificatore del volume
Proxmox scrive un volume in una di due forme, ed entrambe dicono quale guest lo possiede. local-lvm:vm-5001-disk-0 e local:5001/vm-5001-disk-0.raw appartengono entrambi al container 5001. Un bind mount non è né l’una né l’altra: è un percorso assoluto, e la barra iniziale è ciò su cui Proxmox stesso fa il controllo.
Quell’analisi decide fra excluded ed erased. Un punto di mount nella configurazione di questo container che punta al volume di un altro guest viene saltato dalla distruzione, perché Proxmox controlla la proprietà, quindi quei dati non vengono cancellati. Senza il confronto l’anteprima direbbe «erased» di dati che restano. Qualcuno ripristinerebbe poi un backup di cui non aveva bisogno, che è un disastro minore rispetto al contrario ma pur sempre un disastro causato da noi.
L’anteprima è una precondizione, non un’immagine
Ciò che il cliente ha visto torna indietro insieme al comando di applicazione come elenco di conferme, e l’agente rilegge la configurazione di nuovo subito prima di distruggere. Se differisce, un punto di mount aggiunto, un volume diverso sotto la stessa chiave, rifiuta.
Fra l’anteprima e il clic c’è un essere umano, e in quel tempo un amministratore può collegare un disco nell’interfaccia di Proxmox. «I bind mount e i punti di mount gestiti dall’host non fanno automaticamente parte di una reinstallazione» è vero soltanto se qualcosa lo impone.
Ogni conferma è una chiave e un volume, e deliberatamente non l’etichetta. L’etichetta è il giudizio della piattaforma e non ha nulla da decidere all’altro capo; se venisse consegnata all’agente come parte di ciò con cui fa il confronto, ci sarebbe un ramo che sceglie in base a essa, e allora le regole vivrebbero in due posti. L’etichetta viene comunque annotata, accanto alle conferme nel record del comando e nella riga di intento, perché quello è il lato dell’audit, e lì deve dire esattamente ciò che è stato mostrato al cliente.
Solo crescita, confrontata in byte, mostrata in gibibyte
Una reinstallazione non può ridurre il root filesystem. Il confronto avviene sui byte e lo schermo arrotonda per eccesso a gibibyte interi.
Arrotondare per eccesso è la direzione prudente, e il controesempio dice perché. Si arrotondino per difetto 8,5 GiB a «8 GiB» sullo schermo, si accetti su quella base una richiesta di 8 GiB, e se ne va mezzo gibibyte che nessuno ha chiesto di perdere. Misurato contro un Proxmox ricostruito, un container che riportava 16 GiB dove il piano ne chiedeva 8 è stato rifiutato.
C’è una trappola un livello più sotto. Proxmox scrive le dimensioni come 8G, 512M oppure come numero nudo, e un numero nudo è espresso in kibibyte. Letto come byte, 8388608 fa 8 MiB; letto correttamente fa 8 GiB. Un parser che qui tira a indovinare non solleva eccezioni, semplicemente approva ogni riduzione che gli venga mai mostrata.
Nove ragioni per rifiutare
Prima che accada qualcosa di irreversibile ci sono nove impedimenti con un nome: il container non c’è più, protection=1 è impostato, Proxmox tiene un lock sul guest, è in corso un nostro lavoro di backup o di ripristino, esiste un punto di ripristino protetto, il root filesystem si ridurrebbe, non è stato possibile leggerne la dimensione attuale, un punto di mount non è stato classificabile, oppure una reinstallazione è già in corso.
Ognuno è un valore tipizzato con una propria frase tradotta in nove lingue, così che una ragione fuori elenco non possa averne una. Vengono controllati due volte dalla nostra parte, una quando l’anteprima viene disegnata e di nuovo quando si preme il pulsante di conferma, e una terza volta dall’agente contro la configurazione che legge da sé. Tre chiusure, perché questo è il passo irreversibile del capitolo.
La password di root viene chiesta nella seconda fase e non nella prima, e viaggia come dato transitorio invece di essere conservata. Un segreto che si digita appena prima del passo irreversibile non passa mezz’ora in una scheda del browser.
«Da riconciliare» è il terzo esito, e tiene chiuso il lock
Se qualcosa fallisce fra la distruzione e la creazione, l’esecuzione non diventa «fallita». Diventa «da riconciliare», e porta con sé l’ID del container, il passo in cui si è interrotta e se il container esistesse ancora quando l’agente ha rinunciato. Quell’ultimo campo può essere null, e null non è false: «non sono riuscito a stabilirlo» non deve leggersi come «l’ID è libero».
Se un fallimento sia distruttivo si ricava dalla posizione del passo nell’elenco ordinato delle tappe, dalla distruzione in avanti. È per questo che le tappe sono un elenco e non un insieme di stringhe sciolte: alla domanda «era già successo qualcosa di irreversibile» si risponde in un unico punto.
Un’esecuzione in quello stato tiene chiuso il lock sul server. Avviare una seconda sostituzione su un ID dove nessuno sa se ci sia un container è esattamente la cosa che questo progetto vieta, e a chiuderlo deve essere una persona.
Chi perde un compare and swap deve rileggere
Non c’è alcun worker che aggiorna queste righe. Lo stato avanza quando qualcuno lo legge: la lettura guarda il comando a cui l’intento è collegato e fa avanzare la riga, con un compare and swap su un contatore di generazione, così che due lettori simultanei insieme eseguano ogni transizione esattamente una volta.
Questo era stato progettato. Ciò che non era stato progettato, e che il test nel browser ha colto, è che cosa debba restituire chi perde quello scambio.
Il pannello interroga il server una volta al secondo dopo che è stata richiesta un’anteprima, e quell’interrogazione esegue la stessa transizione. Se il cliente carica la pagina in quello stesso secondo, l’interrogazione vince lo scambio, la pagina restituisce la propria riga, ormai vecchia, e lo schermo continua a dire «in attesa dell’agente» mentre l’anteprima è già nel database. Una navigazione, una richiesta e una schermata che mostra uno stato che non esisteva più. Solo un secondo ricaricamento lo sistemava.
La correzione sta in una frase e vale la pena interiorizzarla: perdere un compare and swap significa, per costruzione, che qualcun altro ha appena eseguito esattamente la transizione che si voleva. La risposta corretta è quindi la sua riga, e costa una select. Restituire la propria è la cosa intuitiva da fare ed è sempre sbagliata.
Che cosa metteremmo per iscritto
- Se la cosa che si sostituisce non ha un’immagine, la si chiami sostituzione. La parola cambia ciò che si deve al cliente prima di cominciare.
- Etichettare i dati e il mount separatamente. Sono due domande, e la combinazione di una risposta vera all’una con una risposta falsa all’altra è il peggior esito che il sistema possa produrre.
- Far tornare indietro l’anteprima come condizione, rileggerla all’altro capo e rifiutare a ogni differenza. In mezzo ha fatto clic un essere umano.
- Arrotondare in modo prudente quando un errore di arrotondamento può solo distruggere.
- Dare a un’operazione distruttiva rimasta a metà uno stato proprio, tenere chiuso il lock e non lasciarla mai cadere né nel successo né nel fallimento.
- Rileggere dopo aver perso un compare and swap, sempre.
Il supporto ai container su Proxmox viene distribuito per gradi, e la reinstallazione sta in fondo a quell’ordine per le ragioni dette sopra. Se si trova più indietro in quella sequenza, i compromessi fra i due tipi di guest sono in VM o container su Proxmox, e la ragione per cui una chiamata API riuscita non è un lavoro finito è in perché un 200 di Proxmox non è un esito.
Dal lavoro su Proxmox di Provibr.