Fijar el kernel que arrancará tu VPS con GRUB
En una imagen cloud de Ubuntu, GRUB_DEFAULT puede no hacer nada. Lee las entradas reales, cuenta los kernels y fija el próximo arranque sin perder SSH.
Qué decide qué kernel arranca tu VPS
El kernel que arranque tu VPS la próxima vez lo decide un único archivo generado: /boot/grub/grub.cfg. Nunca edites ese archivo. Edita sus entradas y vuelve a generarlo. En una imagen cloud de Ubuntu, una de esas entradas procede del proveedor de la imagen y puede hacer que la selección del menú sea irrelevante. Por eso GRUB_DEFAULT=1 seguido de update-grub no cambia nada en un servidor alquilado, aunque los mismos dos pasos sí funcionen en una instalación de laptop.
Sigue este orden. Confirma que puedes elegir el kernel. Lee todos los archivos de entrada, incluidos los que haya añadido el proveedor. Lee la salida generada y cuenta las entradas que contiene realmente. Sólo entonces elige un método de fijación. Si te equivocas en una máquina a la que sólo accedes mediante SSH, necesitarás una consola de rescate. Por eso las opciones más seguras aparecen al final de esta página y suelen ser las adecuadas.
Primero compruebe que el kernel es suyo y se puede fijar
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virt al mostrar kvm, qemu o xen significa que ejecuta su propio kernel y que todo lo siguiente se aplica. Al mostrar lxc o openvz, el servidor comparte el kernel del host, por lo que no tiene un bootloader propio ni nada que fijar. En ese caso, uname -r informa de una versión que no aparece en absoluto en /boot/vmlinuz-*, porque el kernel en ejecución pertenece al host y ninguna configuración del disco puede cambiarlo.
ls -1 /boot/vmlinuz-* es la lista real de kernels que puede elegir. Si contiene una sola línea, el kernel anterior ya se ha eliminado y ninguna configuración del bootloader puede recuperarlo. Esto suele ocurrir durante un autoremove. Conviene entenderlo antes de eliminar kernels antiguos en Ubuntu en un sistema importante.
El archivo que edita no es el archivo que lee GRUB
/etc/default/grub contiene asignaciones simples de variables de shell. Es una entrada. /boot/grub/grub.cfg es la salida, y comienza con # DO NOT EDIT THIS FILE y el motivo. Todo lo que escriba en la salida desaparece la próxima vez que se instale o elimine un paquete del kernel, porque los scripts de esos paquetes la regeneran.
cat /usr/sbin/update-grubupdate-grub es un contenedor. Ejecuta grub-mkconfig -o /boot/grub/grub.cfg, que lee las variables, ejecuta todos los scripts de /etc/grub.d/ y escribe el resultado. Son dos comandos en una sola dirección: las entradas entran y grub.cfg sale.
Qué sobrescribe la configuración: /etc/default/grub.d
grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/La segunda ruta es la parte que suele pasarse por alto. grub-mkconfig obtiene primero el contenido de /etc/default/grub y, después, el de todos los archivos *.cfg de /etc/default/grub.d/ en el orden de expansión glob. Consulte el código que lo hace:
grep -n 'default/grub' /usr/sbin/grub-mkconfigLa carga del archivo es una operación normal de shell, por lo que prevalece la última asignación. Las imágenes cloud de Ubuntu incluyen archivos en ese directorio y establecen valores como el tiempo de espera y la línea de comandos del kernel después de que ya se haya leído su archivo. Su GRUB_TIMEOUT=10 en /etc/default/grub se sobrescribe un momento después con un archivo del proveedor que lo establece en 0. El comando grep anterior muestra las asignaciones exactas de su imagen, así que consulte esas asignaciones en lugar de confiar en esta explicación.
La regla práctica es la siguiente: coloque su propia configuración en un archivo que se ordene al final, como /etc/default/grub.d/99-local.cfg, en lugar de editar /etc/default/grub. Así, ningún archivo incluido en la imagen podrá cargarse después.
Por qué GRUB_FORCE_PARTUUID hace irrelevante la selección del menú
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 generador que busque el sistema de archivos raíz mediante el UUID de la partición. El generador escribe este valor directamente en la línea de comandos del kernel como root=PARTUUID=..., en lugar de buscar un UUID del sistema de archivos durante el arranque. El proveedor de la imagen lo establece porque permite que una misma imagen de disco arranque de forma fiable en hardware distinto del utilizado para crearla. El segundo grep muestra el código que utiliza la variable, en /etc/grub.d/10_linux. Ese script está en su propio disco y es la referencia que determina el comportamiento de la imagen.
Aquí importa la consecuencia: en esa ruta, el generador escribe una entrada de arranque directa en lugar de una lista completa de los kernels instalados. Cuente cuántas entradas se han generado.
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfgSi el resultado es 1, no existe una segunda entrada que seleccionar. Por tanto, GRUB_DEFAULT=1 hace referencia a una entrada inexistente. GRUB no puede resolverla y arranca la primera entrada, que corresponde al kernel nuevo que se intentaba evitar. grub-set-default tampoco ayuda, porque el valor predeterminado no es el problema. El menú que se intenta seleccionar nunca se generó.
Para recuperar un menú completo, aparte el archivo del proveedor y previsualice el resultado antes de aplicarlo. grub-mkconfig sin -o escribe en la salida estándar y no modifica nada en el 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) 'Si el recuento pasa de 1 a varias entradas, significa que estas aparecen cuando se elimina el forzado. Todavía no se ha escrito nada. Devuelva el archivo a su ubicación original si el segundo recuento no es correcto, porque el PARTUUID forzado permite que la imagen del proveedor localice su sistema de archivos raíz. Al eliminarlo, la máquina pasa a utilizar la ruta de búsqueda. Cree una instantánea antes de ejecutar update-grub realmente.
Si el único objetivo es superar un kernel defectuoso, deténgase aquí y utilice las opciones más seguras que aparecen más abajo. Reconstruir el menú de arranque en un servidor remoto para evitar una sola actualización implica más riesgo del que justifica el problema.
Por qué los números de entrada son una mala opción para fijarlos
GRUB_DEFAULT acepta un número, un título o un identificador. Los números cuentan las entradas de nivel superior desde 0. Una entrada anidada usa > como separador, por lo que GRUB_DEFAULT="1>2" significa la entrada del índice 2 dentro del submenú del índice 1.
Los índices cambian. 10_linux muestra los kernels más recientes primero, por lo que instalar un kernel desplaza cada entrada anterior una posición hacia abajo y eliminar un kernel las desplaza hacia arriba. Tu cuidadoso 1>2 sigue resolviéndose después de eso. Ahora identifica otro kernel. No se produce ningún error ni aparece ninguna advertencia, y lo descubres después de reiniciar.
Los identificadores no cambian, porque cada uno contiene la versión del kernel. Consulta el tuyo:
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfgIgnora las primeras líneas de la salida, que corresponden a la variable definida en la cabecera. Después, el lado izquierdo es el título que ve el usuario y el lado derecho es el identificador que se pasa a las herramientas. Para una entrada dentro de un submenú, une el identificador del submenú y el identificador de la entrada con >, en ese orden, igual que en la forma numérica.
Arrancar una vez el kernel anterior con grub-reboot
Una selección de un solo arranque es la opción adecuada en un servidor remoto porque se revierte por sí sola. grub-reboot escribe next_entry en /boot/grub/grubenv. GRUB lee esa variable, la borra y guarda el valor borrado antes de arrancar nada. Por tanto, un kernel que provoca un panic no se vuelve a intentar en el siguiente arranque. Tiene un único intento y, después, la máquina vuelve por sí sola a su valor predeterminado normal.
Primero confirme que la configuración generada lee esa variable:
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfgDebe aparecer una línea load_env y un bloque que establezca default a partir de next_entry. Si grep no muestra nada, su imagen nunca lee grubenv durante el arranque. En ese caso, grub-reboot se aceptará en el shell y el gestor de arranque lo ignorará. Es la misma ruta de arranque directo forzado de la sección anterior, presente también en otro punto.
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listgrub-editenv list debería mostrar ahora una línea next_entry= con exactamente el valor que indicó. Abra la consola de su proveedor en una pestaña del navegador. Después, reinicie y compruebe el resultado.
sudo rebootuname -rQue uname -r muestre la versión anterior significa que la selección se aplicó correctamente. Si muestra la versión nueva, el identificador no se resolvió o grubenv no se está leyendo. En cualquier caso, la máquina está disponible, que es precisamente el objetivo de usar la opción de un solo arranque.
Haz que la elección persista con GRUB_DEFAULT=saved
GRUB_DEFAULT=saved hace que el valor predeterminado proceda de saved_entry en grubenv, y ese valor se establece con grub-set-default. Se conserva durante las instalaciones del kernel, porque update-grub vuelve a escribir grub.cfg y nunca modifica 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.cfgEl último comando debe mostrar set default="${saved_entry}". Si muestra set default="0", algo cargado después de su archivo volvió a establecer GRUB_DEFAULT como un valor literal. Por tanto, vuelva a enumerar /etc/default/grub.d/ y compruebe que 99-local.cfg quede realmente en último lugar.
GRUB_SAVEDEFAULT=true es una configuración diferente y es fácil confundirla con esta. Guarda lo que acaba de arrancar como el nuevo valor predeterminado, de modo que este sigue el último arranque correcto. En un servidor, un reinicio desatendido puede cambiar silenciosamente la selección fijada. Déjela desactivada salvo que ese sea el comportamiento que necesita.
Una selección fijada por identificador todavía puede fallar de una forma. Si elimina el kernel al que hace referencia, el identificador deja de resolverse y el sistema vuelve a la primera entrada. Por tanto, mantenga también el paquete o impida que ese kernel se elimine mediante autoremove.
Cómo mostrar el menú en la consola del proveedor
La selección interactiva requiere que el menú aparezca en pantalla, pero las imágenes de cloud lo ocultan. Añada estas líneas al archivo que se ordena en último lugar y ejecute sudo update-grub.
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10GRUB_TIMEOUT_STYLE=hidden junto con GRUB_TIMEOUT=0 no muestra nada, por lo que quien observe la consola ve que los mensajes del kernel empiezan de inmediato y concluye que se omitió el cargador de arranque. GRUB_RECORDFAIL_TIMEOUT es el tiempo de espera independiente que se usa después de un arranque que no se completó. Las imágenes de cloud también lo establecen en 0. Por eso, un servidor cuyo arranque acaba de fallar tampoco se detiene ni espera su intervención.
Si el proveedor ofrece una consola serie en lugar de una consola gráfica y sigue sin ver nada, GRUB está escribiendo en un terminal que no puede ver. Añada ambas líneas juntas, porque la primera selecciona las salidas y la segunda configura el puerto:
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"A partir de ahora, se añaden diez segundos a cada arranque. Vuelva a establecer el tiempo de espera en 0 cuando termine.
Opciones más seguras que editar el cargador de arranque
Cambiar la entrada del cargador de arranque en una máquina a la que sólo accede por SSH es la opción de mayor riesgo de esta página. Existen alternativas más sencillas y normalmente resuelven el problema real.
Retenga los paquetes del kernel. Si el objetivo es «no me instales un kernel más nuevo», indíqueselo al gestor de paquetes en lugar de hacerlo al cargador de arranque.
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 los nombres que mostró el primer comando, porque las imágenes de nube suelen instalar la variante virtual o kvm en lugar de generic. Si el kernel más nuevo apareció durante una actualización de la imagen y sospecha que la versión en sí cambió sin avisar, no fue así, porque una versión de mantenimiento incluye las actualizaciones que ya tiene integradas en un nuevo medio de instalación y no ofrece a un servidor ya actualizado nada que no hubiera recibido semanas antes. apt upgrade omite los paquetes retenidos y lo indica con The following packages have been kept back:. Las actualizaciones desatendidas en Ubuntu también los omiten. El coste es real: un kernel retenido deja de recibir correcciones de seguridad. Trátelo como una pausa con fecha de finalización y libérelo con sudo apt-mark unhold. Si evita las actualizaciones del kernel porque los reinicios provocan una interrupción y no porque un kernel concreto sea defectuoso, la aplicación de parches en caliente del kernel en un VPS ofrece una alternativa.
Cree una instantánea antes de la actualización. Una instantánea se restaura en minutos, sin escribir nada en la consola y sin riesgo de dejar un cambio parcial en el cargador de arranque. Cree la instantánea, actualice, reinicie y verifique. Si el kernel nuevo presenta problemas, revierta la instantánea y la ruta de arranque quedará exactamente como estaba.
Use la consola o una imagen de rescate en un equipo que ya está caído. Cuando el servidor ya no arranca, la configuración del cargador de arranque no es el lugar donde debe corregirlo. Esa recuperación sigue su propio procedimiento: qué hacer cuando un VPS no arranca después de una actualización del kernel.
Qué se rompe y qué mensaje verá
Su cambio en /boot/grub/grub.cfg desapareció. Se instaló o eliminó un paquete del kernel, su script del mantenedor se ejecutó mediante update-grub y el archivo se volvió a generar a partir de las entradas. La cabecera # DO NOT EDIT THIS FILE indica las dos ubicaciones de entrada. Edite esas ubicaciones.
grub-editenv: error: environment block too small. Falta /boot/grub/grubenv o está truncado. Vuelva a crearlo con sudo grub-editenv /boot/grub/grubenv create, establezca de nuevo su valor y confírmelo con sudo grub-editenv list.
Un kernel fijado produce un panic con VFS: Unable to mount root fs on unknown-block(0,0). La entrada fijada apunta a un kernel o initrd que ya no existe en el disco, normalmente porque se eliminó el paquete mientras el identificador permanecía en grubenv. La recuperación consiste en arrancar desde la consola una entrada funcional y borrar después el valor obsoleto.
uname -r no cambia después de un reinicio que debía modificarlo. Compruebe tres cosas, en este orden: si grub-editenv list todavía muestra su valor o ya se consumió; si el identificador que estableció aparece en el grub.cfg actual; y si grub.cfg contiene una línea set default que lee la variable que estableció. Una de esas tres condiciones lo explica siempre.
El menú apareció por sí solo después de un bloqueo. GRUB registra un arranque fallido en grubenv como recordfail=1 y eso fuerza la aparición del menú en el arranque siguiente para que una persona pueda intervenir. Bórrelo con sudo grub-editenv /boot/grub/grubenv unset recordfail cuando el equipo vuelva a estar operativo.
La única frase que conviene recordar es esta: el archivo que edita no es el archivo que lee GRUB, y en una imagen cloud la diferencia entre ambos es donde surge la confusión. Lea primero la configuración generada. Todas las decisiones de esta página se basan en lo que realmente indica.
FAQ
¿Por qué GRUB_DEFAULT=1 no cambia el kernel con el que arranca mi VPS?
En una imagen de nube de Ubuntu, el /boot/grub/grub.cfg generado suele contener una sola entrada de arranque. Por eso, el índice 1 no identifica ninguna entrada y GRUB vuelve a la primera. Confírmelo con sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg. Un recuento de 1 es la respuesta. La causa es GRUB_FORCE_PARTUUID, establecido por el proveedor de la imagen en un archivo situado bajo /etc/default/grub.d/. Esto hace que el generador use una ruta de arranque directa en lugar de crear una lista completa de los kernels instalados. Busque el archivo con grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.
¿Cómo arranco el kernel anterior una sola vez?
Ejecute sudo grub-reboot '<identifier>' con un identificador copiado de su propio grub.cfg y reinicie con la consola del proveedor ya abierta. GRUB elimina next_entry antes de arrancar, por lo que la selección se aplica exactamente a un intento. Si un kernel provoca un panic, no se vuelve a intentar con él. Confirme que el valor se haya establecido con sudo grub-editenv list. Antes de depender de esta opción, ejecute sudo grep -n next_entry /boot/grub/grub.cfg, porque una imagen cuya configuración nunca carga grubenv ignorará el comando sin mostrar ningún error.
¿Debo fijar la selección por número de entrada o por identificador?
Por identificador. Los números de entrada son posiciones de una lista que 10_linux reconstruye empezando por el kernel más reciente. Por tanto, instalar o eliminar cualquier kernel los desplaza. Además, un 1>2 obsoleto puede seguir resolviendo a una entrada real, pero incorrecta, sin mostrar ningún aviso. Los identificadores contienen la versión del kernel. Por eso, coinciden con el kernel previsto o no se pueden resolver. Enumérelos con sudo grep -n menuentry_id_option /boot/grub/grub.cfg y copie la cadena entre comillas que aparece después de cada línea de entrada.
¿Es más seguro mantener retenido el paquete del kernel que cambiar el cargador de arranque?
Para el objetivo habitual, sí. sudo apt-mark hold linux-image-virtual linux-headers-virtual impide que llegue un kernel más reciente. Así, la ruta de arranque no cambia y no hay nada que pueda configurarse incorrectamente desde una consola a la que quizá no tenga acceso. Compruebe primero los nombres de las variantes instaladas en su equipo con apt list --installed y verifique la retención con apt-mark showhold. La contrapartida es que un kernel retenido no recibe correcciones de seguridad. Por tanto, decida cuándo ejecutará sudo apt-mark unhold antes de aplicar la retención.