Jak zmienić domyślny kernel na VPS z Ubuntu
Edycja GRUB_DEFAULT nie działa na obrazach chmurowych Ubuntu. Sprawdź jak poprawnie odczytać wpisy menu i przypiąć wersję kernela bez ryzyka utraty dostępu do serwera przez SSH.
Co decyduje o tym, który kernel uruchamia VPS
O tym, który kernel uruchomi się na VPS, decyduje jeden wygenerowany plik: /boot/grub/grub.cfg. Tego pliku nie należy edytować ręcznie. Należy edytować pliki wejściowe i ponownie wygenerować konfigurację. W obrazach chmurowych Ubuntu jeden z plików wejściowych jest dostarczany przez dostawcę 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 laptopowej.
Należy postępować zgodnie z tą kolejnością. Najpierw należy potwierdzić, czy użytkownik ma możliwość wyboru kernela. Następnie należy odczytać wszystkie pliki wejściowe, w tym te dodane przez dostawcę. Należy sprawdzić wygenerowany plik wyjściowy i policzyć rzeczywistą liczbę wpisów. Dopiero wtedy można wybrać metodę przypięcia (pinning). 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 zazwyczaj są one właściwym wyborem.
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 dysku nie może tego zmienić.
ls -1 /boot/vmlinuz-* to rzeczywista lista kerneli, pomiędzy którymi możesz wybierać. Jeśli zawiera 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 bezpośrednio w pliku wyjściowym zostaną utracone przy instalacji lub usunięciu pakietu jądra, ponieważ skrypty pakietowe generują go ponownie.
cat /usr/sbin/update-grubupdate-grub jest skryptem opakowującym. Uruchamia on grub-mkconfig -o /boot/grub/grub.cfg, który odczytuje zmienne, wykonuje wszystkie skrypty znajdujące się w /etc/grub.d/ i zapisuje wynik. Dwa polecenia, jeden kierunek: dane wejściowe są przetwarzane, a wynikiem jest 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/ w kolejności glob. Należy przeanalizować kod, który to wykonuje:
grep -n 'default/grub' /usr/sbin/grub-mkconfigWczytywanie (sourcing) odbywa się za pomocą zwykłej powłoki, więc ostatnie przypisanie jest wiążące. Obrazy chmurowe Ubuntu dostarczają pliki w tym katalogu, które ustawiają parametry takie jak limit czasu (timeout) oraz wiersz poleceń jądra (kernel command line) już po odczytaniu Twojego pliku. Twoje GRUB_TIMEOUT=10 w /etc/default/grub jest nadpisywane chwilę później przez plik dostawcy, który ustawia tę wartość na 0. Powyższe polecenie grep wyświetla dokładne przypisania w Twoim obrazie, więc należy zapoznać się z nimi zamiast polegać wyłącznie 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 przez obraz nie zostanie zastosowane po Twoich zmianach.
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.cfgGRUB_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ć UUID systemu plików 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 stanowi wiążące źródło informacji o działaniu obrazu.
Istotną konsekwencją jest fakt, że na tej ścieżce generator tworzy bezpośredni wpis rozruchowy zamiast pełnej listy zainstalowanych jąder. Policz, ile wpisów ostatecznie uzyskałeś.
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfgJeśli liczba wpisów wynosi 1, nie ma drugiego elementu 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 domyślny wybór. Menu, z którego próbujesz skorzystać, 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) 'Wzrost liczby wpisów z 1 do kilku oznacza, że po usunięciu wymuszenia wpisy stają się widoczne. Nic nie zostało jeszcze zapisane. Przywróć plik, jeśli druga liczba nie wydaje się poprawna, 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ę w tym miejscu 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 na przypinanie
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 jednak teraz na inne jądro. Nie pojawiają się żadne błędy ani ostrzeżenia, a o zmianie dowiadujesz się 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.cfgZignoruj pierwsze kilka linii wyjścia, które definiują zmienną w nagłówku. Poniżej, po lewej stronie znajduje się tytuł widoczny dla użytkownika, a po prawej identyfikator, który przekazuje się 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 formacie numerycznym.
Jednorazowe uruchomienie 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 próbowane przy następnym rozruchu. 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.cfgWymagana 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 listgrub-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ć serwer i sprawdzić wynik.
sudo rebootuname -runame -r zgłaszające starszą wersję oznacza, że przypięcie zadziałało. Zgłoszenie nowej wersji oznacza, że identyfikator nie został rozpoznany lub grubenv nie jest odczytywane; w obu przypadkach maszyna działa, 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 jąder systemu, 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.cfgOstatnie polecenie musi zwrócić set default="${saved_entry}". Jeśli zwróci set default="0", oznacza to, że jakiś skrypt wywołany po Twoim pliku przywrócił GRUB_DEFAULT do wartości dosłownej. 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 uruchomiony system staje się nowym wyborem domyślnym, więc domyślny wpis podąża za ostatnim udanym rozruchem. Na serwerze oznacza to, że bezobsługowy restart może niepostrzeżenie zmienić przypięty wpis. 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 odwołuje 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 lub wykluczyć dane jądro z operacji autoremove.
Wyświetlanie menu na 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=10GRUB_TIMEOUT_STYLE=hidden w połączeniu z GRUB_TIMEOUT=0 powoduje brak jakichkolwiek komunikatów, przez co osoba obserwująca konsolę widzi jedynie start komunikatów jądra i zakłada, że bootloader został pominięty. GRUB_RECORDFAIL_TIMEOUT to oddzielny limit czasu stosowany po nieudanym rozruchu; obrazy chmurowe ustawiają go na 0, dlatego serwer, który nie uruchomił się poprawnie, nie zatrzymuje się i nie oczekuje na interakcję.
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 można zobaczyć. 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 procesu rozruchu dodawane jest 10 sekund oczekiwania. 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ą tańsze rozwiązania, które zazwyczaj rozwiązują rzeczywisty problem.
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 showholdNależy użyć nazw wyświetlonych przez pierwsze polecenie, ponieważ obrazy chmurowe zazwyczaj instalują wariant virtual lub kvm zamiast generic. Jeśli nowsze jądro pojawiło się w okolicach odświeżenia obrazu i istnieje podejrzenie, że zmieniła się wersja dystrybucji, jest to błędne założenie, ponieważ wydanie punktowe to aktualizacje już posiadane, scalone w nowy nośnik instalacyjny i nie oferuje ono serwerowi z zainstalowanymi poprawkami niczego, czego nie otrzymałby kilka tygodni wcześniej. Wstrzymany pakiet jest pomijany przez apt upgrade, co jest sygnalizowane za pomocą The following packages have been kept back:, a także przez automatyczne aktualizacje w systemie Ubuntu. Koszt tego rozwiązania jest realny: wstrzymane jądro przestaje otrzymywać poprawki bezpieczeństwa, dlatego należy traktować to jako tymczasowe wstrzymanie z wyznaczoną datą zakończenia 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.
Snapshot przed aktualizacją. Snapshot pozwala na przywrócenie stanu systemu w kilka minut, bez konieczności pracy w konsoli i ryzyka niepełnej modyfikacji bootloadera. Należy wykonać snapshot, przeprowadzić aktualizację, zrestartować system i zweryfikować działanie. Jeśli nowe jądro działa nieprawidłowo, przywrócenie stanu przywraca dokładnie taką ścieżkę rozruchu, jaka była wcześniej.
Użycie konsoli lub obrazu ratunkowego dla niedziałającej maszyny. Gdy serwer nie uruchamia się, konfiguracja bootloadera nie jest miejscem, w którym należy szukać naprawy, a ścieżka odzyskiwania stanowi odrębną procedurę: co zrobić, 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 jest brakujący lub ucięty. Odtwórz go za pomocą sudo grub-editenv /boot/grub/grubenv create, następnie ponownie ustaw swoją wartość i potwierdź 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). Przypięty wpis wskazuje na jądro lub initrd, którego nie ma już na dysku, zazwyczaj dlatego, że pakiet został usunięty, podczas gdy identyfikator pozostał w grubenv. Odzyskiwanie polega na uruchomieniu systemu z działającego wpisu z poziomu konsoli, a następnie wyczyszczeniu nieaktualnej wartości.
uname -r pozostaje niezmienione po restarcie, po którym oczekiwałeś zmiany. Sprawdź trzy rzeczy w podanej kolejności: czy grub-editenv list nadal pokazuje twoją wartość, czy została ona skonsumowana; czy ustawiony przez ciebie identyfikator pojawia się w bieżącym grub.cfg; czy grub.cfg zawiera linię set default, która odczytuje ustawioną 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 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 nieporozumień. 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 kernela, 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 kerneli. Znajdź ten plik za pomocą grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.
Jak uruchomić poprzedni kernel tylko raz?
Uruchom sudo grub-reboot '<identifier>' z identyfikatorem skopiowanym z własnego grub.cfg, a następnie zrestartuj system, mając otwartą konsolę dostawcy. GRUB czyści next_entry przed rozruchem, więc wybór dotyczy dokładnie jednej próby, a kernel, który wywoła kernel panic, nie będzie ponownie użyty. 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 polecenie bez zgłaszania błędu.
Czy lepiej przypinać kernel numerem wpisu czy identyfikatorem?
Identyfikatorem. Numery wpisów to pozycje na liście, którą 10_linux przebudowuje, umieszczając najnowsze wersje na początku, więc instalacja lub usunięcie dowolnego kernela zmienia ich kolejność, a nieaktualny 1>2 nadal wskazuje na istniejący, lecz błędny wpis bez żadnego ostrzeżenia. Identyfikatory zawierają wersję kernela, więc albo pasują do wybranego kernela, 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 kernela jest bezpieczniejsze niż zmiana bootloadera?
Dla typowego celu – tak. sudo apt-mark hold linux-image-virtual linux-headers-virtual całkowicie blokuje instalację nowszego kernela, więc ścieżka rozruchu nigdy się nie zmienia i nie ma ryzyka błędu, którego nie da się naprawić z poziomu konsoli, do której możesz nie mieć dostępu. Najpierw sprawdź nazwy wariantów zainstalowane w swoim systemie za pomocą apt list --installed i zweryfikuj blokadę za pomocą apt-mark showhold. Ceną jest to, że zablokowany kernel nie otrzymuje poprawek bezpieczeństwa, więc zaplanuj, kiedy wykonasz sudo apt-mark unhold, zanim nałożysz blokadę.