SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

Jak trwale ustawić wersję jądra w VPS z Ubuntu

Edycja GRUB_DEFAULT nie działa na obrazach chmurowych. Sprawdź, jak poprawnie odczytać wpisy menu i przypiąć konkretne jądro bez ryzyka utraty dostępu do serwera przez SSH.

Co decyduje o tym, które jądro uruchamia VPS

O tym, które jądro uruchomi się przy następnym starcie VPS, decyduje jeden wygenerowany plik: /boot/grub/grub.cfg. Tego pliku nie należy edytować bezpośrednio. Należy edytować pliki wejściowe i wygenerować go ponownie. W obrazach chmurowych Ubuntu jeden z tych plików wejściowych pochodzi od dostawcy obrazu. Może on sprawić, że wybór w menu stanie się nieistotny. Dlatego wykonanie GRUB_DEFAULT=1, a następnie update-grub nie przynosi żadnych zmian na wynajętym serwerze, podczas gdy te same kroki działają na instalacji na laptopie.

Należy postępować zgodnie z poniższą kolejnością. Najpierw należy potwierdzić, czy wybór jądra faktycznie należy do użytkownika. Następnie trzeba przeczytać wszystkie pliki wejściowe, w tym te dodane przez dostawcę. Należy sprawdzić wygenerowany plik wyjściowy i policzyć wpisy, które faktycznie zawiera. Dopiero wtedy można wybrać metodę przypięcia wersji jądra. Błąd w tym procesie na maszynie dostępnej wyłącznie przez SSH wymusza użycie konsoli ratunkowej, dlatego najbezpieczniejsze rozwiązania znajdują się na końcu tej strony i często są one tymi właściwymi.

Najpierw sprawdź, czy kernel należy do Ciebie, aby móc go przypiąć

uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*

systemd-detect-virt wyświetlenie kvm, qemu lub xen oznacza, że używasz własnego kernela i wszystko poniżej ma zastosowanie. Wyświetlenie lxc lub openvz oznacza, że serwer współdzieli kernel hosta, więc nie posiadasz własnego bootloadera i nie ma czego przypinać. W takim przypadku uname -r zgłasza wersję, która w ogóle nie pojawia się w /boot/vmlinuz-*, ponieważ działający kernel należy do hosta i żadne ustawienie na Twoim dysku nie może tego zmienić.

ls -1 /boot/vmlinuz-* to rzeczywista lista kerneli, pomiędzy którymi możesz wybierać. Jeśli zawiera ona tylko jedną linię, poprzedni kernel został już usunięty i żadne ustawienie bootloadera go nie przywróci. Zazwyczaj dzieje się to podczas operacji autoremove, co warto zrozumieć przed przystąpieniem do usuwania starych kerneli w Ubuntu na serwerze, który jest dla Ciebie istotny.

Edytowany plik nie jest plikiem odczytywanym przez GRUB

/etc/default/grub zawiera przypisania zwykłych zmiennych powłoki. Jest to plik wejściowy. /boot/grub/grub.cfg stanowi plik wyjściowy i rozpoczyna się od # DO NOT EDIT THIS FILE oraz uzasadnienia. Wszystkie zmiany wprowadzone w pliku wyjściowym zostaną utracone przy następnej instalacji lub usunięciu pakietu jądra, ponieważ skrypty pakietów generują go ponownie.

cat /usr/sbin/update-grub

update-grub jest skryptem opakowującym. Uruchamia on grub-mkconfig -o /boot/grub/grub.cfg, który odczytuje zmienne, wykonuje każdy skrypt znajdujący się w /etc/grub.d/ i zapisuje wynik. Dwa polecenia, jeden kierunek: dane wejściowe są przetwarzane, a na wyjściu powstaje grub.cfg.

Co nadpisuje ustawienia: /etc/default/grub.d

grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/

Druga ścieżka jest często pomijana. grub-mkconfig wczytuje najpierw /etc/default/grub, a następnie każdy plik *.cfg w /etc/default/grub.d/ zgodnie z kolejnością glob. Należy przeanalizować kod, który za to odpowiada:

grep -n 'default/grub' /usr/sbin/grub-mkconfig

Wczytywanie odbywa się za pomocą zwykłego powłoki, więc ostatnie przypisanie jest wiążące. Obrazy chmurowe Ubuntu zawierają pliki w tym katalogu, które ustawiają parametry takie jak timeout oraz linię poleceń jądra już po wczytaniu pliku użytkownika. Wartość GRUB_TIMEOUT=10 w /etc/default/grub jest nadpisywana chwilę później przez plik dostawcy, który ustawia ją na 0. Powyższe polecenie grep wyświetla dokładne przypisania w danym obrazie, dlatego należy polegać na ich treści, a nie na tym opisie.

Praktyczna zasada brzmi: własne ustawienia należy umieszczać w pliku, który jest sortowany jako ostatni, na przykład /etc/default/grub.d/99-local.cfg, zamiast edytować /etc/default/grub. Dzięki temu żadne ustawienie dostarczone z obrazem nie zostanie zastosowane po konfiguracji użytkownika.

Dlaczego GRUB_FORCE_PARTUUID sprawia, że wybór w menu staje się nieistotny

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.cfg

GRUB_FORCE_PARTUUID instruuje generator, aby odnalazł główny system plików na podstawie UUID partycji, wpisując go bezpośrednio do linii poleceń jądra jako root=PARTUUID=..., zamiast przeszukiwać system plików według UUID podczas rozruchu. Dostawca obrazu ustawia tę opcję, ponieważ pozwala ona na niezawodne uruchomienie jednego obrazu dysku na sprzęcie, dla którego nie został on przygotowany. Drugie polecenie grep wskazuje kod, który operuje na tej zmiennej w /etc/grub.d/10_linux. Ten skrypt znajduje się na Twoim dysku i jest rozstrzygający w kwestii działania obrazu.

Konsekwencja jest tutaj kluczowa: na tej ścieżce generator tworzy bezpośredni wpis rozruchowy zamiast pełnej listy zainstalowanych jąder. Policz, co ostatecznie uzyskałeś.

sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg

Jeśli liczba wpisów wynosi 1, nie ma drugiego wpisu do wyboru, więc GRUB_DEFAULT=1 wskazuje wpis, który nie istnieje. GRUB nie może go rozpoznać, więc uruchamia pierwszy wpis, czyli nowe jądro, którego próbowałeś uniknąć. grub-set-default również nie pomaga, ponieważ problemem nie jest wartość domyślna. Menu, z którego próbujesz dokonać wyboru, nigdy nie zostało wygenerowane.

Aby przywrócić pełne menu, przenieś plik dostawcy w inne miejsce i wyświetl podgląd wyniku przed jego zatwierdzeniem. grub-mkconfig bez -o zapisuje wynik na standardowe wyjście i nie wprowadza żadnych zmian na dysku.

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) '

Liczba wpisów, która wzrasta z 1 do kilku, oznacza, że wpisy pojawiają się po usunięciu wymuszenia. Nic nie zostało jeszcze zapisane. Przywróć plik, jeśli druga liczba nie wygląda poprawnie, ponieważ wymuszony PARTUUID jest sposobem, w jaki obraz dostawcy lokalizuje główny system plików, a jego usunięcie przenosi maszynę na ścieżkę wyszukiwania. Wykonaj migawkę przed uruchomieniem update-grub w rzeczywistym środowisku.

Jeśli Twoim jedynym celem jest przetrwanie jednego wadliwego jądra, zatrzymaj się tutaj i skorzystaj z bezpieczniejszych opcji opisanych poniżej. Przebudowa menu rozruchowego na zdalnym serwerze w celu uniknięcia pojedynczej aktualizacji wiąże się z większym ryzykiem, niż jest to warte.

Dlaczego numery wpisów są niewłaściwym sposobem przypinania

GRUB_DEFAULT akceptuje numer, tytuł lub identyfikator. Numery liczą wpisy najwyższego poziomu od 0. Wpis zagnieżdżony używa > jako separatora, więc GRUB_DEFAULT="1>2" oznacza wpis o indeksie 2 wewnątrz podmenu o indeksie 1.

Indeksy ulegają zmianie. 10_linux wyświetla jądra od najnowszego, więc instalacja jądra przesuwa każdy starszy wpis o jeden w dół, a usunięcie jądra przesuwa je w górę. Twoje starannie dobrane 1>2 nadal rozwiązuje się poprawnie po takiej operacji. Wskazuje ono teraz jednak na inne jądro. Nie występuje żaden błąd ani ostrzeżenie, a użytkownik dowiaduje się o zmianie dopiero po restarcie.

Identyfikatory nie zmieniają się, ponieważ każdy z nich zawiera wersję jądra. Odczytaj swoje:

sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfg

Pomiń pierwsze kilka linii wyjścia, które zawierają zmienną definiowaną w nagłówku. Poniżej, po lewej stronie znajduje się tytuł widoczny dla użytkownika, a po prawej identyfikator przekazywany do narzędzi. W przypadku wpisu wewnątrz podmenu, połącz identyfikator podmenu z identyfikatorem wpisu za pomocą >, zachowując tę kolejność, dokładnie tak samo jak w przypadku formatu numerycznego.

Jednorazowy rozruch poprzedniego jądra za pomocą grub-reboot

Wybór jednorazowy jest właściwym rozwiązaniem na serwerze zdalnym, ponieważ cofa się automatycznie. grub-reboot zapisuje next_entry w /boot/grub/grubenv. GRUB odczytuje tę zmienną, czyści ją i zapisuje wyczyszczoną wartość przed uruchomieniem czegokolwiek, dzięki czemu jądro, które powoduje kernel panic, nie jest ponownie uruchamiane przy następnym restarcie. Użytkownik otrzymuje jedną próbę, po czym maszyna samodzielnie wraca do domyślnej konfiguracji.

Najpierw należy potwierdzić, czy wygenerowana konfiguracja w ogóle odczytuje tę zmienną:

sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfg

Wymagana jest linia load_env oraz blok, który ustawia default na podstawie next_entry. Jeśli polecenie grep nic nie zwraca, obraz nigdy nie odczytuje grubenv podczas rozruchu, więc grub-reboot zostanie zaakceptowane w powłoce, a następnie zignorowane przez program rozruchowy. Jest to ta sama ścieżka wymuszonego bezpośredniego rozruchu z poprzedniej sekcji, pojawiająca się w drugim miejscu.

sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv list

grub-editenv list powinno teraz wyświetlić linię next_entry= zawierającą dokładnie to, co zostało przekazane. Należy otworzyć konsolę dostawcy w karcie przeglądarki, zrestartować maszynę i sprawdzić wynik.

sudo reboot
uname -r

uname -r wskazujące na starszą wersję oznacza, że przypięcie zadziałało. Wskazanie nowej wersji oznacza, że identyfikator nie został rozpoznany lub grubenv nie jest odczytywane, a maszyna i tak uruchomiła się, co jest celem stosowania metody jednorazowej.

Utrwalenie wyboru za pomocą GRUB_DEFAULT=saved

GRUB_DEFAULT=saved sprawia, że wartość domyślna jest pobierana z saved_entry w grubenv, a ustawia się ją za pomocą grub-set-default. Ustawienie to przetrwa instalacje nowych jąder, ponieważ update-grub nadpisuje grub.cfg i nigdy nie modyfikuje 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.cfg

Ostatnie polecenie musi zwrócić set default="${saved_entry}". Jeśli zwróci set default="0", oznacza to, że skrypt uruchomiony po Twoim pliku przywrócił GRUB_DEFAULT do wartości literalnej. W takim przypadku należy ponownie wyświetlić /etc/default/grub.d/ i sprawdzić, czy 99-local.cfg faktycznie znajduje się na końcu listy.

GRUB_SAVEDEFAULT=true to inne ustawienie, które łatwo pomylić z powyższym. Powoduje ono, że ostatnio uruchomione jądro staje się nowym wyborem domyślnym, więc domyślny wpis podąża za ostatnim udanym rozruchem. Na serwerze oznacza to, że automatyczny restart może nieoczekiwanie zmienić przypiętą wersję jądra. Pozostaw tę opcję wyłączoną, chyba że jest to pożądane zachowanie.

Przypięcie za pomocą identyfikatora nadal może zawieść w jednym przypadku. Usunięcie jądra, do którego odnosi się identyfikator, spowoduje, że przestanie on być rozpoznawany, co przywróci wybór pierwszego wpisu na liście. Dlatego należy również zablokować pakiet przed aktualizacją lub wykluczyć dane jądro z procesu autoremove.

Wyświetlanie menu w konsoli dostawcy

Interaktywny wybór wymaga wyświetlenia menu na ekranie, co w obrazach chmurowych jest domyślnie ukryte. Należy umieścić poniższe wpisy w pliku, który jest przetwarzany jako ostatni, a następnie wykonać sudo update-grub.

GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10

GRUB_TIMEOUT_STYLE=hidden w połączeniu z GRUB_TIMEOUT=0 powoduje brak jakiegokolwiek wyjścia, przez co osoba obserwująca konsolę widzi natychmiastowe uruchomienie komunikatów jądra i wyciąga błędny wniosek, że bootloader został pominięty. GRUB_RECORDFAIL_TIMEOUT to osobny limit czasu stosowany po nieudanym rozruchu; obrazy chmurowe ustawiają go na 0, dlatego serwer, który nie zdołał się uruchomić, nie zatrzymuje się i nie oczekuje na reakcję użytkownika.

Jeśli dostawca udostępnia konsolę szeregową zamiast graficznej, a obraz nadal pozostaje czarny, oznacza to, że GRUB zapisuje dane do terminala, którego nie widać. Należy dodać obie linie jednocześnie, ponieważ pierwsza wybiera wyjścia, a druga konfiguruje port:

GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"

Od teraz do każdego rozruchu dodawane jest dziesięć sekund. Po zakończeniu prac należy przywrócić wartość limitu czasu na 0.

Bezpieczniejsze alternatywy dla edycji bootloadera

Zmiana parametrów bootloadera na maszynie dostępnej wyłącznie przez SSH jest najbardziej ryzykownym działaniem opisanym w tym dokumencie. Istnieją prostsze rozwiązania, które zazwyczaj eliminują rzeczywistą przyczynę problemu.

Wstrzymanie pakietów jądra. Jeśli celem jest uniknięcie instalacji nowszego jądra, należy poinformować o tym menedżer pakietów, zamiast modyfikować 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 showhold

Należy użyć nazw wyświetlonych przez pierwsze polecenie, ponieważ obrazy chmurowe często instalują wariant virtual lub kvm zamiast generic. Wstrzymany pakiet jest pomijany przez apt upgrade, co jest sygnalizowane komunikatem The following packages have been kept back:; jest on również ignorowany przez automatyczne aktualizacje w systemie Ubuntu. Wiąże się to z realnym kosztem: wstrzymane jądro przestaje otrzymywać poprawki bezpieczeństwa, dlatego należy traktować to jako tymczasowe rozwiązanie z określonym terminem i odblokować pakiet za pomocą sudo apt-mark unhold. Jeśli unikanie aktualizacji jądra wynika z kosztów przestoju przy restarcie, a nie z wadliwego działania konkretnej wersji, rozwiązaniem jest live kernel patching na serwerze VPS.

Wykonanie migawki przed aktualizacją. Migawka pozwala na przywrócenie systemu w kilka minut, bez konieczności pracy w konsoli i ryzyka niepełnego wprowadzenia zmian w bootloaderze. Należy wykonać migawkę, przeprowadzić aktualizację, zrestartować system i zweryfikować działanie. Jeśli nowe jądro działa nieprawidłowo, przywrócenie migawki przywraca dokładnie poprzednią ścieżkę rozruchu.

Użycie konsoli lub obrazu ratunkowego dla niedziałającego serwera. Gdy serwer nie uruchamia się, konfiguracja bootloadera nie jest miejscem, w którym należy szukać naprawy. Ścieżka odzyskiwania stanowi odrębną procedurę: postępowanie w przypadku, gdy VPS nie uruchamia się po aktualizacji jądra.

Co ulega awarii i jaki komunikat zobaczysz

Twoja edycja w /boot/grub/grub.cfg zniknęła. Zainstalowano lub usunięto pakiet jądra, jego skrypt opiekuna uruchomił update-grub, a plik został wygenerowany ponownie na podstawie danych wejściowych. Nagłówek # DO NOT EDIT THIS FILE wskazuje dwie lokalizacje wejściowe. Edytuj te pliki.

grub-editenv: error: environment block too small. Plik /boot/grub/grubenv nie istnieje lub jest ucięty. Odtwórz go za pomocą sudo grub-editenv /boot/grub/grubenv create, następnie ponownie ustaw swoją wartość i potwierdź ją za pomocą sudo grub-editenv list.

Przypięte jądro powoduje kernel panic z VFS: Unable to mount root fs on unknown-block(0,0). Wpis, który przypiąłeś, wskazuje na jądro lub initrd, którego nie ma już na dysku. Zazwyczaj dzieje się tak, ponieważ pakiet został usunięty, podczas gdy identyfikator pozostał w grubenv. Odzyskiwanie polega na uruchomieniu systemu z konsoli przy użyciu działającego wpisu, a następnie wyczyszczeniu nieaktualnej wartości.

uname -r pozostaje bez zmian po restarcie, po którym spodziewałeś się zmiany. Sprawdź kolejno trzy rzeczy: czy grub-editenv list nadal zawiera Twoją wartość, czy została ona skonsumowana; czy identyfikator, który ustawiłeś, pojawia się w bieżącym grub.cfg; czy grub.cfg zawiera linię set default, która odczytuje ustawioną przez Ciebie zmienną. Jeden z tych trzech punktów zawsze wyjaśnia przyczynę.

Menu pojawiło się samoczynnie po awarii. GRUB rejestruje nieudany rozruch w grubenv jako recordfail=1, co wymusza wyświetlenie menu przy kolejnym uruchomieniu, aby umożliwić interwencję użytkownika. Wyczyść ten stan za pomocą sudo grub-editenv /boot/grub/grubenv unset recordfail, gdy maszyna będzie już sprawna.


Jedno zdanie, które warto zapamiętać: plik, który edytujesz, nie jest plikiem, który odczytuje GRUB, a w obrazach chmurowych to właśnie rozbieżność między nimi jest źródłem problemów. Najpierw przeczytaj wygenerowaną konfigurację. Każda decyzja na tej stronie wynika z tego, co jest w niej faktycznie zapisane.

FAQ

Dlaczego GRUB_DEFAULT=1 nie zmienia jądra, z którego uruchamia się mój VPS?

Ponieważ w obrazach chmurowych Ubuntu wygenerowany /boot/grub/grub.cfg często zawiera tylko jeden wpis rozruchowy, więc indeks 1 nie wskazuje na nic, a GRUB wraca do pierwszego wpisu. Potwierdź to za pomocą sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg. Wynik 1 jest odpowiedzią. Przyczyną jest GRUB_FORCE_PARTUUID, ustawione przez dostawcę obrazu w pliku w /etc/default/grub.d/, co kieruje generator na ścieżkę bezpośredniego rozruchu zamiast budowania pełnej listy zainstalowanych jąder. Znajdź ten plik za pomocą grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.

Jak uruchomić poprzednie jądro tylko raz?

Uruchom sudo grub-reboot '<identifier>' z identyfikatorem skopiowanym z własnego grub.cfg, a następnie zrestartuj system, mając już otwartą konsolę dostawcy. GRUB czyści next_entry przed uruchomieniem, więc wybór dotyczy dokładnie jednej próby, a jądro, które wywoła panikę, nie zostanie ponownie wybrane. Potwierdź, że wartość została zapisana za pomocą sudo grub-editenv list. Zanim na tym polegniesz, uruchom sudo grep -n next_entry /boot/grub/grub.cfg, ponieważ obraz, którego konfiguracja nigdy nie ładuje grubenv, zignoruje to polecenie bez zgłaszania błędu.

Czy lepiej przypinać numerem wpisu czy identyfikatorem?

Identyfikatorem. Numery wpisów to pozycje na liście, którą 10_linux przebudowuje, umieszczając najnowsze jądra na początku, więc instalacja lub usunięcie dowolnego jądra zmienia ich kolejność, a nieaktualny 1>2 nadal wskazuje na istniejący, lecz błędny wpis, nie wyświetlając przy tym żadnego ostrzeżenia. Identyfikatory zawierają wersję jądra, więc albo pasują do wybranego jądra, albo nie zostaną rozpoznane. Wyświetl je za pomocą sudo grep -n menuentry_id_option /boot/grub/grub.cfg i skopiuj ciąg znaków w cudzysłowie, który znajduje się w każdej linii wpisu.

Czy blokowanie pakietu jądra jest bezpieczniejsze niż zmiana bootloadera?

W typowym przypadku tak. sudo apt-mark hold linux-image-virtual linux-headers-virtual całkowicie blokuje instalację nowszego jądra, więc ścieżka rozruchu nigdy się nie zmienia i nie ma ryzyka błędu, którego nie dałoby się naprawić z poziomu konsoli, do której możesz nie mieć dostępu. Najpierw sprawdź nazwy wariantów zainstalowane w twoim systemie za pomocą apt list --installed, a następnie zweryfikuj blokadę za pomocą apt-mark showhold. Ceną jest to, że zablokowane jądro nie otrzymuje poprawek bezpieczeństwa, więc zaplanuj, kiedy uruchomisz sudo apt-mark unhold, zanim nałożysz blokadę.