如何正確鎖定 VPS 的開機核心版本?
Ubuntu 雲端映像檔中 GRUB_DEFAULT 設定無效,是因為 /etc/default/grub.d 內的檔案會覆蓋您的變更。本文說明如何讀取正確選單項目並安全鎖定核心,避免伺服器無法開機進入救援模式。
決定 VPS 開機核心的機制
VPS 下次開機時所使用的核心,是由一個產生的檔案 /boot/grub/grub.cfg 所決定。請勿直接編輯該檔案,應編輯其輸入來源並重新產生。在 Ubuntu 雲端映像檔中,其中一個輸入來源由映像檔供應商提供,這可能導致選單設定失效;這就是為什麼在租用的伺服器上執行 GRUB_DEFAULT=1 後接著執行 update-grub 卻毫無作用,但在筆記型電腦安裝環境中卻能生效的原因。
請依照下列順序操作。首先確認核心是否可由您自行選擇。接著讀取所有輸入檔案,包含供應商新增的檔案。讀取產生的輸出檔案,並計算其中實際包含的項目數量。最後才選擇鎖定核心的方法。若在僅能透過 SSH 連線的機器上操作錯誤,將需要使用救援主控台,因此本頁末尾提供的最安全方案,通常也是最正確的選擇。
首先確認核心是否可供鎖定
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virt 顯示 kvm、qemu 或 xen 代表您執行的是自有的核心,下方所有說明皆適用。顯示 lxc 或 openvz 代表您的伺服器共用宿主機的核心,因此您沒有自己的 bootloader,也無須鎖定任何項目。在此情況下,uname -r 顯示的版本完全不會出現在 /boot/vmlinuz-* 中,因為執行中的核心屬於宿主機,磁碟上的任何設定皆無法變更。
ls -1 /boot/vmlinuz-* 是您可以選擇的核心真實清單。若清單中僅剩一行,代表先前的核心已被刪除,任何 bootloader 設定都無法將其復原。這種情況通常發生在執行 autoremove 之後,在您關心的伺服器上執行 清理 Ubuntu 舊核心 前,務必先理解此機制。
您編輯的檔案並非 GRUB 實際讀取的檔案
/etc/default/grub 包含純文字的 shell 變數賦值,作為輸入來源。/boot/grub/grub.cfg 則是輸出檔案,且開頭會標註 # DO NOT EDIT THIS FILE 以及原因。任何寫入輸出檔案的內容,在安裝或移除核心套件時都會消失,因為套件指令碼會重新產生該檔案。
cat /usr/sbin/update-grubupdate-grub 是一個封裝程式。它會執行 grub-mkconfig -o /boot/grub/grub.cfg,後者負責讀取變數、執行 /etc/grub.d/ 中的所有指令碼,並寫入最終結果。這兩個指令遵循單一方向:輸入進入,grub.cfg 產出。
覆蓋您設定的來源:/etc/default/grub.d
grep -rn '^[^#]' /etc/default/grub /etc/default/grub.d/第二個路徑是人們最容易忽略的部分。grub-mkconfig 會先讀取 /etc/default/grub,接著依照 glob 順序讀取 /etc/default/grub.d/ 目錄下的每一個 *.cfg 檔案。請參考執行此動作的程式碼:
grep -n 'default/grub' /usr/sbin/grub-mkconfig讀取(sourcing)過程採用標準 shell 語法,因此最後的賦值會生效。Ubuntu 雲端映像檔會在該目錄中提供檔案,並在您的檔案讀取完畢後,設定諸如逾時時間與核心命令列參數等項目。您在 /etc/default/grub 中設定的 GRUB_TIMEOUT=10,隨後會被廠商提供的檔案覆蓋為 0。上述的 grep 指令會列出您映像檔中實際的賦值內容,請直接查看該輸出,而非僅憑此處說明。
由此得出的實務規則為:將您的設定放入排序在最後的檔案中(例如 /etc/default/grub.d/99-local.cfg),而非直接編輯 /etc/default/grub。如此一來,映像檔內建的任何設定都無法在您之後執行覆蓋。
為何 GRUB_FORCE_PARTUUID 會導致選單選項失效
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 指示產生器透過分割區 UUID 尋找根檔案系統,並直接寫入核心命令列作為 root=PARTUUID=...,而非在開機時搜尋檔案系統 UUID。映像檔供應商設定此參數是為了讓單一磁碟映像檔能在非建置環境的硬體上可靠開機。第二個 grep 指令顯示了處理該變數的程式碼,位於 /etc/grub.d/10_linux。該腳本位於您的磁碟上,是決定映像檔行為的權威來源。
其結果至關重要:在此路徑下,產生器會寫入直接開機項目,而非產生完整的已安裝核心列表。請計算您最終得到的項目數量。
sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg
sudo grep -nE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg若數量為 1,則沒有第二個項目可供選擇,因此 GRUB_DEFAULT=1 指定了一個不存在的項目。GRUB 無法解析該項目,因此會啟動第一個項目,也就是您試圖避開的新核心。grub-set-default 也無濟於事,因為問題不在於預設值,而是您試圖選擇的選單根本未被產生。
若要恢復完整選單,請將供應商檔案移至他處,並在執行變更前預覽結果。執行不帶 -o 的 grub-mkconfig 會將結果寫入標準輸出,且不會更動磁碟上的任何內容。
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) '若項目數量從 1 跳升至多個,代表移除強制設定後項目即會出現。此時尚未寫入任何變更。若計算出的數量不正確,請將檔案移回原處,因為強制設定的 PARTUUID 是供應商映像檔定位根檔案系統的方式,移除它會使機器改用搜尋路徑。在正式執行 update-grub 前,請務必建立快照。
若您的唯一目標是應對單一損壞的核心,請在此停止並使用下方更安全的選項。在遠端伺服器上重建開機選單以規避單次升級,其風險遠大於問題本身。
為何不應使用項目編號進行鎖定
GRUB_DEFAULT 接受編號、標題或識別碼。編號從 0 開始計算頂層項目。巢狀項目使用 > 作為分隔符,因此 GRUB_DEFAULT="1>2" 代表索引 1 之子選單內索引 2 的項目。
索引會變動。10_linux 會依最新順序排列核心,因此安裝新核心會將所有舊項目向下推移一個位置,移除核心則會將其向上拉升。您原本設定的 1>2 在變動後依然有效,但它現在指向的是另一個不同的核心。系統不會報錯,也不會發出警告,您通常在重新開機後才會發現問題。
識別碼不會變動,因為每個識別碼都包含核心版本。請查看您的識別碼:
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfg請忽略輸出的前幾行(那是標頭中定義的變數)。之後,左側是使用者看到的標題,右側則是您傳遞給工具的識別碼。若要指定子選單內的項目,請依照數值形式的順序,使用 > 將子選單識別碼與項目識別碼連接起來。
使用 grub-reboot 僅開機進入舊核心一次
在遠端伺服器上,單次開機選擇是正確的做法,因為它會自動復原。grub-reboot 會將 next_entry 寫入 /boot/grub/grubenv。GRUB 會讀取該變數,將其清除,並在開機前儲存清除後的值,因此若核心發生 kernel panic,下次開機時不會再次嘗試該核心。您有一次嘗試機會,之後機器會自動恢復為預設值。
首先確認您產生的設定檔確實會讀取該變數:
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfg您應該會看到一行 load_env,以及一個從 next_entry 設定 default 的區塊。如果 grep 沒有輸出任何內容,表示您的映像檔在開機時從未讀取 grubenv,因此在 shell 中執行 grub-reboot 雖然會被接受,但開機載入程式會將其忽略。這與上一節中提到的強制直接開機路徑是相同的問題。
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listgrub-editenv list 現在應該會輸出包含您所傳入內容的 next_entry= 行。在瀏覽器分頁中開啟雲端供應商的控制台,接著重新開機並檢查結果。
sudo rebootuname -runame -r 若顯示舊版本,表示鎖定成功。若顯示新版本,表示識別碼未解析成功,或是 grubenv 未被讀取;無論如何,機器都能正常開機,這正是使用單次開機指令的目的。
透過 GRUB_DEFAULT=saved 讓設定永久生效
GRUB_DEFAULT=saved 會讓預設值從 saved_entry 讀取,該檔案位於 grubenv,您可以使用 grub-set-default 來設定此數值。此設定在核心安裝後依然有效,因為 update-grub 會重寫 grub.cfg,且絕不會更動 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最後的指令必須輸出 set default="${saved_entry}"。若輸出 set default="0",代表有其他檔案在您的設定檔之後,將 GRUB_DEFAULT 改回了字面數值;請再次列出 /etc/default/grub.d/ 並確認 99-local.cfg 確實排在最後。
GRUB_SAVEDEFAULT=true 是另一項設定,容易與此項混淆。它會將您剛啟動的項目設為新的預設值,因此預設值會跟隨最後一次成功的開機項目。在伺服器上,這意味著無人值守的重啟可能會悄悄變更您的鎖定項目。除非您有此需求,否則請保持關閉。
透過識別碼鎖定項目仍有一種失敗情況。若移除該識別碼所指向的核心,識別碼將無法解析,系統會退回至第一個項目。因此,請同時鎖定該套件,或將該核心排除在 autoremove 之外。
將選單顯示於供應商控制台
若需進行互動式選擇,必須將選單顯示於螢幕上,但雲端映像檔預設會隱藏它。請將下列設定加入排序最後的檔案中,接著執行 sudo update-grub。
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10GRUB_TIMEOUT_STYLE=hidden 搭配 GRUB_TIMEOUT=0 會導致完全沒有畫面,監控控制台的使用者會看到核心訊息直接啟動,進而誤以為開機載入程式被跳過。GRUB_RECORDFAIL_TIMEOUT 是開機未完成時所使用的獨立逾時設定,雲端映像檔亦將其設為 0,這就是為何伺服器開機失敗時不會停止並等待您的原因。
若您的供應商提供的是序列控制台而非圖形控制台,且您仍無法看到任何內容,代表 GRUB 正將輸出寫入您無法看見的終端機。請同時加入以下兩行,第一行用於選擇輸出裝置,第二行則用於設定連接埠:
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"此後每次開機都會增加 10 秒的等待時間。完成後,請將逾時時間設回 0。
比編輯開機載入程式更安全的選項
在僅能透過 SSH 存取的機器上修改開機載入程式(bootloader)設定,是本頁面中風險最高的選項。通常有成本更低且能解決根本問題的替代方案。
凍結核心套件(Hold the kernel packages)。 如果目標是「不要提供我更新的核心」,請直接告知套件管理程式,而非修改開機載入程式。
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請使用第一個指令所顯示的名稱,因為雲端映像檔通常會安裝 virtual 或 kvm 版本,而非 generic。若新核心是在映像檔更新前後出現,且您懷疑發行版本身發生變更,事實並非如此,因為 點發行版(point release)僅是將您已擁有的更新整合進新的安裝媒體,對於已修補過的伺服器而言,並不會提供幾週前未曾出現過的內容。被凍結的套件會被 apt upgrade 跳過,並以 The following packages have been kept back: 進行提示,同時也會被 Ubuntu 的自動更新(unattended upgrades) 跳過。此舉確實有代價:凍結的核心將無法接收安全性修補,因此請將其視為有期限的暫停,並適時以 sudo apt-mark unhold 解除凍結。若您避免更新核心是因為重開機導致停機,而非核心本身有問題,VPS 的即時核心修補(live kernel patching) 才是解決方案。
升級前建立快照(Snapshot)。 快照可在幾分鐘內還原,無需透過主控台輸入指令,也不會發生開機載入程式變更僅執行一半的風險。流程為:建立快照、升級、重開機、驗證。若新核心運作異常,直接還原即可,開機路徑將完全恢復原狀。
若機器已無法開機,請使用主控台或救援映像檔。 一旦伺服器無法開機,修改開機載入程式設定已無法解決問題,該復原路徑有其專屬程序:核心更新後 VPS 無法開機的處理方式。
故障排除與錯誤訊息說明
您對 /boot/grub/grub.cfg 的編輯內容消失了。 這是因為安裝或移除核心套件時,維護腳本執行了 update-grub,導致檔案根據輸入來源重新產生。# DO NOT EDIT THIS FILE 標頭中列出了兩個輸入位置,請改為編輯這些檔案。
grub-editenv: error: environment block too small。 /boot/grub/grubenv 遺失或內容不完整。請使用 sudo grub-editenv /boot/grub/grubenv create 重新建立,接著重新設定您的數值,並以 sudo grub-editenv list 確認。
鎖定(pinned)的核心發生 VFS: Unable to mount root fs on unknown-block(0,0) 核心崩潰。 您鎖定的項目指向一個磁碟上已不存在的核心或 initrd,通常是因為套件已被移除,但識別碼仍殘留在 grubenv 中。復原方式是透過主控台開機進入正常的項目,然後清除該過時的數值。
重開機後 uname -r 未如預期變更。 請依序檢查三件事:grub-editenv list 是否仍顯示您的數值或已被消耗;您設定的識別碼是否出現在目前的 grub.cfg 中;grub.cfg 是否包含讀取您所設定變數的 set default 行。這三點中必有一項能解釋原因。
當機後選單自動出現。 GRUB 會將開機失敗記錄在 grubenv 的 recordfail=1 中,這會強制在下次開機時顯示選單,以便人工介入。待系統恢復正常後,請使用 sudo grub-editenv /boot/grub/grubenv unset recordfail 清除該記錄。
唯一值得記住的原則:您編輯的檔案並非 GRUB 實際讀取的檔案;在雲端映像檔中,兩者之間的落差正是混亂的根源。請務必先閱讀產生的設定檔。本頁面中的所有決策,皆依據該檔案的實際內容而定。
FAQ
為什麼設定 GRUB_DEFAULT=1 後,我的 VPS 仍未切換至預期的核心?
因為 Ubuntu 雲端映像檔產生的 /boot/grub/grub.cfg 通常只有一個開機項目,索引 1 並無對應目標,導致 GRUB 自動退回第一個項目。請使用 sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg 確認,若計數為 1 即為原因。這是因為 GRUB_FORCE_PARTUUID 被映像檔供應商設定在 /etc/default/grub.d/ 下的檔案中,該設定會強制系統採用直接開機路徑,而非建立完整的已安裝核心清單。請使用 grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/ 找出該檔案。
如何僅執行一次舊版核心開機?
請使用從您自己的 grub.cfg 複製的識別碼執行 sudo grub-reboot '<identifier>',接著在保持開啟供應商控制台的狀態下重新開機。GRUB 會在開機前清除 next_entry,因此該選擇僅對單次開機有效,若核心發生 panic 也不會重試。請使用 sudo grub-editenv list 確認數值已寫入。在依賴此功能前,請先執行 sudo grep -n next_entry /boot/grub/grub.cfg,因為若設定檔未載入 grubenv,系統將會忽略該指令且不會顯示錯誤。
應該使用項目編號還是識別碼來鎖定核心?
請使用識別碼。項目編號是清單中的位置,而 10_linux 會依最新版本優先重建清單,因此安裝或移除任何核心都會導致編號變動。過時的 1>2 仍會指向一個真實但錯誤的項目,且系統不會發出任何警告。識別碼包含核心版本,因此它們要麼符合您預期的核心,要麼會解析失敗。請使用 sudo grep -n menuentry_id_option /boot/grub/grub.cfg 列出識別碼,並複製每個項目行後方的引號字串。
鎖定核心套件是否比修改開機載入程式更安全?
針對一般需求而言,是的。sudo apt-mark hold linux-image-virtual linux-headers-virtual 可防止新版核心被安裝,因此開機路徑不會改變,這能避免在無法存取控制台的情況下發生錯誤。請先使用 apt list --installed 檢查您系統上安裝的核心版本名稱,並使用 apt-mark showhold 確認鎖定狀態。代價是鎖定的核心將無法接收安全性更新,因此在執行鎖定前,請先規劃好何時執行 sudo apt-mark unhold。