Come impostare il kernel di avvio del VPS
Su un'immagine cloud Ubuntu, GRUB_DEFAULT può non avere effetto. Scopri le voci reali di GRUB e fissa il prossimo avvio senza rischiare la console di ripristino.
Cosa determina il kernel con cui si avvia il VPS
Il kernel con cui si avvierà il VPS è determinato da un file generato, /boot/grub/grub.cfg. Non modificare mai direttamente questo file. Modifica i relativi file di input e rigeneralo. In un'immagine cloud Ubuntu, uno di questi input viene fornito dal produttore dell'immagine e può rendere irrilevante la selezione dal menu. Per questo GRUB_DEFAULT=1 seguito da update-grub non modifica nulla su un server a noleggio, mentre gli stessi due passaggi funzionano su un'installazione eseguita su un laptop.
Procedi in quest'ordine. Verifica che il kernel sia effettivamente selezionabile. Leggi ogni file di input, compresi quelli aggiunti dal produttore. Leggi l'output generato e conta le voci che contiene realmente. Solo dopo scegli un metodo di blocco. Se commetti un errore su una macchina raggiungibile esclusivamente tramite SSH, potresti dover usare una console di ripristino. Per questo, le soluzioni più sicure sono riportate alla fine di questa pagina e spesso sono quelle corrette.
First check that the kernel is yours to pin
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virt printing kvm, qemu or xen means you run your own kernel and everything below applies. Printing lxc or openvz means your server shares the host's kernel, so there is no bootloader of yours and nothing to pin. In that case uname -r reports a version that does not appear in /boot/vmlinuz-* at all, because the running kernel belongs to the host and no setting on your disk can change it.
ls -1 /boot/vmlinuz-* is the real list of kernels you can choose between. If it holds one line, the previous kernel is already deleted, and no bootloader setting brings it back. That usually happens during an autoremove, which is worth understanding before you clean up old kernels on Ubuntu on a box you care about.
Il file che modifichi non è il file letto da GRUB
/etc/default/grub contiene semplici assegnazioni di variabili della shell. È un input. /boot/grub/grub.cfg è l'output e inizia con # DO NOT EDIT THIS FILE e con la motivazione. Qualsiasi modifica apportata all'output viene persa alla successiva installazione o rimozione di un pacchetto del kernel, perché gli script del pacchetto lo rigenerano.
cat /usr/sbin/update-grubupdate-grub è un wrapper. Esegue grub-mkconfig -o /boot/grub/grub.cfg, che legge le variabili, esegue ogni script in /etc/grub.d/ e scrive il risultato. Due comandi, una sola direzione: gli input entrano e ne esce grub.cfg.
Che cosa sovrascrive la tua impostazione: /etc/default/grub.d
grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/Il secondo percorso è il dettaglio che spesso viene trascurato. grub-mkconfig esegue prima il source di /etc/default/grub, quindi di ogni file *.cfg presente in /etc/default/grub.d/, nell’ordine determinato dal glob. Leggi il codice che esegue questa operazione:
grep -n 'default/grub' /usr/sbin/grub-mkconfigIl source usa la normale sintassi della shell, quindi prevale l’ultima assegnazione. Le immagini cloud Ubuntu includono file in quella directory e impostano valori come il timeout e la riga di comando del kernel dopo che il tuo file è già stato letto. Il valore GRUB_TIMEOUT=10 in /etc/default/grub viene sovrascritto subito dopo da un file del fornitore, che lo imposta a 0. Il comando grep precedente stampa le assegnazioni effettive presenti nella tua immagine; usa quelle informazioni invece di fare affidamento su questa frase.
La regola pratica è quindi inserire le proprie impostazioni in un file che venga ordinato per ultimo, ad esempio /etc/default/grub.d/99-local.cfg, invece di modificare /etc/default/grub. In questo modo nessun file incluso nell’immagine può essere elaborato dopo il tuo.
Perché GRUB_FORCE_PARTUUID rende irrilevante la selezione del menu
grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/
grep -n GRUB_FORCE_PARTUUID /etc/grub.d/10_linux
sudo grep -n 'root=PARTUUID' /boot/grub/grub.cfgGRUB_FORCE_PARTUUID indica al generatore di individuare il filesystem root tramite l'UUID della partizione. Il valore viene scritto direttamente nella riga di comando del kernel come root=PARTUUID=..., invece di cercare un UUID del filesystem durante l'avvio. Il vendor dell'immagine lo imposta perché consente alla stessa immagine disco di avviarsi in modo affidabile su hardware diverso da quello su cui è stata creata. Il secondo comando grep mostra il codice che utilizza la variabile, in /etc/grub.d/10_linux. Questo script si trova sul disco locale ed è la fonte autorevole per stabilire il comportamento della tua immagine.
Qui conta la conseguenza: in questo percorso il generatore scrive una voce di avvio diretta, non l'elenco completo dei kernel installati. Conta quante voci sono state generate.
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfgSe il conteggio è 1, non esiste una seconda voce da selezionare. Di conseguenza, GRUB_DEFAULT=1 indica una voce inesistente. GRUB non riesce a risolverla e avvia la prima voce, cioè il nuovo kernel che volevi evitare. Neppure grub-set-default è utile, perché il problema non riguarda il valore predefinito. Il menu da cui stai cercando di scegliere non è mai stato generato.
Per ripristinare il menu completo, sposta temporaneamente il file del vendor e visualizza l'anteprima del risultato prima di applicarla. grub-mkconfig senza -o scrive sull'output standard e non modifica nulla sul disco.
sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) '
sudo mkdir -p /root/grub-backup
sudo mv /etc/default/grub.d/<the file your grep named> /root/grub-backup/
sudo grub-mkconfig 2>/dev/null | grep -cE '^\s*(menuentry|submenu) 'Un conteggio che passa da 1 a diversi valori indica che le voci compaiono dopo aver rimosso il forzatura. Non è ancora stato scritto nulla. Ripristina il file se il secondo conteggio non è corretto, perché il PARTUUID forzato consente all'immagine del provider di individuare il filesystem root. Rimuovendolo, il sistema passa invece al percorso di ricerca. Crea uno snapshot prima di eseguire realmente update-grub.
Se l'unico obiettivo è superare un kernel non funzionante, fermati qui e usa le opzioni più sicure riportate più avanti. Ricostruire il menu di avvio su un server remoto per evitare un singolo aggiornamento comporta un rischio superiore al beneficio.
Perché gli indici delle voci non sono adatti per fissare una voce
GRUB_DEFAULT accetta un numero, un titolo o un identificatore. I numeri contano le voci di primo livello a partire da 0. Una voce annidata usa > come separatore; quindi GRUB_DEFAULT="1>2" indica la voce con indice 2 all'interno del sottomenu con indice 1.
Gli indici cambiano. 10_linux elenca i kernel dal più recente al meno recente; quindi l'installazione di un kernel sposta in basso di una posizione tutte le voci precedenti e la rimozione di un kernel le sposta in alto. Il tuo attento 1>2 continua a essere accettato, ma ora identifica un kernel diverso. Non viene generato alcun errore né avviso e te ne accorgi solo dopo un riavvio.
Gli identificatori non cambiano, perché ognuno contiene la versione del kernel. Leggi il tuo:
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfgIgnora le prime righe dell'output: contengono la variabile definita nell'intestazione. Nelle righe successive, il lato sinistro contiene il titolo visualizzato all'utente e il lato destro contiene l'identificatore da passare agli strumenti. Per una voce all'interno di un sottomenu, concatena l'identificatore del sottomenu e quello della voce usando >, in quest'ordine, esattamente come nella forma numerica.
Avviare una sola volta il kernel precedente con grub-reboot
Su un server remoto, una selezione valida per un solo avvio è la scelta corretta perché si annulla da sola. grub-reboot scrive next_entry in /boot/grub/grubenv. GRUB legge questa variabile, la cancella e salva il valore cancellato prima di avviare qualsiasi elemento. In questo modo, un kernel che va in panic non viene riprovato al riavvio successivo. Il sistema effettua un solo tentativo, quindi torna autonomamente al valore predefinito normale.
Per prima cosa, verificare che la configurazione generata legga effettivamente questa variabile:
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfgDevono essere presenti una riga load_env e un blocco che imposta default a partire da next_entry. Se grep non restituisce nulla, l'immagine non legge grubenv durante l'avvio. Di conseguenza, grub-reboot viene accettato dalla shell e poi ignorato dal bootloader. È lo stesso percorso di avvio diretto forzato della sezione precedente, che ricompare in un secondo punto.
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listgrub-editenv list dovrebbe ora stampare una riga next_entry= contenente esattamente il valore passato. Aprire la console del provider in una scheda del browser, quindi riavviare il server e verificare il risultato.
sudo rebootuname -rSe uname -r segnala la versione precedente, il blocco ha funzionato. Se segnala la nuova versione, significa che l'identificatore non è stato risolto oppure che grubenv non viene letto. In ogni caso, il sistema è operativo: è proprio questo il vantaggio della modalità valida per un solo avvio.
Impostare la scelta in modo persistente con GRUB_DEFAULT=saved
GRUB_DEFAULT=saved fa derivare la voce predefinita da saved_entry in grubenv, e si imposta quel valore con grub-set-default. L’impostazione resta valida dopo l’installazione dei kernel, perché update-grub riscrive grub.cfg e non modifica mai grubenv.
echo 'GRUB_DEFAULT=saved' | sudo tee /etc/default/grub.d/99-local.cfg
sudo update-grub
sudo grub-set-default '<the identifier you copied>'
sudo grub-editenv list
sudo grep -n 'set default' /boot/grub/grub.cfgL’ultimo comando deve stampare set default="${saved_entry}". Se stampa set default="0", un file caricato dopo il tuo ha reimpostato GRUB_DEFAULT su un valore letterale. Elenca quindi di nuovo /etc/default/grub.d/ e verifica che 99-local.cfg venga effettivamente ordinato per ultimo.
GRUB_SAVEDEFAULT=true è un’impostazione diversa e può essere facilmente confusa con questa. Salva come nuova voce predefinita quella appena avviata, quindi la voce predefinita segue l’ultimo avvio completato correttamente. Su un server, un riavvio non supervisionato può quindi modificare silenziosamente la voce fissata. Lasciala disabilitata, a meno che sia proprio questo il comportamento desiderato.
Anche una voce fissata tramite identificatore può smettere di funzionare. Se rimuovi il kernel a cui fa riferimento, l’identificatore non viene più risolto e viene selezionata nuovamente la prima voce. Mantieni quindi anche il pacchetto oppure escludi quel kernel da autoremove.
Inserire il menu nella console del provider
La selezione interattiva richiede che il menu sia visualizzato, ma le immagini cloud lo nascondono. Inserire queste righe nel file che viene ordinato per ultimo, quindi eseguire sudo update-grub.
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10GRUB_TIMEOUT_STYLE=hidden insieme a GRUB_TIMEOUT=0 non visualizza nulla. Chi osserva la console vede quindi comparire subito i messaggi del kernel e conclude che il bootloader sia stato saltato. GRUB_RECORDFAIL_TIMEOUT è il timeout separato usato dopo un avvio non completato. Anche le immagini cloud lo impostano su 0. Per questo un server che ha appena fallito l'avvio non si arresta comunque per attendere un intervento.
Se il provider offre una console seriale invece di una console grafica e non viene visualizzato ancora nulla, GRUB sta scrivendo su un terminale non visibile. Aggiungere entrambe le righe, perché la prima seleziona gli output e la seconda configura la porta:
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"D'ora in poi, a ogni avvio verranno aggiunti dieci secondi. Al termine, reimpostare il timeout su 0.
Opzioni più sicure della modifica del bootloader
Modificare l’input del bootloader su una macchina raggiungibile soltanto tramite SSH è l’opzione più rischiosa descritta in questa pagina. Esistono alternative meno rischiose, che di solito risolvono il problema reale.
Bloccare i pacchetti del kernel. Se l’obiettivo è «non installare un kernel più recente», indicatelo al gestore dei pacchetti invece che al bootloader.
apt list --installed 2>/dev/null | grep -E '^linux-(image|headers|generic|virtual|kvm|aws|azure|gcp|oracle)'
sudo apt-mark hold linux-image-virtual linux-headers-virtual
apt-mark showholdUsate i nomi visualizzati dal primo comando, perché le immagini cloud installano spesso la variante virtual o kvm invece di generic. Un pacchetto bloccato viene ignorato da apt upgrade, che lo segnala con The following packages have been kept back:, e viene ignorato anche da unattended upgrades su Ubuntu. Il costo è concreto: un kernel bloccato non riceve gli aggiornamenti di sicurezza. Considerate quindi il blocco una sospensione con una data di scadenza e rimuovetelo con sudo apt-mark unhold. Se evitate gli aggiornamenti del kernel perché i riavvii causano downtime, e non perché un kernel specifico presenta problemi, il live kernel patching su un VPS risolve invece questo problema.
Creare uno snapshot prima dell’aggiornamento. Uno snapshot può essere ripristinato in pochi minuti, senza digitare comandi nella console e senza rischiare una modifica del bootloader applicata solo parzialmente. Create lo snapshot, eseguite l’aggiornamento, riavviate e verificate il sistema. Se il nuovo kernel presenta problemi, eseguite il rollback: il percorso di avvio tornerà esattamente allo stato precedente.
Usare la console o un’immagine di ripristino per una macchina già non avviabile. Quando il server non si avvia, la configurazione del bootloader non è il punto in cui risolvere il problema. Il ripristino segue una procedura distinta: cosa fare quando un VPS non si avvia dopo un aggiornamento del kernel.
Cosa si rompe e quale messaggio viene visualizzato
La modifica a /boot/grub/grub.cfg è scomparsa. È stato installato o rimosso un pacchetto del kernel, il relativo maintainer script è stato eseguito tramite update-grub e il file è stato rigenerato a partire dai file sorgente. L'header # DO NOT EDIT THIS FILE indica le due posizioni di input. Modifica quelle.
grub-editenv: error: environment block too small. /boot/grub/grubenv manca o è troncato. Ricrealo con sudo grub-editenv /boot/grub/grubenv create, quindi imposta nuovamente il valore e verifica con sudo grub-editenv list.
Un kernel bloccato tramite pinning va in kernel panic con VFS: Unable to mount root fs on unknown-block(0,0). La voce fissata punta a un kernel o a un initrd che non si trova più sul disco, in genere perché il pacchetto è stato rimosso mentre l'identificatore è rimasto in grubenv. Per il ripristino, avvia dalla console una voce funzionante, quindi cancella il valore obsoleto.
uname -r non cambia dopo un riavvio che avrebbe dovuto modificarlo. Verifica tre elementi, in quest'ordine: grub-editenv list mostra ancora il tuo valore oppure il valore è già stato utilizzato? L'identificatore impostato compare nell'attuale grub.cfg? grub.cfg contiene una riga set default che legge la variabile impostata? Una di queste tre condizioni spiega sempre il problema.
Il menu è comparso autonomamente dopo un crash. GRUB registra un avvio non riuscito in grubenv come recordfail=1 e, al successivo avvio, forza la visualizzazione del menu per consentire un intervento manuale. Dopo aver verificato che il sistema sia funzionante, cancellalo con sudo grub-editenv /boot/grub/grubenv unset recordfail.
La frase da ricordare è questa: il file che modifichi non è il file letto da GRUB e, in una cloud image, la confusione nasce proprio dalla differenza tra i due. Leggi prima la configurazione generata. Ogni decisione descritta in questa pagina deriva da ciò che la configurazione mostra effettivamente.
FAQ
Perché GRUB_DEFAULT=1 non cambia il kernel con cui si avvia il mio VPS?
Perché in un'immagine cloud Ubuntu il file /boot/grub/grub.cfg generato contiene spesso una sola voce di avvio. L'indice 1 quindi non identifica alcuna voce e GRUB usa la prima. Verificalo con sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg. Un conteggio pari a 1 conferma la causa. Il motivo è GRUB_FORCE_PARTUUID, impostato dal fornitore dell'immagine in un file sotto /etc/default/grub.d/. Questa impostazione porta il generatore a usare un percorso di avvio diretto invece di creare un elenco completo dei kernel installati. Trova il file con grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.
Come posso avviare il kernel precedente una sola volta?
Esegui sudo grub-reboot '<identifier>' usando un identificatore copiato dal tuo grub.cfg, quindi riavvia con la console del provider già aperta. GRUB cancella next_entry prima dell'avvio, quindi la scelta si applica a un solo tentativo. Se il kernel va in panic, non viene riprovato. Verifica che il valore sia stato scritto con sudo grub-editenv list. Prima di fare affidamento su questa procedura, esegui sudo grep -n next_entry /boot/grub/grub.cfg. Un'immagine la cui configurazione non carica mai grubenv ignorerà il comando senza generare errori.
È meglio fissare la scelta tramite numero della voce o identificatore?
Tramite identificatore. I numeri delle voci indicano posizioni in un elenco che 10_linux ricostruisce inserendo prima le versioni più recenti. L'installazione o la rimozione di un kernel qualsiasi può quindi modificarli. Inoltre, un 1>2 obsoleto può ancora corrispondere a una voce reale ma errata, senza visualizzare alcun avviso. Gli identificatori contengono la versione del kernel. Corrispondono quindi al kernel previsto oppure non vengono risolti. Elencali con sudo grep -n menuentry_id_option /boot/grub/grub.cfg e copia la stringa tra virgolette che segue ogni riga della voce.
È più sicuro bloccare il pacchetto del kernel invece di modificare il bootloader?
Per l'obiettivo usuale, sì. sudo apt-mark hold linux-image-virtual linux-headers-virtual impedisce del tutto l'arrivo di un kernel più recente. Il percorso di avvio non cambia e non ci sono modifiche che possano fallire usando una console a cui potresti non avere accesso. Controlla prima i nomi delle varianti installate sul tuo server con apt list --installed e verifica il blocco con apt-mark showhold. Lo svantaggio è che un kernel bloccato non riceve gli aggiornamenti di sicurezza. Decidi quindi quando eseguirai sudo apt-mark unhold prima di applicare il blocco.