Como fixar o kernel que o VPS inicia no próximo boot
Em imagens cloud Ubuntu, GRUB_DEFAULT pode não alterar o boot. Veja as entradas reais do GRUB e fixe o próximo kernel sem perder o acesso SSH.
O que decide qual kernel o seu VPS inicia
O kernel que o seu VPS inicia da próxima vez é decidido por um único ficheiro gerado, /boot/grub/grub.cfg. Nunca edite esse ficheiro. Edite as entradas que o geram e regenere-o. Numa imagem cloud do Ubuntu, uma dessas entradas vem do fornecedor da imagem e pode tornar irrelevante a seleção no menu. É por isso que GRUB_DEFAULT=1 seguido de update-grub não altera nada num servidor alugado, embora os mesmos dois passos funcionem numa instalação num portátil.
Siga esta ordem. Confirme que pode escolher o kernel. Leia todos os ficheiros de entrada, incluindo os adicionados pelo fornecedor. Leia o resultado gerado e conte as entradas que ele contém efetivamente. Só depois escolha um método de fixação. Se fizer isto de forma errada numa máquina à qual só acede por SSH, poderá precisar de uma consola de recuperação. Por isso, as opções mais seguras estão no fim desta página e são frequentemente as corretas.
Primeiro confirme que o kernel é seu para fixar
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virt, kvm, qemu ou xen indica que está a utilizar o seu próprio kernel e que tudo o que segue se aplica. lxc ou openvz indica que o servidor partilha o kernel do host. Nesse caso, não existe um bootloader seu e não há nada para fixar. Nesse cenário, uname -r apresenta uma versão que não aparece em /boot/vmlinuz-*, porque o kernel em execução pertence ao host e nenhuma configuração no seu disco pode alterá-lo.
ls -1 /boot/vmlinuz-* é a lista real de kernels que pode escolher. Se tiver uma única linha, o kernel anterior já foi eliminado e nenhuma configuração do bootloader o pode restaurar. Isso normalmente acontece durante um autoremove. Vale a pena compreender esse processo antes de remover kernels antigos no Ubuntu num servidor importante.
O ficheiro que edita não é o ficheiro que o GRUB lê
/etc/default/grub contém atribuições simples de variáveis da shell. É uma entrada. /boot/grub/grub.cfg é a saída e começa com # DO NOT EDIT THIS FILE e o motivo. Tudo o que escrever na saída desaparece na próxima instalação ou remoção de um pacote do kernel, porque os scripts desses pacotes regeneram o ficheiro.
cat /usr/sbin/update-grubupdate-grub é um wrapper. Executa grub-mkconfig -o /boot/grub/grub.cfg, que lê as variáveis, executa todos os scripts em /etc/grub.d/ e escreve o resultado. São dois comandos numa só direção: as entradas entram e grub.cfg sai.
O que substitui a sua configuração: /etc/default/grub.d
grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/O segundo caminho é a parte que costuma passar despercebida. grub-mkconfig obtém primeiro as definições de /etc/default/grub e, depois, todos os ficheiros *.cfg em /etc/default/grub.d/ pela ordem dos nomes glob. Veja o código que faz isso:
grep -n 'default/grub' /usr/sbin/grub-mkconfigA obtenção das definições usa a shell simples, por isso a última atribuição prevalece. As imagens cloud do Ubuntu incluem ficheiros nesse diretório e definem valores como o tempo limite e a linha de comandos do kernel depois de o seu ficheiro já ter sido lido. O seu valor GRUB_TIMEOUT=10 em /etc/default/grub é substituído pouco depois por um ficheiro do fornecedor que o define como 0. O comando grep acima mostra as atribuições exatas na sua imagem; consulte essas atribuições em vez de confiar nesta frase.
A regra prática é colocar as suas definições num ficheiro que fique por último na ordenação, como /etc/default/grub.d/99-local.cfg, em vez de editar /etc/default/grub. Assim, nada fornecido pela imagem será processado depois do seu ficheiro.
Por que GRUB_FORCE_PARTUUID torna irrelevante a seleção no 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 instrui o gerador a localizar o sistema de ficheiros raiz pelo UUID da partição, gravado diretamente na linha de comandos do kernel como root=PARTUUID=..., em vez de procurar um UUID de sistema de ficheiros durante o arranque. O fornecedor da imagem define esta variável porque permite que uma única imagem de disco arranque de forma fiável em hardware diferente daquele onde foi criada. O segundo grep mostra o código que atua sobre a variável, em /etc/grub.d/10_linux. Esse script está no seu próprio disco e é a autoridade sobre o comportamento da sua imagem.
A consequência é o que importa aqui: nesse caminho, o gerador escreve uma entrada de arranque direta em vez de uma lista completa dos kernels instalados. Conte quantas entradas foram criadas.
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfgSe a contagem for 1, não existe uma segunda entrada para selecionar, portanto GRUB_DEFAULT=1 identifica uma entrada inexistente. O GRUB não consegue resolvê-la e arranca a primeira entrada, que é o novo kernel que pretendia evitar. grub-set-default também não ajuda, porque o problema não está na predefinição. O menu que está a tentar selecionar nunca foi gerado.
Para recuperar um menu completo, mova o ficheiro do fornecedor para outro local e pré-visualize o resultado antes de o aplicar. grub-mkconfig sem -o escreve na saída padrão e não altera nada no 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) 'Uma contagem que passa de 1 para várias entradas significa que as entradas aparecem quando a imposição é removida. Nada foi escrito ainda. Reponha o ficheiro se a segunda contagem não parecer correta, porque o PARTUUID imposto é o mecanismo usado pela imagem do seu fornecedor para localizar o sistema de ficheiros raiz, e removê-lo coloca a máquina no caminho de pesquisa. Crie um snapshot antes de executar update-grub efetivamente.
Se o único objetivo for sobreviver a um kernel problemático, pare aqui e use as opções mais seguras apresentadas abaixo. Reconstruir o menu de arranque num servidor remoto para escapar a uma única atualização envolve mais risco do que o problema justifica.
Por que os índices das entradas são a escolha errada para fixar
GRUB_DEFAULT aceita um número, um título ou um identificador. Os números contam as entradas de nível superior a partir de 0. Uma entrada aninhada usa > como separador. Assim, GRUB_DEFAULT="1>2" significa a entrada no índice 2 dentro do submenu no índice 1.
Os índices mudam. 10_linux lista os kernels do mais recente para o mais antigo. Por isso, instalar um kernel desloca todas as entradas anteriores uma posição para baixo, e remover um kernel desloca-as para cima. O seu 1>2 continua a ser resolvido depois disso. Agora, ele identifica um kernel diferente. Nada gera um erro ou aviso, e você só descobre o problema depois de um reboot.
Os identificadores não mudam, porque cada um contém a versão do kernel. Leia o seu:
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfgIgnore as primeiras linhas da saída. Elas correspondem à variável definida no cabeçalho. Depois disso, o lado esquerdo é o título que o leitor vê, e o lado direito é o identificador que você passa às ferramentas. Para uma entrada dentro de um submenu, una o identificador do submenu ao identificador da entrada usando >, nessa ordem, exatamente como na forma numérica.
Arrancar uma vez o kernel anterior com grub-reboot
Uma seleção única é a opção correta num servidor remoto porque é automaticamente revertida. grub-reboot escreve next_entry em /boot/grub/grubenv. O GRUB lê essa variável, limpa-a e guarda o valor limpo antes de arrancar qualquer coisa. Assim, um kernel que entra em panic não é tentado novamente no arranque seguinte. Tem uma tentativa e, depois, a máquina regressa automaticamente ao seu padrão normal.
Primeiro, confirme se a configuração gerada lê essa variável:
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfgDeve encontrar uma linha load_env e um bloco que define default a partir de next_entry. Se o grep não imprimir nada, a sua imagem nunca lê grubenv durante o arranque. Nesse caso, grub-reboot será aceite pela shell e depois ignorado pelo bootloader. Esse é o mesmo caminho de arranque direto forçado da secção anterior, agora presente num segundo local.
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listgrub-editenv list deve agora imprimir uma linha next_entry= com exatamente o valor que passou. Abra a consola do seu fornecedor num separador do navegador. Depois, reinicie e verifique o resultado.
sudo rebootuname -runame -r a indicar a versão anterior significa que o bloqueio funcionou. A indicação da versão nova significa que o identificador não foi resolvido ou que grubenv não está a ser lido. Em qualquer dos casos, a máquina está disponível, que é precisamente o objetivo de usar a forma de execução única.
Faça a seleção persistir com GRUB_DEFAULT=saved
GRUB_DEFAULT=saved faz com que o padrão venha de saved_entry em grubenv, e esse valor é definido com grub-set-default. A configuração sobrevive às instalações de kernels, porque update-grub reescreve grub.cfg e nunca altera 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.cfgO último comando deve imprimir set default="${saved_entry}". Se imprimir set default="0", algo carregado depois do seu ficheiro definiu GRUB_DEFAULT novamente com um valor literal. Nesse caso, liste /etc/default/grub.d/ outra vez e confirme que 99-local.cfg fica efetivamente por último na ordenação.
GRUB_SAVEDEFAULT=true é uma definição diferente e é fácil confundi-la com esta. Ela guarda o kernel que acabou de arrancar como o novo padrão. Assim, o padrão acompanha o último arranque bem-sucedido. Num servidor, isso significa que um reboot não supervisionado pode alterar silenciosamente a sua seleção fixa. Deixe-a desativada, a menos que seja esse o comportamento pretendido.
Uma seleção fixa por identificador ainda pode falhar de uma forma. Se remover o kernel identificado, o identificador deixa de ser resolvido e o sistema volta à primeira entrada. Por isso, mantenha também o pacote bloqueado ou impeça que esse kernel seja removido automaticamente.
Obter o menu na consola do provedor
A seleção interativa exige que o menu esteja visível, mas as imagens de cloud ocultam-no. Coloque estas linhas no ficheiro que é processado por último e execute sudo update-grub.
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10GRUB_TIMEOUT_STYLE=hidden em conjunto com GRUB_TIMEOUT=0 não mostra nada, pelo que quem observa a consola vê as mensagens do kernel começar imediatamente e conclui que o bootloader foi ignorado. GRUB_RECORDFAIL_TIMEOUT é o timeout separado usado depois de um boot que não foi concluído. As imagens de cloud também o definem como 0. Por isso, um servidor que acabou de falhar no boot continua sem parar para aguardar a sua intervenção.
Se o provedor disponibilizar uma consola série em vez de uma consola gráfica e continuar sem ver nada, o GRUB está a escrever num terminal que não consegue ver. Adicione as duas linhas, porque a primeira seleciona as saídas e a segunda configura a porta:
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"A partir de agora, são adicionados dez segundos a todos os boots. Volte a definir o timeout como 0 quando terminar.
Opções mais seguras do que editar o bootloader
Alterar a entrada do bootloader numa máquina à qual só acede por SSH é a opção de maior risco desta página. Existem alternativas mais simples, que normalmente resolvem o problema real.
Bloqueie os pacotes do kernel. Se o objetivo é "não me instale um kernel mais recente", informe o gestor de pacotes em vez de alterar o 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 showholdUse os nomes apresentados pelo primeiro comando, porque as imagens cloud normalmente instalam a variante virtual ou kvm, e não generic. Um pacote bloqueado é ignorado pelo apt upgrade, que o indica com The following packages have been kept back:, e também é ignorado pelas atualizações não assistidas no Ubuntu. O custo é real: um kernel bloqueado deixa de receber correções de segurança. Trate esta medida como uma pausa com uma data definida e remova o bloqueio com sudo apt-mark unhold. Se evita atualizações do kernel porque os reboots causam indisponibilidade, e não porque um kernel específico tem problemas, as correções live do kernel numa VPS resolvem esse problema.
Crie um snapshot antes da atualização. Um snapshot pode ser restaurado em minutos, sem introduzir comandos na consola e sem o risco de uma alteração parcial do bootloader. Crie o snapshot, atualize, faça reboot e verifique. Se o novo kernel apresentar problemas, faça rollback e o caminho de arranque ficará exatamente como estava.
Use a consola ou uma imagem de recuperação numa máquina que já está indisponível. Quando o servidor já não arranca, a configuração do bootloader não é o local onde deve corrigir o problema. Esse processo de recuperação é independente: o que fazer quando uma VPS não arranca depois de uma atualização do kernel.
O que falha e a mensagem que verá
A sua alteração em /boot/grub/grub.cfg desapareceu. Foi instalado ou removido um pacote do kernel, o script do maintainer foi executado em update-grub e o ficheiro foi gerado novamente a partir das entradas. O cabeçalho # DO NOT EDIT THIS FILE identifica as duas localizações de entrada. Edite essas localizações.
grub-editenv: error: environment block too small. /boot/grub/grubenv está em falta ou truncado. Recrie-o com sudo grub-editenv /boot/grub/grubenv create, defina novamente o seu valor e confirme com sudo grub-editenv list.
Um kernel fixado entra em panic com VFS: Unable to mount root fs on unknown-block(0,0). A entrada fixada aponta para um kernel ou initrd que já não está no disco, normalmente porque o pacote foi removido enquanto o identificador permaneceu em grubenv. A recuperação consiste em arrancar pela consola uma entrada funcional e, depois, limpar o valor obsoleto.
uname -r não muda depois de um reboot que deveria alterá-lo. Verifique três pontos, por esta ordem: grub-editenv list ainda mostra o seu valor ou esse valor já foi consumido; o identificador que definiu aparece no grub.cfg atual; grub.cfg contém uma linha set default que lê a variável definida. Uma dessas três condições explica sempre o problema.
O menu apareceu sozinho depois de uma falha. O GRUB regista um arranque falhado em grubenv como recordfail=1. Isso força a apresentação do menu no arranque seguinte para permitir a intervenção de um administrador. Limpe-o com sudo grub-editenv /boot/grub/grubenv unset recordfail quando a máquina estiver estável.
A frase que vale a pena guardar é esta: o ficheiro que edita não é o ficheiro que o GRUB lê. Numa imagem cloud, a diferença entre ambos é a origem da confusão. Leia primeiro a configuração gerada. Todas as decisões nesta página dependem do que ela realmente indica.
FAQ
Por que GRUB_DEFAULT=1 não altera o kernel com que a minha VPS arranca?
Porque, numa cloud image do Ubuntu, o /boot/grub/grub.cfg gerado muitas vezes contém apenas uma entrada de arranque. Assim, o índice 1 não corresponde a nada e o GRUB recua para a primeira entrada. Confirme com sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg. Um valor de 1 confirma a causa. Essa configuração é definida por GRUB_FORCE_PARTUUID, pelo fornecedor da imagem, num ficheiro em /etc/default/grub.d/. Isso faz com que o gerador siga um caminho de arranque direto, em vez de criar uma lista completa dos kernels instalados. Encontre o ficheiro com grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.
Como arranco o kernel anterior apenas uma vez?
Execute sudo grub-reboot '<identifier>' com um identificador copiado do seu próprio grub.cfg e reinicie com a consola do fornecedor já aberta. O GRUB limpa next_entry antes do arranque. Assim, a escolha aplica-se a uma única tentativa e um kernel que entre em panic não volta a ser tentado. Confirme que o valor foi aplicado com sudo grub-editenv list. Antes de depender deste método, execute sudo grep -n next_entry /boot/grub/grub.cfg, porque uma imagem cuja configuração nunca carrega grubenv ignora o comando sem apresentar um erro.
Devo fixar pelo número da entrada ou pelo identificador?
Pelo identificador. Os números das entradas são posições numa lista que 10_linux recria, começando pelo kernel mais recente. Por isso, instalar ou remover qualquer kernel altera esses números. Um 1>2 antigo ainda pode resolver para uma entrada real, mas incorreta, sem apresentar qualquer aviso. Os identificadores contêm a versão do kernel. Assim, correspondem ao kernel pretendido ou não são resolvidos. Liste-os com sudo grep -n menuentry_id_option /boot/grub/grub.cfg e copie a string entre aspas que aparece depois de cada linha de entrada.
Manter o pacote do kernel é mais seguro do que alterar o bootloader?
Para o objetivo habitual, sim. sudo apt-mark hold linux-image-virtual linux-headers-virtual impede que um kernel mais recente seja instalado. Assim, o caminho de arranque não muda e não há nada que possa ser configurado incorretamente através de uma consola à qual talvez não tenha acesso. Verifique primeiro os nomes das variantes instaladas no seu próprio sistema com apt list --installed e confirme o bloqueio com apt-mark showhold. A desvantagem é que um kernel bloqueado não recebe correções de segurança. Por isso, defina quando executará sudo apt-mark unhold antes de aplicar o bloqueio.