วิธีตั้งค่าเลือก Kernel สำหรับบูต VPS บน Ubuntu
แก้ไขปัญหา GRUB_DEFAULT ไม่ทำงานบน Ubuntu Cloud Image ด้วยการตรวจสอบรายการเมนูจริงก่อนตั้งค่า เพื่อป้องกันเซิร์ฟเวอร์บูตไม่ขึ้นและไม่ต้องใช้ Rescue Console อีกต่อไป
สิ่งที่กำหนดว่า VPS ของคุณจะบูตด้วยเคอร์เนลใด
สิ่งที่กำหนดว่า VPS ของคุณจะบูตด้วยเคอร์เนลใดในครั้งถัดไปคือไฟล์ที่ถูกสร้างขึ้นมาหนึ่งไฟล์ นั่นคือ /boot/grub/grub.cfg คุณไม่ควรแก้ไขไฟล์ดังกล่าวโดยตรง แต่ให้แก้ไขไฟล์ที่เป็นอินพุตของไฟล์นั้นแล้วสร้างขึ้นมาใหม่ สำหรับอิมเมจ Ubuntu บนคลาวด์ อินพุตไฟล์หนึ่งจะมาจากผู้ให้บริการอิมเมจ ซึ่งอาจทำให้การเลือกเมนูไม่มีผล นี่คือเหตุผลว่าทำไมการใช้ GRUB_DEFAULT=1 ตามด้วย update-grub จึงไม่ส่งผลใดๆ บนเซิร์ฟเวอร์ที่เช่ามา ในขณะที่ขั้นตอนเดียวกันนี้สามารถใช้งานได้บนการติดตั้งบนแล็ปท็อป
ให้ดำเนินการตามลำดับนี้ ยืนยันก่อนว่าคุณมีสิทธิ์เลือกเคอร์เนลเอง อ่านไฟล์อินพุตทุกไฟล์ รวมถึงไฟล์ที่ผู้ให้บริการเพิ่มเข้ามา อ่านไฟล์เอาต์พุตที่ถูกสร้างขึ้นและนับจำนวนรายการที่มีอยู่จริง จากนั้นจึงเลือกวิธีการล็อกเวอร์ชัน (pinning) การทำขั้นตอนนี้ผิดพลาดบนเครื่องที่คุณเข้าถึงได้ผ่าน SSH เท่านั้น จะทำให้คุณต้องใช้ rescue console ดังนั้นคำตอบที่ปลอดภัยที่สุดจะอยู่ที่ส่วนท้ายของหน้านี้ และมักจะเป็นวิธีที่ถูกต้องเสมอ
ตรวจสอบก่อนว่า kernel เป็นของคุณเพื่อทำการ pin
uname -r
systemd-detect-virt
ls -1 /boot/vmlinuz-*systemd-detect-virt การแสดงผล kvm, qemu หรือ xen หมายความว่าคุณรัน kernel ของคุณเอง และทุกอย่างที่ระบุด้านล่างนี้จะมีผลบังคับใช้ การแสดงผล lxc หรือ openvz หมายความว่าเซิร์ฟเวอร์ของคุณใช้ kernel ร่วมกับโฮสต์ ดังนั้นจึงไม่มี bootloader ของคุณเองและไม่มีสิ่งใดให้ pin ในกรณีนี้ uname -r จะรายงานเวอร์ชันที่ไม่ปรากฏอยู่ใน /boot/vmlinuz-* เลย เนื่องจาก kernel ที่กำลังทำงานอยู่เป็นของโฮสต์ และไม่มีการตั้งค่าใดบนดิสก์ของคุณที่จะเปลี่ยนแปลงมันได้
ls -1 /boot/vmlinuz-* คือรายการ kernel จริงที่คุณสามารถเลือกได้ หากรายการนี้เหลือเพียงบรรทัดเดียว แสดงว่า kernel ก่อนหน้านี้ถูกลบไปแล้ว และไม่มีการตั้งค่า bootloader ใดที่จะกู้คืนกลับมาได้ เหตุการณ์นี้มักเกิดขึ้นระหว่างการทำ autoremove ซึ่งเป็นเรื่องที่ควรทำความเข้าใจก่อนที่คุณจะ ล้าง kernel เก่าบน Ubuntu ในเครื่องที่คุณให้ความสำคัญ
ไฟล์ที่คุณแก้ไขไม่ใช่ไฟล์ที่ GRUB อ่าน
/etc/default/grub เก็บการกำหนดค่าตัวแปรเชลล์แบบปกติไว้ นี่คือไฟล์อินพุต ส่วน /boot/grub/grub.cfg คือไฟล์เอาต์พุต ซึ่งจะขึ้นต้นด้วย # DO NOT EDIT THIS FILE และเหตุผลประกอบ ทุกสิ่งที่คุณเขียนลงในไฟล์เอาต์พุตจะหายไปในครั้งถัดไปที่มีการติดตั้งหรือลบแพ็กเกจเคอร์เนล เนื่องจากสคริปต์ของแพ็กเกจเหล่านั้นจะสร้างไฟล์ขึ้นมาใหม่
cat /usr/sbin/update-grubupdate-grub เป็น wrapper ตัวหนึ่ง มันจะรัน 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 ก่อน จากนั้นจึงอ่านไฟล์ *.cfg ทุกไฟล์ใน /etc/default/grub.d/ ตามลำดับตัวอักษร โปรดอ่านโค้ดที่ทำหน้าที่นี้:
grep -n 'default/grub' /usr/sbin/grub-mkconfigการดึงไฟล์มาใช้งาน (sourcing) เป็นการทำงานของเชลล์ปกติ ดังนั้นการกำหนดค่าล่าสุดจะเป็นผล Ubuntu cloud images มีการจัดส่งไฟล์ไว้ในไดเรกทอรีดังกล่าว โดยไฟล์เหล่านั้นจะกำหนดค่าต่างๆ เช่น timeout และ kernel command line หลังจากที่ไฟล์ของคุณถูกอ่านไปแล้ว ค่า GRUB_TIMEOUT=10 ของคุณใน /etc/default/grub จึงถูกเขียนทับในเวลาต่อมาโดยไฟล์ของผู้ผลิตที่ตั้งค่าไว้เป็น 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 สั่งให้ตัวสร้างเมนูค้นหาระบบไฟล์ root ด้วย partition UUID โดยเขียนค่าลงในบรรทัดคำสั่งของ kernel โดยตรงในรูปแบบ root=PARTUUID=... แทนที่จะใช้วิธีค้นหา filesystem UUID ในระหว่างการบูต ผู้ผลิตอิมเมจกำหนดค่านี้ไว้เพื่อให้ disk image หนึ่งชุดสามารถบูตบนฮาร์ดแวร์ที่ไม่ได้ถูกสร้างมาเพื่อมันได้อย่างน่าเชื่อถือ คำสั่ง grep ที่สองจะแสดงโค้ดที่ทำงานตามตัวแปรนี้ใน /etc/grub.d/10_linux สคริปต์ดังกล่าวอยู่ในดิสก์ของคุณเองและเป็นตัวกำหนดว่าอิมเมจของคุณทำงานอย่างไร
ผลลัพธ์ที่สำคัญคือ: ในเส้นทางดังกล่าว ตัวสร้างเมนูจะเขียนรายการบูตแบบระบุเจาะจงแทนที่จะเป็นรายการ kernel ทั้งหมดที่ติดตั้งไว้ ให้ลองนับจำนวนรายการที่คุณมีอยู่
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 ไม่สามารถหาตำแหน่งรายการนั้นได้ จึงบูตรายการแรกซึ่งก็คือ kernel ใหม่ที่คุณพยายามหลีกเลี่ยง grub-set-default ก็ช่วยไม่ได้เช่นกัน เพราะค่าเริ่มต้นไม่ใช่ส่วนที่เสียหาย เมนูที่คุณพยายามเลือกนั้นไม่เคยถูกสร้างขึ้นมาเลย
หากต้องการให้เมนูเต็มรูปแบบกลับมา ให้ย้ายไฟล์ของผู้ผลิตออกไปก่อนแล้วดูตัวอย่างผลลัพธ์ก่อนที่จะนำไปใช้งานจริง grub-mkconfig โดยไม่มี -o จะเขียนผลลัพธ์ออกทาง standard output และไม่มีการแก้ไขไฟล์ใดๆ บนดิสก์
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 คือวิธีที่อิมเมจของผู้ให้บริการใช้ระบุตำแหน่งระบบไฟล์ root และการลบออกจะทำให้เครื่องเปลี่ยนไปใช้เส้นทางการค้นหาแทน ให้ทำ snapshot ก่อนที่จะรัน update-grub จริง
หากเป้าหมายเดียวของคุณคือการเอาตัวรอดจาก kernel ที่มีปัญหาเพียงตัวเดียว ให้หยุดที่ขั้นตอนนี้และใช้ตัวเลือกที่ปลอดภัยกว่าซึ่งระบุไว้ด้านล่าง การสร้างเมนูบูตใหม่บนเซิร์ฟเวอร์ระยะไกลเพื่อหลีกเลี่ยงการอัปเกรดเพียงครั้งเดียวมีความเสี่ยงมากกว่าปัญหาที่กำลังเผชิญอยู่
เหตุผลที่การระบุหมายเลขลำดับของรายการไม่ใช่แนวทางที่ถูกต้องในการตรึงค่า
GRUB_DEFAULT ยอมรับทั้งหมายเลขลำดับ, ชื่อหัวข้อ หรือตัวระบุ (identifier) โดยหมายเลขลำดับจะนับรายการระดับบนสุดเริ่มจาก 0 สำหรับรายการที่ซ้อนอยู่ภายในจะใช้ > เป็นตัวคั่น ดังนั้น GRUB_DEFAULT="1>2" จึงหมายถึงรายการที่อยู่ในลำดับที่ 2 ภายในเมนูย่อยลำดับที่ 1
ลำดับของรายการสามารถเปลี่ยนแปลงได้ 10_linux จะแสดงรายการเคอร์เนลโดยเรียงจากเวอร์ชันใหม่ที่สุดไปเก่าที่สุด ดังนั้นการติดตั้งเคอร์เนลใหม่จะเลื่อนรายการเก่าทั้งหมดลงไปหนึ่งลำดับ และการลบเคอร์เนลออกจะเลื่อนรายการเหล่านั้นขึ้นมา ค่า 1>2 ที่คุณตั้งไว้อย่างระมัดระวังจะยังคงทำงานได้หลังจากเกิดการเปลี่ยนแปลงดังกล่าว แต่จะกลายเป็นการชี้ไปยังเคอร์เนลคนละตัวกัน โดยที่ระบบจะไม่แจ้งเตือนหรือแสดงข้อผิดพลาดใดๆ และคุณจะทราบถึงปัญหานี้ก็ต่อเมื่อรีบูตเครื่องไปแล้วเท่านั้น
ตัวระบุ (identifier) จะไม่มีการเปลี่ยนแปลงตำแหน่ง เนื่องจากแต่ละตัวระบุจะมีเวอร์ชันของเคอร์เนลรวมอยู่ด้วย คุณสามารถตรวจสอบตัวระบุของคุณได้ดังนี้:
sudo awk -F"'" '/menuentry_id_option/ {print $2, "==>", $4}' /boot/grub/grub.cfgให้ข้ามบรรทัดแรกๆ ของผลลัพธ์ซึ่งเป็นส่วนของการกำหนดตัวแปรในส่วนหัว (header) ไป หลังจากนั้น ข้อมูลทางด้านซ้ายมือคือชื่อหัวข้อที่ผู้ใช้เห็น และข้อมูลทางด้านขวามือคือตัวระบุที่คุณต้องส่งให้กับเครื่องมือต่างๆ สำหรับรายการที่อยู่ภายในเมนูย่อย ให้เชื่อมตัวระบุของเมนูย่อยและตัวระบุของรายการเข้าด้วยกันด้วย > ตามลำดับนั้น ซึ่งเป็นรูปแบบเดียวกับการระบุด้วยหมายเลขลำดับ
การบูต kernel รุ่นก่อนหน้าหนึ่งครั้งด้วย grub-reboot
การเลือกบูตเพียงครั้งเดียวเป็นวิธีที่เหมาะสมสำหรับเซิร์ฟเวอร์ระยะไกล เพราะระบบจะคืนค่ากลับเป็นปกติโดยอัตโนมัติ grub-reboot จะเขียนค่า next_entry ลงใน /boot/grub/grubenv ตัว GRUB จะอ่านตัวแปรนี้ ล้างค่าทิ้ง และบันทึกค่าที่ล้างแล้ว ก่อน ที่จะเริ่มบูตสิ่งใด ดังนั้นหาก kernel เกิดอาการ kernel panic ระบบจะไม่พยายามบูตซ้ำในรอบถัดไป คุณจะมีโอกาสลองหนึ่งครั้ง จากนั้นเครื่องจะกลับไปใช้ค่าเริ่มต้นตามปกติด้วยตัวเอง
ขั้นแรก ให้ตรวจสอบว่าไฟล์ config ที่สร้างขึ้นมีการอ่านตัวแปรดังกล่าวหรือไม่:
sudo grep -n -B2 -A5 'next_entry' /boot/grub/grub.cfgคุณควรพบบรรทัด load_env และบล็อกที่กำหนดค่า default จาก next_entry หากคำสั่ง grep ไม่แสดงผลลัพธ์ใดๆ แสดงว่าอิมเมจของคุณไม่ได้อ่านค่า grubenv ในขณะบูต ดังนั้นคำสั่ง grub-reboot จะถูกยอมรับใน shell แต่จะถูก bootloader เพิกเฉย ซึ่งเป็นปัญหาเดียวกับเส้นทางการบูตตรงที่ระบุไว้ในส่วนก่อนหน้า
sudo grub-reboot '<the identifier you copied>'
sudo grub-editenv listตอนนี้ grub-editenv list ควรแสดงบรรทัด next_entry= ที่มีค่าที่คุณระบุไว้พอดี ให้เปิดคอนโซลของผู้ให้บริการของคุณในแท็บเบราว์เซอร์ จากนั้นสั่ง reboot และตรวจสอบผลลัพธ์
sudo rebootuname -rหาก uname -r แสดงเวอร์ชันที่เก่ากว่า หมายความว่าการล็อกเวอร์ชัน (pin) สำเร็จ หากแสดงเวอร์ชันใหม่ หมายความว่าตัวระบุ (identifier) ไม่สามารถแก้ไขค่าได้ หรือ grubenv ไม่ถูกอ่าน ซึ่งไม่ว่ากรณีใดเครื่องก็จะบูตขึ้นมาได้สำเร็จ ซึ่งเป็นจุดประสงค์หลักของการใช้งานแบบครั้งเดียว (one shot)
ทำให้การตั้งค่าคงอยู่ด้วย GRUB_DEFAULT=saved
GRUB_DEFAULT=saved จะทำให้ค่าเริ่มต้นถูกดึงมาจาก saved_entry ในไฟล์ grubenv และคุณสามารถกำหนดค่าดังกล่าวได้ด้วยคำสั่ง grub-set-default วิธีนี้จะยังคงอยู่แม้มีการติดตั้ง kernel ใหม่ เนื่องจาก 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 เป็นการตั้งค่าที่แตกต่างออกไปและมักสับสนกับค่านี้ได้ง่าย โดยมันจะบันทึกสิ่งที่คุณเพิ่งบูตเข้าใช้งานให้กลายเป็นค่าเริ่มต้นใหม่ ดังนั้นค่าเริ่มต้นจะเปลี่ยนไปตามการบูตครั้งล่าสุดที่สำเร็จ สำหรับเซิร์ฟเวอร์แล้ว นั่นหมายความว่าการรีบูตโดยไม่มีผู้ดูแลอาจทำให้ค่าที่คุณปักหมุดไว้เปลี่ยนไปโดยไม่รู้ตัว ควรปิดการตั้งค่านี้ไว้เว้นแต่ว่านั่นคือสิ่งที่คุณต้องการ
การปักหมุดด้วยตัวระบุ (identifier) ยังคงมีจุดที่ล้มเหลวได้ หากคุณลบ kernel ที่ระบุชื่อไว้ออก ตัวระบุนั้นจะไม่สามารถแก้ไขค่าได้ ซึ่งจะทำให้ระบบกลับไปเลือกรายการแรกแทน ดังนั้นคุณควรล็อก (hold) แพ็กเกจดังกล่าวไว้ หรือตั้งค่าไม่ให้ kernel นั้นถูกลบออกโดยคำสั่ง autoremove
การนำเมนูเข้าสู่คอนโซลของผู้ให้บริการ
การเลือกแบบโต้ตอบจำเป็นต้องแสดงเมนูบนหน้าจอ แต่ cloud image มักจะซ่อนเมนูนี้ไว้ ให้ใส่ค่าเหล่านี้ลงในไฟล์ที่ถูกประมวลผลเป็นลำดับสุดท้าย จากนั้นจึงรัน sudo update-grub
GRUB_TIMEOUT=10
GRUB_TIMEOUT_STYLE=menu
GRUB_RECORDFAIL_TIMEOUT=10การใช้ GRUB_TIMEOUT_STYLE=hidden ร่วมกับ GRUB_TIMEOUT=0 จะไม่แสดงผลใดๆ ออกมาเลย ทำให้ผู้ที่เฝ้าดูคอนโซลเห็นข้อความของ kernel เริ่มทำงานทันทีและสรุปว่า bootloader ถูกข้ามไป ส่วน GRUB_RECORDFAIL_TIMEOUT คือค่า timeout แยกต่างหากที่ใช้หลังจากบูตไม่สำเร็จ ซึ่ง cloud image มักตั้งค่าไว้ที่ 0 เช่นกัน จึงเป็นเหตุผลว่าทำไมเซิร์ฟเวอร์ที่บูตล้มเหลวถึงไม่หยุดรอให้คุณดำเนินการ
หากผู้ให้บริการของคุณให้คอนโซลแบบ serial แทนแบบกราฟิกแล้วคุณยังไม่เห็นอะไรเลย แสดงว่า GRUB กำลังเขียนข้อมูลไปยัง terminal ที่คุณมองไม่เห็น ให้เพิ่มทั้งสองบรรทัดนี้พร้อมกัน เพราะบรรทัดแรกใช้เลือก output และบรรทัดที่สองใช้กำหนดค่าพอร์ต:
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"นับจากนี้ไปจะมีการเพิ่มเวลา 10 วินาทีในทุกการบูต ให้ตั้งค่า timeout กลับเป็น 0 เมื่อคุณดำเนินการเสร็จสิ้น
ทางเลือกที่ปลอดภัยกว่าการแก้ไข bootloader
การเปลี่ยนค่า input ของ bootloader บนเครื่องที่คุณเข้าถึงได้ผ่าน SSH เท่านั้นถือเป็นตัวเลือกที่มีความเสี่ยงสูงสุดในหน้านี้ ยังมีวิธีแก้ปัญหาที่ต้นทุนต่ำกว่าและมักจะแก้ปัญหาที่แท้จริงได้ดีกว่า
การระงับการอัปเดตแพ็กเกจ kernel (Hold the kernel packages) หากเป้าหมายคือ "ไม่ต้องการให้ระบบอัปเดต kernel ใหม่" ให้แจ้งความต้องการนี้กับ package manager แทนที่จะไปแก้ไขที่ 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ให้ใช้ชื่อแพ็กเกจตามที่คำสั่งแรกแสดงผล เนื่องจากอิมเมจบนระบบคลาวด์มักจะติดตั้ง kernel รุ่น virtual หรือ kvm แทนที่จะเป็น generic แพ็กเกจที่ถูกระงับไว้จะถูกข้ามโดย apt upgrade ซึ่งจะแจ้งเตือนด้วยข้อความ The following packages have been kept back: และจะถูกข้ามโดย การอัปเดตอัตโนมัติบน Ubuntu ด้วยเช่นกัน วิธีนี้มีข้อควรระวังคือ kernel ที่ถูกระงับไว้จะไม่ได้รับแพตช์ความปลอดภัย ดังนั้นให้ถือว่าเป็นการหยุดอัปเดตชั่วคราวและควรปลดล็อกด้วยคำสั่ง sudo apt-mark unhold เมื่อถึงเวลา หากคุณหลีกเลี่ยงการอัปเดต kernel เพียงเพราะไม่ต้องการให้ระบบต้องรีบูตจนเกิด downtime แทนที่จะเป็นเพราะ kernel รุ่นใหม่มีปัญหา การทำ live kernel patching บน VPS คือคำตอบสำหรับกรณีนี้
ทำ snapshot ก่อนการอัปเกรด การทำ snapshot ช่วยให้กู้คืนระบบได้ภายในไม่กี่นาที โดยไม่ต้องพิมพ์คำสั่งผ่าน console และไม่มีความเสี่ยงจากการแก้ไข bootloader ที่อาจไม่สมบูรณ์ ให้ทำ snapshot, อัปเกรด, รีบูต และตรวจสอบผล หาก kernel ใหม่ทำงานผิดปกติ ให้ย้อนกลับ (roll back) ซึ่งจะทำให้เส้นทางการบูตกลับไปเป็นสถานะเดิมอย่างแม่นยำ
ใช้ console หรือ rescue image สำหรับเครื่องที่ล่มไปแล้ว เมื่อเซิร์ฟเวอร์ไม่สามารถบูตได้ การแก้ไขไฟล์ config ของ bootloader ไม่ใช่วิธีที่ถูกต้องในการกู้คืน และเส้นทางการกู้คืนนั้นมีขั้นตอนเฉพาะของมันเอง: สิ่งที่ควรทำเมื่อ VPS บูตไม่ขึ้นหลังการอัปเดต kernel
สิ่งที่ทำให้เกิดข้อผิดพลาดและข้อความที่คุณจะพบ
การแก้ไข /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) เกิด kernel panic พร้อมข้อความ 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 ถึงไม่เปลี่ยน kernel ที่ VPS ของฉันใช้บูต?
เนื่องจากใน Ubuntu cloud image นั้น /boot/grub/grub.cfg ที่ถูกสร้างขึ้นมักจะมีรายการบูตเพียงรายการเดียว ดังนั้นดัชนีที่ 1 จึงไม่มีอยู่จริงและ GRUB จะย้อนกลับไปใช้รายการแรกแทน ให้ตรวจสอบด้วย sudo grep -cE '^\s*(menuentry|submenu) ' /boot/grub/grub.cfg หากนับได้ 1 นั่นคือคำตอบ สาเหตุมาจาก GRUB_FORCE_PARTUUID ซึ่งผู้ให้บริการ image ตั้งค่าไว้ในไฟล์ภายใต้ /etc/default/grub.d/ ทำให้ตัวสร้างรายการบูตเลือกใช้เส้นทางการบูตโดยตรงแทนที่จะสร้างรายการ kernel ทั้งหมดที่ติดตั้งไว้ คุณสามารถค้นหาไฟล์ดังกล่าวได้ด้วย grep -rn GRUB_FORCE_PARTUUID /etc/default/grub /etc/default/grub.d/
ฉันจะบูตด้วย kernel รุ่นก่อนหน้าเพียงครั้งเดียวได้อย่างไร?
ให้รัน sudo grub-reboot '<identifier>' โดยใช้ตัวระบุ (identifier) ที่คัดลอกมาจาก grub.cfg ของคุณเอง จากนั้นทำการรีบูตโดยเปิดหน้าต่าง console ของผู้ให้บริการค้างไว้ GRUB จะล้างค่า next_entry ทันทีหลังจากบูตเสร็จ ดังนั้นตัวเลือกนี้จะมีผลเพียงครั้งเดียว และหาก kernel เกิดอาการ kernel panic ระบบจะไม่พยายามบูตซ้ำด้วย kernel เดิม ให้ตรวจสอบว่าค่าถูกบันทึกไว้ด้วย sudo grub-editenv list ก่อนจะใช้งานจริงให้รัน sudo grep -n next_entry /boot/grub/grub.cfg เพราะหาก image ที่คุณใช้มีการตั้งค่าที่ไม่โหลด grubenv คำสั่งดังกล่าวจะถูกเพิกเฉยโดยไม่มีการแจ้งเตือนข้อผิดพลาดใดๆ
ฉันควรระบุ kernel โดยใช้หมายเลขลำดับหรือตัวระบุ?
ควรใช้ตัวระบุ หมายเลขลำดับคือตำแหน่งในรายการที่ 10_linux จะเรียงลำดับใหม่โดยเอา kernel ใหม่ล่าสุดไว้บนสุด ดังนั้นการติดตั้งหรือลบ kernel ใดๆ จะทำให้ลำดับเปลี่ยนไป และค่า 1>2 ที่ล้าสมัยอาจชี้ไปยัง kernel อื่นที่ถูกต้องแต่ไม่ใช่ตัวที่คุณต้องการโดยไม่มีข้อความเตือนใดๆ ตัวระบุจะมีเวอร์ชันของ kernel กำกับอยู่ ดังนั้นมันจะตรงกับ kernel ที่คุณต้องการหรือล้มเหลวไปเลย ให้แสดงรายการด้วย sudo grep -n menuentry_id_option /boot/grub/grub.cfg และคัดลอกข้อความในเครื่องหมายคำพูดที่ปรากฏในแต่ละบรรทัดของรายการ
การล็อก (hold) แพ็กเกจ kernel ปลอดภัยกว่าการแก้ไข bootloader หรือไม่?
สำหรับเป้าหมายทั่วไป คำตอบคือใช่ sudo apt-mark hold linux-image-virtual linux-headers-virtual จะหยุดการอัปเดต kernel ใหม่ไม่ให้เข้ามาในระบบ ทำให้เส้นทางการบูตไม่เปลี่ยนแปลงและลดความเสี่ยงที่จะเกิดข้อผิดพลาดในกรณีที่คุณไม่สามารถเข้าถึง console ได้ ให้ตรวจสอบชื่อ flavour ของ kernel ที่ติดตั้งอยู่ในเครื่องของคุณก่อนด้วย apt list --installed และตรวจสอบสถานะการล็อกด้วย apt-mark showhold ข้อแลกเปลี่ยนคือ kernel ที่ถูกล็อกไว้จะไม่ได้รับแพตช์ความปลอดภัย ดังนั้นควรวางแผนให้ดีว่าจะรัน sudo apt-mark unhold เมื่อใดก่อนที่จะทำการล็อกแพ็กเกจ