SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-25

Cách ghim kernel khởi động trên VPS Ubuntu an toàn

GRUB_DEFAULT không hoạt động trên Ubuntu cloud image do cấu hình từ nhà cung cấp. Hướng dẫn cách liệt kê menu entries chính xác và ghim kernel mà không cần dùng rescue console.

Yếu tố quyết định kernel nào sẽ khởi động trên VPS của bạn

Kernel nào sẽ khởi động trên VPS của bạn được quyết định bởi một file được tạo tự động là /boot/grub/grub.cfg. Bạn không bao giờ được chỉnh sửa file này trực tiếp. Bạn phải chỉnh sửa các file đầu vào của nó rồi tạo lại file này. Trên các image cloud của Ubuntu, một trong những file đầu vào đó do nhà cung cấp image tạo ra, và nó có thể khiến việc chọn menu trở nên vô nghĩa. Đó là lý do tại sao thao tác GRUB_DEFAULT=1 theo sau bởi update-grub không thay đổi được gì trên server thuê, trong khi hai bước đó lại hoạt động trên bản cài đặt laptop.

Hãy thực hiện theo thứ tự này. Xác nhận rằng bạn có quyền chọn kernel. Đọc mọi file đầu vào, bao gồm cả những file do nhà cung cấp thêm vào. Đọc file đầu ra đã được tạo và đếm số lượng mục thực tế mà nó chứa. Chỉ sau đó mới chọn phương pháp ghim (pinning) kernel. Làm sai bước này trên một máy chủ chỉ có thể truy cập qua SSH sẽ khiến bạn phải dùng đến rescue console, vì vậy các phương án an toàn nhất nằm ở cuối trang này và chúng thường là những lựa chọn đúng đắn.

Kiểm tra xem kernel có thuộc quyền quản lý của bạn để ghim hay không

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

systemd-detect-virt in ra kvm, qemu hoặc xen nghĩa là bạn đang chạy kernel riêng và mọi hướng dẫn bên dưới đều áp dụng được. In ra lxc hoặc openvz nghĩa là máy chủ của bạn dùng chung kernel với host, do đó không có bootloader riêng và không có gì để ghim. Trong trường hợp đó, uname -r báo cáo một phiên bản không hề xuất hiện trong /boot/vmlinuz-*, vì kernel đang chạy thuộc về host và không thiết lập nào trên ổ đĩa của bạn có thể thay đổi nó.

ls -1 /boot/vmlinuz-* là danh sách thực tế các kernel mà bạn có thể chọn. Nếu nó chỉ chứa một dòng, kernel trước đó đã bị xóa và không thiết lập bootloader nào có thể khôi phục nó. Điều này thường xảy ra trong quá trình autoremove, một thao tác cần hiểu rõ trước khi bạn dọn dẹp các kernel cũ trên Ubuntu trên một máy chủ quan trọng.

File bạn chỉnh sửa không phải là file GRUB đọc

/etc/default/grub chứa các gán biến shell thuần túy. Đây là một file đầu vào. /boot/grub/grub.cfg là file đầu ra, và nó bắt đầu bằng # DO NOT EDIT THIS FILE cùng với lý do. Bất kỳ nội dung nào bạn viết vào file đầu ra đều sẽ mất đi vào lần tiếp theo một gói kernel được cài đặt hoặc gỡ bỏ, vì các script của gói đó sẽ tạo lại file này.

cat /usr/sbin/update-grub

update-grub là một wrapper. Nó chạy grub-mkconfig -o /boot/grub/grub.cfg, lệnh này đọc các biến, chạy mọi script trong /etc/grub.d/, và ghi kết quả ra. Hai lệnh, một hướng: dữ liệu đầu vào đi vào, grub.cfg được tạo ra.

Cái gì ghi đè thiết lập của bạn: /etc/default/grub.d

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

Đường dẫn thứ hai là phần mọi người thường bỏ sót. grub-mkconfig nạp /etc/default/grub trước, sau đó đến mọi file *.cfg trong /etc/default/grub.d/ theo thứ tự glob. Hãy đọc đoạn mã thực hiện việc này:

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

Việc nạp file (sourcing) thực hiện bằng shell thuần, nên lệnh gán cuối cùng sẽ có hiệu lực. Các cloud image của Ubuntu cung cấp sẵn các file trong thư mục đó, và chúng thiết lập các thông số như timeout và kernel command line sau khi file của bạn đã được đọc. GRUB_TIMEOUT=10 của bạn trong /etc/default/grub bị ghi đè ngay sau đó bởi một file của nhà cung cấp, file này thiết lập giá trị về 0. Lệnh grep ở trên in ra các lệnh gán chính xác trên image của bạn, vì vậy hãy đọc các giá trị đó thay vì tin vào câu này.

Quy tắc thực tế rút ra là: hãy đặt các thiết lập của riêng bạn vào một file có thứ tự sắp xếp cuối cùng, ví dụ như /etc/default/grub.d/99-local.cfg, thay vì chỉnh sửa /etc/default/grub. Khi đó, không có gì do image cung cấp có thể ghi đè lên cấu hình của bạn.

Tại sao GRUB_FORCE_PARTUUID làm cho việc chọn menu trở nên vô nghĩa

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 ra lệnh cho trình tạo menu tìm filesystem gốc bằng partition UUID, được ghi trực tiếp vào dòng lệnh kernel dưới dạng root=PARTUUID=..., thay vì tìm kiếm filesystem UUID trong quá trình boot. Nhà cung cấp image thiết lập giá trị này để một disk image có thể boot ổn định trên phần cứng mà nó không được tạo ra. Lệnh grep thứ hai hiển thị cho bạn đoạn mã xử lý biến này trong /etc/grub.d/10_linux. Script đó nằm trên ổ đĩa của bạn và nó là cơ sở xác định hành vi của image.

Hệ quả là điều quan trọng ở đây: trên đường dẫn đó, trình tạo menu ghi một entry boot trực tiếp thay vì danh sách đầy đủ các kernel đã cài đặt. Hãy đếm số lượng entry bạn nhận được.

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

Nếu số lượng là 1, sẽ không có entry thứ hai để chọn, vì vậy GRUB_DEFAULT=1 chỉ định một entry không tồn tại. GRUB không thể phân giải nó, nên nó boot entry đầu tiên, chính là kernel mới mà bạn đang cố tránh. grub-set-default cũng không giúp ích gì, vì giá trị mặc định không phải là phần bị lỗi. Menu mà bạn đang cố chọn chưa bao giờ được tạo ra.

Để lấy lại menu đầy đủ, hãy di chuyển file của nhà cung cấp sang chỗ khác và xem trước kết quả trước khi áp dụng. grub-mkconfig không có -o sẽ ghi ra standard output và không thay đổi bất cứ thứ gì trên đĩa.

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

Số lượng tăng từ 1 lên nhiều nghĩa là các entry xuất hiện sau khi bỏ chế độ ép buộc. Chưa có gì được ghi lại cả. Hãy trả lại file cũ nếu số lượng entry thứ hai không đúng, vì PARTUUID bị ép buộc là cách image của nhà cung cấp xác định vị trí filesystem gốc, và việc xóa nó sẽ chuyển máy sang đường dẫn tìm kiếm thay thế. Hãy tạo snapshot trước khi bạn chạy update-grub thực sự.

Nếu mục tiêu duy nhất của bạn là vượt qua một kernel lỗi, hãy dừng lại ở đây và sử dụng các tùy chọn an toàn hơn ở bên dưới. Việc xây dựng lại menu boot trên một máy chủ từ xa chỉ để tránh một bản nâng cấp đơn lẻ mang lại nhiều rủi ro hơn là lợi ích.

Tại sao dùng số thứ tự mục để ghim là sai lầm

GRUB_DEFAULT chấp nhận một con số, một tiêu đề hoặc một định danh. Các con số đếm các mục cấp cao nhất bắt đầu từ 0. Một mục lồng nhau sử dụng > làm dấu phân cách, vì vậy GRUB_DEFAULT="1>2" có nghĩa là mục ở chỉ số 2 bên trong menu con ở chỉ số 1.

Các chỉ số sẽ thay đổi. 10_linux liệt kê các kernel theo thứ tự mới nhất trước, vì vậy việc cài đặt một kernel mới sẽ đẩy mọi mục cũ hơn xuống một vị trí, và việc gỡ bỏ một kernel sẽ kéo chúng lên. Cấu hình 1>2 mà bạn cẩn thận thiết lập vẫn sẽ phân giải sau đó. Nó lúc này lại trỏ đến một kernel khác. Không có lỗi, không có cảnh báo, và bạn chỉ biết về điều đó sau khi reboot.

Các định danh không thay đổi, vì mỗi định danh đều chứa phiên bản kernel. Hãy đọc định danh của bạn:

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

Bỏ qua vài dòng đầu của kết quả, đó là các biến được định nghĩa trong phần header. Sau đó, phía bên trái là tiêu đề mà người dùng nhìn thấy và phía bên phải là định danh bạn truyền cho các công cụ. Đối với một mục bên trong menu con, hãy nối định danh của menu con và định danh của mục đó bằng >, theo đúng thứ tự đó, giống hệt như cách dùng định dạng số.

Boot kernel cũ một lần bằng grub-reboot

Lựa chọn boot một lần là phương án an toàn trên server từ xa vì nó sẽ tự hoàn tác. grub-reboot ghi next_entry vào /boot/grub/grubenv. GRUB đọc biến này, xóa nó đi và lưu giá trị đã xóa trước khi boot bất cứ thứ gì, vì vậy nếu kernel bị panic, nó sẽ không bị boot lại ở lần sau. Bạn có một lần thử, sau đó máy sẽ tự quay về cấu hình mặc định.

Trước tiên, hãy xác nhận file cấu hình đã tạo của bạn có đọc biến này hay không:

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

Bạn cần tìm một dòng load_env và một block thiết lập default từ next_entry. Nếu lệnh grep không trả về kết quả, image của bạn không đọc grubenv khi boot, nên grub-reboot sẽ được chấp nhận ở shell nhưng bị bootloader bỏ qua. Đây chính là đường dẫn boot trực tiếp bị ép buộc từ phần trước xuất hiện ở một nơi khác.

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

grub-editenv list bây giờ sẽ in ra một dòng next_entry= chứa chính xác những gì bạn đã truyền vào. Mở console của nhà cung cấp trong tab trình duyệt, sau đó reboot và kiểm tra kết quả.

sudo reboot
uname -r

uname -r báo phiên bản cũ nghĩa là lệnh pin đã hoạt động. Nếu báo phiên bản mới nghĩa là định danh không phân giải được hoặc grubenv không được đọc, và dù sao thì máy cũng đã lên nguồn, đó chính là mục đích của việc sử dụng lệnh boot một lần.

Thiết lập lựa chọn cố định với GRUB_DEFAULT=saved

GRUB_DEFAULT=saved giúp giá trị mặc định được lấy từ saved_entry trong grubenv, và bạn thiết lập giá trị đó bằng grub-set-default. Cấu hình này vẫn tồn tại sau khi cài đặt kernel mới, vì update-grub ghi đè lên grub.cfg và không bao giờ can thiệp vào 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

Lệnh cuối cùng phải in ra set default="${saved_entry}". Nếu nó in ra set default="0", có một tệp tin được nạp sau tệp của bạn đã ghi đè GRUB_DEFAULT về một giá trị cố định, vì vậy hãy liệt kê /etc/default/grub.d/ một lần nữa và kiểm tra xem 99-local.cfg có thực sự nằm ở cuối danh sách hay không.

GRUB_SAVEDEFAULT=true là một thiết lập khác và rất dễ nhầm lẫn với thiết lập này. Nó lưu lại bất cứ thứ gì bạn vừa boot làm mặc định mới, vì vậy giá trị mặc định sẽ thay đổi theo lần boot thành công gần nhất. Trên máy chủ, điều này có nghĩa là một lần reboot tự động có thể âm thầm thay đổi lựa chọn của bạn. Hãy tắt nó trừ khi đó là điều bạn muốn.

Việc ghim kernel bằng định danh vẫn có một điểm yếu. Nếu bạn xóa kernel được chỉ định, định danh đó sẽ không còn phân giải được nữa, khiến hệ thống quay trở lại mục đầu tiên trong danh sách. Vì vậy, hãy giữ (hold) gói kernel đó hoặc loại trừ nó khỏi lệnh autoremove.

Đưa menu lên console của nhà cung cấp

Việc chọn menu tương tác yêu cầu menu phải hiển thị trên màn hình, nhưng các cloud image lại ẩn nó đi. Hãy thêm các dòng này vào file được sắp xếp cuối cùng, sau đó chạy sudo update-grub.

GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10

GRUB_TIMEOUT_STYLE=hidden cùng với GRUB_TIMEOUT=0 khiến màn hình không hiển thị gì cả, vì vậy người theo dõi console sẽ thấy các thông báo kernel bắt đầu ngay lập tức và kết luận rằng bootloader đã bị bỏ qua. GRUB_RECORDFAIL_TIMEOUT là thời gian chờ riêng biệt được sử dụng sau một lần boot không hoàn tất, và các cloud image cũng đặt nó bằng 0, đó là lý do tại sao một server vừa boot lỗi vẫn không dừng lại và chờ bạn.

Nếu nhà cung cấp của bạn cung cấp serial console thay vì console đồ họa mà bạn vẫn không thấy gì, thì GRUB đang ghi vào một terminal mà bạn không thể nhìn thấy. Hãy thêm cả hai dòng cùng lúc, vì dòng đầu tiên chọn các đầu ra và dòng thứ hai cấu hình cổng:

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

Mười giây sẽ được thêm vào mỗi lần boot kể từ bây giờ. Hãy đặt lại timeout về 0 sau khi bạn hoàn tất.

Các tùy chọn an toàn hơn thay vì chỉnh sửa bootloader

Thay đổi tham số bootloader trên một máy chủ mà bạn chỉ có thể truy cập qua SSH là phương án rủi ro nhất trong trang này. Có những giải pháp ít tốn kém hơn và chúng thường giải quyết đúng vấn đề thực tế.

Giữ nguyên (hold) các gói kernel. Nếu mục tiêu là "không muốn nhận kernel mới hơn", hãy yêu cầu trình quản lý gói làm việc đó thay vì can thiệp và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 showhold

Hãy sử dụng tên gói mà lệnh đầu tiên đã liệt kê, vì các image trên cloud thường cài đặt phiên bản virtual hoặc kvm thay vì generic. Nếu kernel mới xuất hiện cùng lúc với việc làm mới image và bạn nghi ngờ bản release đã thay đổi, thì không phải vậy, vì một bản point release chỉ là các bản cập nhật bạn đã có được tích hợp vào phương tiện cài đặt mới và nó không cung cấp gì thêm cho một máy chủ đã được vá lỗi từ nhiều tuần trước. Một gói đã được "hold" sẽ bị apt upgrade bỏ qua, trình quản lý gói sẽ thông báo điều này bằng The following packages have been kept back:, và nó cũng bị unattended upgrades trên Ubuntu bỏ qua. Cái giá phải trả là có thật: một kernel bị giữ lại sẽ không nhận được các bản vá bảo mật, vì vậy hãy coi đây là một khoảng tạm dừng có thời hạn và giải phóng nó bằng sudo apt-mark unhold. Nếu bạn tránh cập nhật kernel vì việc reboot gây downtime chứ không phải vì kernel đó lỗi, thì live kernel patching trên VPS là giải pháp thay thế.

Snapshot trước khi nâng cấp. Một bản snapshot có thể khôi phục hệ thống trong vài phút, không cần gõ lệnh qua console và không có rủi ro thay đổi bootloader dở dang. Snapshot, nâng cấp, reboot, kiểm tra. Nếu kernel mới hoạt động không ổn định, hãy roll back và đường dẫn boot sẽ trở lại chính xác như cũ.

Sử dụng console hoặc rescue image cho máy chủ đã bị down. Một khi máy chủ không thể boot, cấu hình bootloader không phải là nơi để bạn sửa lỗi, và quy trình khôi phục đó là một thủ tục riêng biệt: phải làm gì khi VPS không boot được sau khi cập nhật kernel.

Những lỗi thường gặp và thông báo bạn sẽ thấy

Chỉnh sửa của bạn trong /boot/grub/grub.cfg đã biến mất. Một gói kernel đã được cài đặt hoặc gỡ bỏ, script của người bảo trì đã chạy update-grub, và file này được tạo lại từ các đầu vào. Phần header # DO NOT EDIT THIS FILE liệt kê hai vị trí đầu vào. Hãy chỉnh sửa các file đó.

grub-editenv: error: environment block too small. /boot/grub/grubenv bị thiếu hoặc bị cắt cụt. Hãy tạo lại nó bằng sudo grub-editenv /boot/grub/grubenv create, sau đó thiết lập lại giá trị của bạn và xác nhận bằng sudo grub-editenv list.

Kernel được ghim (pinned) gây ra kernel panic với VFS: Unable to mount root fs on unknown-block(0,0). Mục bạn đã ghim trỏ đến một kernel hoặc initrd không còn trên đĩa, thường là do gói đã bị gỡ bỏ trong khi định danh vẫn còn trong grubenv. Cách khôi phục là boot từ console vào một mục đang hoạt động, sau đó xóa giá trị cũ đi.

uname -r không thay đổi sau khi reboot dù bạn mong đợi nó thay đổi. Hãy kiểm tra ba thứ theo thứ tự: grub-editenv list có còn hiển thị giá trị của bạn hay đã bị tiêu thụ; định danh bạn đặt có xuất hiện trong grub.cfg hiện tại không; grub.cfg có chứa dòng set default đọc biến bạn đã đặt hay không. Một trong ba điều này luôn là nguyên nhân.

Menu tự động xuất hiện sau một lần crash. GRUB ghi lại lần boot thất bại trong grubenv dưới dạng recordfail=1, điều này buộc menu phải hiện ra ở lần boot tiếp theo để người dùng can thiệp. Hãy xóa nó bằng sudo grub-editenv /boot/grub/grubenv unset recordfail sau khi máy chủ đã hoạt động bình thường.


Câu duy nhất cần ghi nhớ: file bạn chỉnh sửa không phải là file mà GRUB đọc, và trên các cloud image, khoảng cách giữa chúng chính là nơi gây ra nhầm lẫn. Hãy đọc file cấu hình đã được tạo trước. Mọi quyết định trên trang này đều dựa trên những gì file đó thực sự ghi lại.

FAQ

Tại sao GRUB_DEFAULT=1 không thay đổi kernel mà VPS của tôi khởi động?

Vì trên các cloud image của Ubuntu, /boot/grub/grub.cfg được tạo ra thường chỉ chứa một mục khởi động duy nhất, nên chỉ số 1 không trỏ đến đâu cả và GRUB sẽ quay về mục đầu tiên. Hãy xác nhận điều này bằng sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg. Kết quả trả về là 1. Nguyên nhân là do GRUB_FORCE_PARTUUID, được nhà cung cấp image thiết lập trong một file nằm dưới /etc/default/grub.d/, khiến trình tạo cấu hình đi theo đường dẫn khởi động trực tiếp thay vì xây dựng danh sách đầy đủ các kernel đã cài đặt. Tìm file đó bằng grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/.

Làm thế nào để khởi động kernel cũ chỉ một lần duy nhất?

Chạy sudo grub-reboot '<identifier>' với định danh (identifier) được sao chép từ grub.cfg của chính bạn, sau đó khởi động lại máy trong khi đã mở sẵn console của nhà cung cấp. GRUB sẽ xóa next_entry trước khi khởi động, vì vậy lựa chọn này chỉ áp dụng cho đúng một lần và kernel bị panic sẽ không được thử lại. Xác nhận giá trị đã được ghi nhận bằng sudo grub-editenv list. Trước khi tin tưởng vào lệnh này, hãy chạy sudo grep -n next_entry /boot/grub/grub.cfg, vì một image có cấu hình không load grubenv sẽ bỏ qua lệnh mà không báo lỗi.

Nên ghim (pin) theo số thứ tự mục hay theo định danh?

Theo định danh. Số thứ tự mục là vị trí trong danh sách mà 10_linux sắp xếp lại theo thứ tự mới nhất lên đầu, vì vậy việc cài đặt hoặc gỡ bỏ bất kỳ kernel nào cũng sẽ làm thay đổi vị trí của chúng, và một 1>2 cũ vẫn có thể trỏ đến một mục thực tế nhưng không đúng ý bạn mà không có cảnh báo nào. Định danh chứa phiên bản kernel, nên chúng sẽ khớp với kernel bạn muốn hoặc không tìm thấy. Hãy liệt kê chúng bằng sudo grep -n menuentry_id_option /boot/grub/grub.cfg và sao chép chuỗi nằm trong dấu ngoặc kép ở mỗi dòng mục.

Việc giữ (hold) gói kernel có an toàn hơn thay đổi bootloader không?

Với mục đích thông thường thì có. sudo apt-mark hold linux-image-virtual linux-headers-virtual ngăn chặn việc cập nhật kernel mới, vì vậy đường dẫn khởi động không bao giờ thay đổi và không có rủi ro sai sót khi bạn không thể truy cập console. Trước tiên, hãy kiểm tra tên các flavour đã cài đặt trên máy của bạn bằng apt list --installed và xác minh trạng thái hold bằng apt-mark showhold. Đánh đổi ở đây là kernel bị hold sẽ không nhận được các bản vá bảo mật, vì vậy hãy quyết định thời điểm bạn sẽ chạy sudo apt-mark unhold trước khi thực hiện lệnh hold.