戰(zhàn):跳過(guò)重啟實(shí)現(xiàn)秒切Linux內(nèi)核的完整指南)
1. 從“重啟”到“秒切內(nèi)核”kexec到底解決了什么痛點(diǎn)我在生產(chǎn)環(huán)境折騰內(nèi)核不是一天兩天了。早些年每次升級(jí)內(nèi)核或者調(diào)試一個(gè)新的內(nèi)核特性最讓人頭疼的就是“重啟”這兩個(gè)字。一臺(tái)承載著業(yè)務(wù)的服務(wù)機(jī)動(dòng)不動(dòng)就要等上幾分鐘甚至十幾分鐘的BIOS自檢、硬件初始化、引導(dǎo)加載程序掃描然后才是內(nèi)核解壓、驅(qū)動(dòng)加載、服務(wù)拉起。如果是一臺(tái)需要快速切換內(nèi)核的測(cè)試機(jī)倒還好但如果是在線上環(huán)境每多等一秒都意味著業(yè)務(wù)中斷時(shí)間被拉長(zhǎng)。有沒(méi)有一種辦法能跳過(guò)那堆冗長(zhǎng)的硬件自檢和引導(dǎo)流程直接從當(dāng)前正在運(yùn)行的Linux內(nèi)核“跳”到一個(gè)新內(nèi)核答案就是題目里這個(gè)命令kexec。kexec是Linux內(nèi)核提供的一個(gè)機(jī)制它的全稱是“Kernel EXecution”直白地說(shuō)就是讓當(dāng)前正在運(yùn)行的內(nèi)核直接加載并切換到一個(gè)新內(nèi)核不需要經(jīng)過(guò)傳統(tǒng)的重啟流程。底層對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用用戶態(tài)配套的工具也叫kexec。它的工作方式相當(dāng)于你在辦公室里正開著會(huì)突然要換一間會(huì)議室但你不是走出大樓再?gòu)拇箝T進(jìn)來(lái)而是直接推開隔壁會(huì)議室的門坐下——省掉了所有重復(fù)的入場(chǎng)流程。這個(gè)能力在不少場(chǎng)景下都非常值錢。比如線上內(nèi)核升級(jí)后需要驗(yàn)證新內(nèi)核是否穩(wěn)定你不想經(jīng)歷漫長(zhǎng)的停機(jī)窗口再比如你在調(diào)試一個(gè)會(huì)導(dǎo)致系統(tǒng)崩潰的內(nèi)核模塊希望崩潰時(shí)能自動(dòng)跳到一個(gè)保留的“急救內(nèi)核”去抓取現(xiàn)場(chǎng)信息這就是后面要聊的kdump機(jī)制的基礎(chǔ)。作為L(zhǎng)inux運(yùn)維、內(nèi)核開發(fā)者或者底層系統(tǒng)愛好者掌握kexec幾乎是必修課。這篇內(nèi)容我盡量用實(shí)際操作的角度把kexec的原理、用法、坑點(diǎn)和配套場(chǎng)景都講透確保你不僅能看懂還能直接拿去用。2. kexec的兩種核心工作模式與系統(tǒng)級(jí)架構(gòu)2.1 加載式的兩階段操作kexec -l 與 kexec -ekexec這個(gè)工具的使用邏輯非常清晰一共就兩大步先加載再執(zhí)行。第一步用kexec -l把新內(nèi)核和它的initrd初始根文件系統(tǒng)加載到當(dāng)前內(nèi)核已經(jīng)預(yù)留好的內(nèi)存區(qū)域中同時(shí)把啟動(dòng)參數(shù)一并準(zhǔn)備好。執(zhí)行完這一步新內(nèi)核其實(shí)是“待命”狀態(tài)當(dāng)前系統(tǒng)一切照常運(yùn)行沒(méi)有任何切換發(fā)生。第二步在你準(zhǔn)備好的時(shí)機(jī)執(zhí)行kexec -e當(dāng)前內(nèi)核會(huì)立即停止一切正?;顒?dòng)直接跳轉(zhuǎn)到之前加載好的新內(nèi)核入口地址。整個(gè)切換過(guò)程非??焱ǔ>褪且粌擅氲氖赂緵](méi)有傳統(tǒng)重啟那樣的硬件重置和引導(dǎo)流程。這兩個(gè)步驟分開設(shè)計(jì)是刻意為之。因?yàn)榧虞d內(nèi)核是一個(gè)相對(duì)耗時(shí)的過(guò)程需要讀取內(nèi)核文件、解壓縮、把各段放入內(nèi)存、組裝啟動(dòng)參數(shù)而執(zhí)行切換是瞬間動(dòng)作。把加載和執(zhí)行解耦意味著你可以提前很多時(shí)間把新內(nèi)核準(zhǔn)備好然后在業(yè)務(wù)低峰期一鍵切換。我在實(shí)際使用中經(jīng)常提前幾分鐘把內(nèi)核加載好反復(fù)檢查啟動(dòng)參數(shù)無(wú)誤最后執(zhí)行真正的切換動(dòng)作。需要注意kexec -l參數(shù)后面可以直接指定內(nèi)核鏡像文件路徑比如/boot/vmlinuz-xxx。同時(shí)幾乎都必須配合--initrd指定initrd文件否則新內(nèi)核可能因?yàn)槿鄙俦匾尿?qū)動(dòng)模塊而無(wú)法掛載根文件系統(tǒng)。至于啟動(dòng)參數(shù)可以用--command-line手動(dòng)指定但如果懶得一個(gè)個(gè)敲直接用--reuse-cmdline復(fù)用當(dāng)前內(nèi)核的啟動(dòng)參數(shù)也是一種省事且穩(wěn)妥的做法。2.2 立即執(zhí)行的kexec系統(tǒng)調(diào)用與一次性的頂層封裝上面說(shuō)的兩階段是針對(duì)kexec工具的使用流程。在系統(tǒng)調(diào)用層面其實(shí)還有一個(gè)更加猛烈的封裝kexec_load系統(tǒng)調(diào)用可以直接完成從加載到執(zhí)行的整個(gè)過(guò)程因?yàn)榧虞d和執(zhí)行本質(zhì)上是一個(gè)連續(xù)的動(dòng)作。只是內(nèi)核出于安全考慮通常會(huì)要求調(diào)用者具備CAP_SYS_BOOT權(quán)限而且大多數(shù)發(fā)行版默認(rèn)不會(huì)把這種“自殺式”調(diào)用暴露給普通用戶。實(shí)際日常操作中我們使用kexec工具就足夠了。kexec工具在加載階段會(huì)把內(nèi)核鏡像、initrd等數(shù)據(jù)通過(guò)kexec_load系統(tǒng)調(diào)用交給內(nèi)核但并不會(huì)觸發(fā)立即切換。而kexec -e執(zhí)行的其實(shí)是另一個(gè)系統(tǒng)調(diào)用reboot(LINUX_REBOOT_CMD_KEXEC)相當(dāng)于告訴內(nèi)核“我已經(jīng)準(zhǔn)備好了你現(xiàn)在可以跳轉(zhuǎn)過(guò)去了”。此外如果你的系統(tǒng)使用systemd作為init系統(tǒng)還有一個(gè)更省事的入口systemctl kexec。這個(gè)命令會(huì)讀取systemd預(yù)設(shè)的kexec加載配置通常是/run/systemd/system/kexec.conf或者由systemd-kexec服務(wù)在啟動(dòng)時(shí)配置然后自動(dòng)完成加載并執(zhí)行切換。對(duì)很多場(chǎng)景來(lái)說(shuō)直接用systemctl kexec比手工敲kexec -l加kexec -e要省心很多但前提是你得先把加載信息提前配置好。3. 為什么kexec能跳過(guò)重啟深入內(nèi)核的切換機(jī)制3.1 跳過(guò)固件與引導(dǎo)加載程序的關(guān)鍵設(shè)計(jì)傳統(tǒng)啟動(dòng)的路徑是電源上電 - BIOS/UEFI自檢 - 引導(dǎo)加載程序如GRUB加載內(nèi)核到內(nèi)存 - 內(nèi)核初始化硬件 - 掛載根文件系統(tǒng) - 啟動(dòng)init進(jìn)程。這個(gè)過(guò)程中BIOS/UEFI自檢和引導(dǎo)加載程序的掃描往往是耗時(shí)的大頭很多時(shí)候不需要每次都完整執(zhí)行。kexec的核心思想是當(dāng)前內(nèi)核已經(jīng)完成過(guò)硬件初始化這些硬件狀態(tài)其實(shí)都還活著不需要重新初始化一遍。新內(nèi)核只需要接管這些已經(jīng)被初始化好的硬件即可。所以kexec在加載新內(nèi)核的時(shí)候會(huì)把新內(nèi)核鏡像放入一段特定的內(nèi)存區(qū)域然后在新內(nèi)核入口點(diǎn)執(zhí)行時(shí)它并不會(huì)像傳統(tǒng)引導(dǎo)那樣重新掃描總線、重新枚舉設(shè)備而是盡可能繼承當(dāng)前內(nèi)核已經(jīng)建立的硬件上下文。這就像你換了個(gè)新的大腦來(lái)接管一個(gè)已經(jīng)正常運(yùn)行的身體而不是讓整個(gè)身體推到手術(shù)室重新組裝一遍。當(dāng)然實(shí)際操作中新內(nèi)核還是會(huì)做大量的軟初始化工作比如重新初始化內(nèi)存管理、進(jìn)程調(diào)度、中斷等核心子系統(tǒng)但硬件層面的重復(fù)枚舉確實(shí)被大幅簡(jiǎn)化了。3.2 內(nèi)存布局與加載地址的指定邏輯kexec加載內(nèi)核時(shí)有一個(gè)很重要的概念是加載地址。使用kexec -l加載時(shí)系統(tǒng)默認(rèn)會(huì)選擇一段“安全”的內(nèi)存區(qū)域來(lái)放新內(nèi)核。這個(gè)安全區(qū)域通常是當(dāng)前內(nèi)核未使用的物理內(nèi)存段由內(nèi)核在啟動(dòng)時(shí)預(yù)留或者動(dòng)態(tài)分配。在某些特殊場(chǎng)景下你可能需要手動(dòng)指定加載地址比如當(dāng)默認(rèn)加載區(qū)域被某些固件或驅(qū)動(dòng)占用時(shí)會(huì)報(bào)出kexec: Cannot allocate memory或者類似的內(nèi)存沖突錯(cuò)誤。這時(shí)可以通過(guò)--load參數(shù)配合地址選項(xiàng)手動(dòng)指定。但絕大多數(shù)情況下內(nèi)核自帶的這個(gè)內(nèi)存分配邏輯已經(jīng)足夠智能會(huì)自動(dòng)避開正在使用的內(nèi)存區(qū)域。這里要特別提醒一個(gè)點(diǎn)kexec加載新內(nèi)核時(shí)必須保證新內(nèi)核鏡像的完整性并且新內(nèi)核運(yùn)行時(shí)需要的物理內(nèi)存區(qū)域覆蓋范圍不能和當(dāng)前內(nèi)核正在使用的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)沖突。否則切換后可能出現(xiàn)無(wú)法解釋的隨機(jī)崩潰。我在調(diào)試時(shí)遇到過(guò)好幾次類似問(wèn)題最后都發(fā)現(xiàn)是加載地址選擇不當(dāng)造成的。3.3 內(nèi)核配置選項(xiàng)與kexec的依賴關(guān)系要在你的Linux系統(tǒng)上使用kexec首先內(nèi)核必須開啟相應(yīng)的配置項(xiàng)最常見的有三個(gè)CONFIG_KEXEC、CONFIG_KEXEC_FILE和CONFIG_CRASH_DUMP。CONFIG_KEXEC是支持傳統(tǒng)kexec加載機(jī)制的開關(guān)CONFIG_KEXEC_FILE是指基于文件描述符的加載方式也就是用kexec -s參數(shù)時(shí)走的那條路徑?;谖募募虞d方式在安全性上要更好一些因?yàn)閮?nèi)核會(huì)對(duì)鏡像簽名做校驗(yàn)尤其在啟用了Secure Boot的機(jī)器上這種方式幾乎成了唯一選擇。如果你的內(nèi)核編譯時(shí)沒(méi)開這兩個(gè)選項(xiàng)即使你裝了kexec工具執(zhí)行時(shí)也會(huì)提示不支持。所以在折騰kexec之前先確認(rèn)一下你的內(nèi)核配置zgrep KEXEC /proc/config.gz或者如果你的發(fā)行版沒(méi)有把config導(dǎo)出到/proc/config.gz那就檢查一下/boot/config-$(uname -r)文件。確認(rèn)有CONFIG_KEXECy或CONFIG_KEXEC_FILEy之后再進(jìn)行下一步。4. 實(shí)操篇kexec的安裝、加載與切換全流程4.1 安裝kexec-tools不同發(fā)行版的差異kexec的用戶態(tài)工具一般叫kexec-tools幾乎所有主流Linux發(fā)行版都提供了對(duì)應(yīng)的軟件包。在Debian/Ubuntu系列上安裝命令是sudo apt install kexec-tools安裝過(guò)程中會(huì)詢問(wèn)你是否允許系統(tǒng)在重啟時(shí)自動(dòng)使用kexec快速啟動(dòng)如果沒(méi)有特殊需求可以先選擇否手動(dòng)控制更穩(wěn)。在RHEL/CentOS/Fedora系列上安裝命令是sudo yum install kexec-tools或者在新版本上用sudo dnf install kexec-tools在openSUSE上則是sudo zypper install kexec-tools安裝完成后先用kexec --version驗(yàn)證一下工具是否正??捎谩0姹咎?hào)不同參數(shù)解析上會(huì)有細(xì)微差異但核心操作邏輯基本一致。4.2 加載新內(nèi)核到內(nèi)存一個(gè)完整的kexec -l示例下面我以一個(gè)實(shí)際的升級(jí)場(chǎng)景來(lái)演示。假設(shè)當(dāng)前系統(tǒng)跑的是5.15內(nèi)核你要切換到剛裝好的6.1內(nèi)核但暫時(shí)不想執(zhí)行真正的重啟動(dòng)作。首先確認(rèn)新內(nèi)核和initrd文件已經(jīng)放在了/boot目錄下ls /boot/vmlinuz-6.1* /boot/initrd.img-6.1*確認(rèn)無(wú)誤之后執(zhí)行加載命令sudo kexec -l /boot/vmlinuz-6.1.0-rc3-generic \ --initrd/boot/initrd.img-6.1.0-rc3-generic \ --reuse-cmdline這里--reuse-cmdline會(huì)自動(dòng)復(fù)用當(dāng)前內(nèi)核的啟動(dòng)參數(shù)在很多場(chǎng)景下是最省事的選擇。但如果你的新內(nèi)核需要額外的啟動(dòng)參數(shù)比如要預(yù)留內(nèi)存給kdump那就需要手動(dòng)指定sudo kexec -l /boot/vmlinuz-6.1.0-rc3-generic \ --initrd/boot/initrd.img-6.1.0-rc3-generic \ --command-lineroot/dev/mapper/vg-root ro crashkernel256M quiet這里有一個(gè)小提醒手動(dòng)指定啟動(dòng)參數(shù)時(shí)root一定要寫對(duì)否則新內(nèi)核起來(lái)后會(huì)因?yàn)檎也坏礁募到y(tǒng)而直接進(jìn)入initramfs的緊急模式甚至直接panic。如果你不確定當(dāng)前根文件系統(tǒng)的設(shè)備路徑可以先執(zhí)行findmnt /查看。4.3 執(zhí)行切換kexec -e與systemctl kexec加載完成之后你可以通過(guò)kexec -e立即執(zhí)行內(nèi)核切換。但執(zhí)行前務(wù)必確認(rèn)幾件事所有重要的數(shù)據(jù)已保存當(dāng)前服務(wù)的狀態(tài)可以接受中斷。因?yàn)閗exec -e執(zhí)行后當(dāng)前內(nèi)核幾乎不會(huì)有任何優(yōu)雅的退出流程所有用戶態(tài)進(jìn)程會(huì)直接消失未落盤的數(shù)據(jù)可能會(huì)丟失。如果你用的是systemd更優(yōu)雅的替代方案是sudo systemctl kexecsystemctl kexec會(huì)觸發(fā)systemd先執(zhí)行一些基本的服務(wù)停止操作然后再調(diào)用kexec切換。注意它并不是完整地執(zhí)行關(guān)機(jī)流程而是會(huì)盡量在短時(shí)間內(nèi)停掉部分服務(wù)、卸載部分文件系統(tǒng)然后快速切換。優(yōu)點(diǎn)是比kexec -e稍微溫和一些缺點(diǎn)是沒(méi)有完整關(guān)機(jī)那么干凈。從我個(gè)人的經(jīng)驗(yàn)來(lái)看如果是測(cè)試機(jī)或者你能扛得住服務(wù)直接中斷的場(chǎng)景直接kexec -e反而更簡(jiǎn)單直接因?yàn)榧虞d階段你已經(jīng)準(zhǔn)備好了一切。systemctl kexec多執(zhí)行的那些步驟有時(shí)候反而會(huì)引入額外的變量。4.4 用腳本一鍵完成“加載-切換”的自動(dòng)化實(shí)踐如果你經(jīng)常需要切換不同的內(nèi)核寫一個(gè)簡(jiǎn)單的腳本可以省去每次手動(dòng)輸入?yún)?shù)的麻煩。下面是一個(gè)我常用的shell腳本模板#!/bin/bash KERNEL$1 INITRD$2 EXTRA_CMDLINE${3:-} if [ -z $KERNEL ] || [ -z $INITRD ]; then echo Usage: $0 kernel-image initrd-image [extra-cmdline] exit 1 fi echo Loading kernel: $KERNEL echo Initrd: $INITRD CMD if [ -n $EXTRA_CMDLINE ]; then CMD--command-line$EXTRA_CMDLINE else CMD--reuse-cmdline fi sudo kexec -l $KERNEL --initrd$INITRD $CMD echo Kernel loaded. Run sudo kexec -e to switch.使用時(shí)就執(zhí)行./kexec-switch.sh /boot/vmlinuz-6.1.0-rc3-generic /boot/initrd.img-6.1.0-rc3-generic這個(gè)腳本只負(fù)責(zé)加載切換動(dòng)作留給使用者手動(dòng)觸發(fā)避免腳本誤觸發(fā)導(dǎo)致意外重啟。5. kdump場(chǎng)景kexec的另一半江山5.1 crashkernel預(yù)留內(nèi)存與kdump機(jī)制講到kexec不可能不提kdump。kdump是Linux內(nèi)核崩潰轉(zhuǎn)儲(chǔ)機(jī)制的核心它依賴的正是kexec技術(shù)。在系統(tǒng)啟動(dòng)時(shí)通過(guò)內(nèi)核參數(shù)crashkernel256M預(yù)留一塊物理內(nèi)存這塊內(nèi)存專門用于在系統(tǒng)崩潰時(shí)加載和維護(hù)一個(gè)“捕獲內(nèi)核”。平時(shí)這個(gè)捕獲內(nèi)核并不會(huì)運(yùn)行它只是被kexec預(yù)先加載到預(yù)留內(nèi)存中處于待命狀態(tài)。當(dāng)主內(nèi)核發(fā)生panic時(shí)它會(huì)直接跳轉(zhuǎn)到這個(gè)捕獲內(nèi)核捕獲內(nèi)核啟動(dòng)后會(huì)盡可能保存崩潰現(xiàn)場(chǎng)的內(nèi)存映像也就是生成一個(gè)vmcore文件供后續(xù)離線分析。這背后的流程是系統(tǒng)正常啟動(dòng)時(shí)用kexec -p加載捕獲內(nèi)核。這里-p參數(shù)的含義是panic內(nèi)核加載目標(biāo)是一段獨(dú)立的預(yù)留內(nèi)存。等到真正崩潰時(shí)主內(nèi)核無(wú)法繼續(xù)執(zhí)行但它會(huì)跳轉(zhuǎn)到這段預(yù)留內(nèi)存中運(yùn)行捕獲內(nèi)核。捕獲內(nèi)核不依賴被崩潰破壞的根文件系統(tǒng)而是從預(yù)先加載好的initrd啟動(dòng)然后把主內(nèi)核的內(nèi)存轉(zhuǎn)儲(chǔ)到磁盤上。5.2 配置kdump的完整步驟與參數(shù)說(shuō)明配置一套可用的kdump環(huán)境其實(shí)很簡(jiǎn)單。首先在GRUB的內(nèi)核啟動(dòng)參數(shù)中加入crashkernel256M這個(gè)大小取決于你的系統(tǒng)內(nèi)存總量和需要轉(zhuǎn)儲(chǔ)的內(nèi)存大小。一般建議至少預(yù)留系統(tǒng)內(nèi)存的1/16比如系統(tǒng)有8G內(nèi)存預(yù)留512M會(huì)比較穩(wěn)。修改/etc/default/grub中的GRUB_CMDLINE_LINUX加上參數(shù)然后執(zhí)行sudo update-grub重啟后確認(rèn)預(yù)留內(nèi)存已生效cat /proc/iomem | grep Crash kernel這一步如果沒(méi)有任何輸出說(shuō)明crashkernel參數(shù)沒(méi)有生效需要檢查是不是GRUB配置沒(méi)寫對(duì)。接著安裝kdump工具sudo apt install kdump-tools安裝完成后kdump-tools服務(wù)會(huì)自動(dòng)用kexec -p把當(dāng)前內(nèi)核的捕獲內(nèi)核加載到預(yù)留內(nèi)存中。驗(yàn)證是否加載成功可以看kdump-config show正常情況下會(huì)顯示ready to kdump狀態(tài)。最后測(cè)試一下崩潰轉(zhuǎn)儲(chǔ)是否生效sudo sysctl -w kernel.panic_on_oops1 sudo echo c /proc/sysrq-trigger這條命令會(huì)強(qiáng)制觸發(fā)內(nèi)核panic然后系統(tǒng)應(yīng)該會(huì)快速跳轉(zhuǎn)到捕獲內(nèi)核并生成vmcore。注意執(zhí)行這條命令后系統(tǒng)會(huì)短暫不可用生產(chǎn)環(huán)境別亂試。測(cè)試成功后重啟回到正常內(nèi)核檢查/var/crash目錄下是否有vmcore文件生成。5.3 捕獲內(nèi)核與主內(nèi)核的資源隔離問(wèn)題kdump有一個(gè)容易被忽略的問(wèn)題捕獲內(nèi)核和主內(nèi)核共享物理硬件但預(yù)留內(nèi)存是獨(dú)立的。如果捕獲內(nèi)核加載了和主內(nèi)核沖突的驅(qū)動(dòng)或者在轉(zhuǎn)儲(chǔ)時(shí)訪問(wèn)了被主內(nèi)核破壞的硬件寄存器可能導(dǎo)致轉(zhuǎn)儲(chǔ)失敗。為了減少這種概率很多發(fā)行版會(huì)為捕獲內(nèi)核準(zhǔn)備一個(gè)專門的initrd里面只包含最基礎(chǔ)的驅(qū)動(dòng)模塊。實(shí)際操作中如果發(fā)現(xiàn)kdump經(jīng)常轉(zhuǎn)儲(chǔ)失敗大概率是initrd里包含了太多不必要或者有副作用的驅(qū)動(dòng)模塊。這時(shí)候可以考慮精簡(jiǎn)initrd只保留磁盤控制器、網(wǎng)卡等必要驅(qū)動(dòng)。不過(guò)需要注意的是精簡(jiǎn)initrd是個(gè)技術(shù)活搞不好反而會(huì)讓捕獲內(nèi)核根本起不來(lái)。我在這里的建議是先用發(fā)行版默認(rèn)的initrd跑一遍測(cè)試確認(rèn)基本可用后再考慮優(yōu)化。6. 我踩過(guò)的坑kexec使用中的常見問(wèn)題與排查技巧6.1 kexec -l 報(bào)錯(cuò)內(nèi)核鏡像格式或權(quán)限問(wèn)題最常見的問(wèn)題之一是在執(zhí)行kexec -l時(shí)直接報(bào)kexec: Loading kernel failed或者Invalid file format。這類錯(cuò)誤通常有兩個(gè)原因。第一是權(quán)限不夠。kexec加載內(nèi)核需要root權(quán)限如果你用的是普通用戶記得加上sudo。第二是內(nèi)核鏡像文件格式不對(duì)。部分發(fā)行版在/boot目錄下的vmlinuz文件不是標(biāo)準(zhǔn)的Linux內(nèi)核鏡像而是帶EFI stub的混合鏡像某些舊版本的kexec-tools識(shí)別不了。解決辦法是使用kexec -s走基于文件描述符的加載方式它會(huì)用內(nèi)核內(nèi)部的kexec_file_load接口來(lái)處理兼容性通常更好。另外還有一個(gè)小概率問(wèn)題內(nèi)核鏡像文件損壞。檢查一下文件的校驗(yàn)值是否和發(fā)行版的發(fā)布值一致。如果文件不完整加載時(shí)內(nèi)核在解壓階段就會(huì)失敗。6.2 切換后新內(nèi)核起不來(lái)initrd問(wèn)題與模塊缺失kexec -e執(zhí)行后如果新內(nèi)核一直黑屏或者反復(fù)重啟大概率是initrd有問(wèn)題。initrd的作用是在真正的根文件系統(tǒng)掛載前提供必要的驅(qū)動(dòng)和工具讓內(nèi)核能找到根設(shè)備。如果你沒(méi)有指定initrd或者initrd和內(nèi)核版本不匹配新內(nèi)核會(huì)因?yàn)闆](méi)有磁盤驅(qū)動(dòng)而無(wú)法掛載根文件系統(tǒng)。遇到這種情況我的排查思路是這樣在kexec -l之前先檢查一下initrd里是否包含了對(duì)應(yīng)的磁盤控制器驅(qū)動(dòng)??梢杂靡韵旅畈榭磇nitrd內(nèi)容lsinitramfs /boot/initrd.img-6.1.0-rc3-generic如果發(fā)現(xiàn)缺少必要模塊可以用update-initramfs -c -k 6.1.0-rc3-generic重新生成initrd。另外一個(gè)容易被忽略的點(diǎn)是部分驅(qū)動(dòng)模塊依賴固件文件而固件文件在initrd中缺失導(dǎo)致驅(qū)動(dòng)加載失敗。這種問(wèn)題通常會(huì)在系統(tǒng)日志中留下firmware: failed to load之類的記錄。排查時(shí)如果黑屏看不到任何輸出可以考慮在內(nèi)核啟動(dòng)參數(shù)里加上noreplace-paravirt earlyprintk這類調(diào)試選項(xiàng)讓啟動(dòng)早期信息輸出到串口或VGA上。6.3 Secure Boot場(chǎng)景下的簽名校驗(yàn)問(wèn)題現(xiàn)在不少服務(wù)器默認(rèn)開啟Secure Boot這種情況下直接加載未簽名的內(nèi)核鏡像會(huì)被固件攔截。使用傳統(tǒng)kexec -l通常會(huì)直接失敗或者雖然在加載階段通過(guò)了但在執(zhí)行切換時(shí)被固件中止。正確的做法是使用帶簽名的內(nèi)核鏡像并且使用kexec -s基于文件描述符的加載方式讓內(nèi)核自己去做簽名校驗(yàn)。如果你需要簽名的工具鏈可以在發(fā)行版?zhèn)}庫(kù)里找sbsigntool用自己的Machine Owner KeyMOK對(duì)內(nèi)核鏡像簽名。簽完名后加載時(shí)會(huì)比傳統(tǒng)方式稍微復(fù)雜一點(diǎn)但整體動(dòng)作還是那幾步。如果你只是為了在本地測(cè)試不想折騰簽名最簡(jiǎn)單的方案是在BIOS設(shè)置里臨時(shí)關(guān)閉Secure Boot。但要注意生產(chǎn)環(huán)境不建議為了圖省事關(guān)閉安全啟動(dòng)這會(huì)大大降低系統(tǒng)安全性。6.4 切換后網(wǎng)卡名變化導(dǎo)致網(wǎng)絡(luò)失聯(lián)的應(yīng)對(duì)這是一個(gè)非常實(shí)際的問(wèn)題。當(dāng)你從一個(gè)內(nèi)核切換到另一個(gè)內(nèi)核時(shí)如果新內(nèi)核使用的網(wǎng)絡(luò)驅(qū)動(dòng)命名規(guī)則不同比如從eth0變成了enp0s3你的網(wǎng)絡(luò)配置可能全部失效遠(yuǎn)程連接的機(jī)器會(huì)直接斷線。解決辦法是在新內(nèi)核的啟動(dòng)參數(shù)中固定網(wǎng)卡命名規(guī)則或者提前在initrd和系統(tǒng)配置中預(yù)置好新環(huán)境下的網(wǎng)絡(luò)配置。最簡(jiǎn)單粗暴的辦法是在切換前確保新內(nèi)核的驅(qū)動(dòng)模塊和udev規(guī)則和舊內(nèi)核一致。如果你用的是現(xiàn)代發(fā)行版通常系統(tǒng)的網(wǎng)絡(luò)管理服務(wù)會(huì)自動(dòng)處理網(wǎng)卡重命名但還是建議在切換前把網(wǎng)卡的配置文件檢查一遍。我這里還有一個(gè)實(shí)用的小技巧如果你必須遠(yuǎn)程操作一臺(tái)機(jī)器做kexec切換最好準(zhǔn)備一個(gè)IPMI或者帶外管理口。萬(wàn)一切換后網(wǎng)絡(luò)起不來(lái)你還能通過(guò)帶外方式恢復(fù)。這個(gè)教訓(xùn)我是付出過(guò)代價(jià)的在沒(méi)有任何帶外管理的情況下遠(yuǎn)程切換內(nèi)核結(jié)果只能跑去機(jī)房物理操作。6.5 內(nèi)存不足導(dǎo)致的加載失敗當(dāng)系統(tǒng)可用內(nèi)存非常緊張時(shí)kexec -l可能會(huì)報(bào)錯(cuò)Cannot allocate memory。這是因?yàn)閗exec需要一段連續(xù)的物理內(nèi)存來(lái)存放新內(nèi)核和initrd。系統(tǒng)內(nèi)存碎片化嚴(yán)重時(shí)即使總可用內(nèi)存還有不少也找不到足夠大的連續(xù)區(qū)域。這時(shí)有兩個(gè)常用手段。一是嘗試用--load配合指定的加載地址嘗試不同的內(nèi)存段。這種情況通常需要根據(jù)/proc/iomem里的內(nèi)存布局來(lái)選擇合適的區(qū)域。二是先用echo 3 /proc/sys/vm/drop_caches釋放頁(yè)緩存減少內(nèi)存碎片。如果這兩個(gè)辦法都不行那基本只能等一些內(nèi)存占用大戶退出后再嘗試。6.6 常見問(wèn)題速查表為方便快速定位問(wèn)題我把上面提到的典型問(wèn)題整理成一張速查表現(xiàn)象可能原因排查方向kexec -l 報(bào) Invalid file format內(nèi)核鏡像格式不被識(shí)別嘗試 kexec -s 走文件描述符方式加載時(shí)報(bào) Cannot allocate memory內(nèi)存碎片化或預(yù)留區(qū)域沖突釋放緩存或手動(dòng)指定加載地址kexec -e 后黑屏initrd缺失或驅(qū)動(dòng)模塊不完整檢查initrd內(nèi)容并重新生成Secure Boot攔截切換內(nèi)核鏡像未簽名使用簽名內(nèi)核或臨時(shí)關(guān)閉Secure Boot切換后網(wǎng)絡(luò)失聯(lián)網(wǎng)卡命名變化或驅(qū)動(dòng)缺失提前確認(rèn)新內(nèi)核網(wǎng)絡(luò)配置切換后系統(tǒng)反復(fù)重啟根文件系統(tǒng)參數(shù)不對(duì)檢查 command-line 里的 root 配置kdump轉(zhuǎn)儲(chǔ)失敗預(yù)留內(nèi)存不夠或模塊沖突調(diào)大 crashkernel 或精簡(jiǎn)initrd這張表不能覆蓋所有場(chǎng)景但常見的坑基本都在這了。7. kexec的進(jìn)一步擴(kuò)展與GRUB、systemd和容器的協(xié)同使用7.1 和GRUB引導(dǎo)流程的對(duì)比何時(shí)用kexec何時(shí)用傳統(tǒng)重啟有人可能會(huì)問(wèn)既然kexec這么方便為什么不把所有重啟都改成kexec答案在于kexec的適用范圍是有限的。傳統(tǒng)重啟的好處是徹底。硬件被完全重置、固件重新初始化能夠清掉很多潛在的不穩(wěn)定狀態(tài)。如果系統(tǒng)里某個(gè)硬件設(shè)備狀態(tài)異常傳統(tǒng)重啟很容易解決但kexec切換時(shí)硬件狀態(tài)是繼承的如果某個(gè)設(shè)備已經(jīng)處于不穩(wěn)定狀態(tài)新內(nèi)核也會(huì)跟著遭殃。所以我的經(jīng)驗(yàn)法則是日常內(nèi)核升級(jí)和版本切換用kexec能顯著縮短停機(jī)時(shí)間但如果系統(tǒng)出現(xiàn)莫名其妙的硬件異常、驅(qū)動(dòng)僵死、或者你要清理固件級(jí)別的異常狀態(tài)那就老老實(shí)實(shí)走完整重啟。不要迷信kexec它只是工具不是銀彈。7.2 systemd-kexec服務(wù)與內(nèi)核升級(jí)自動(dòng)化systemd提供了一套和kexec協(xié)同的機(jī)制你可以通過(guò)配置/etc/systemd/kexec.conf來(lái)預(yù)設(shè)kexec加載信息。每當(dāng)系統(tǒng)執(zhí)行systemctl kexec時(shí)systemd會(huì)根據(jù)配置完成加載并切換。同時(shí)在內(nèi)核升級(jí)自動(dòng)化方面有些發(fā)行版會(huì)在安裝新內(nèi)核包后自動(dòng)執(zhí)行kexec -l預(yù)加載新內(nèi)核。這樣用戶之后只需要執(zhí)行一條命令就能完成切換。Debian系的部分環(huán)境在安裝kexec-tools時(shí)會(huì)提示你配置這個(gè)行為。實(shí)際操作時(shí)我不太建議完全自動(dòng)化畢竟內(nèi)核切換是一件影響面很大的動(dòng)作提前預(yù)加載可以但真正的切換動(dòng)作還是應(yīng)該由人來(lái)控制至少得留一個(gè)確認(rèn)環(huán)節(jié)。7.3 kexec在容器和虛擬機(jī)快速迭代中的妙用在容器場(chǎng)景下kexec本身并不直接作用于容器但如果你是搞內(nèi)核開發(fā)的經(jīng)常需要測(cè)試新內(nèi)核在容器運(yùn)行時(shí)環(huán)境下的表現(xiàn)kexec就能幫上大忙。比如某公司在物理機(jī)上跑著一堆容器需要快速驗(yàn)證新內(nèi)核與容器運(yùn)行時(shí)是否兼容。如果走傳統(tǒng)重啟可能要等很多容器重新調(diào)度。用kexec提前加載好新內(nèi)核在維護(hù)窗口一鍵切換整體服務(wù)中斷時(shí)間能壓縮到一分鐘以內(nèi)。虛擬化場(chǎng)景下的思路也類似。某些虛擬化平臺(tái)支持guest內(nèi)部用kexec快速切換內(nèi)核宿主機(jī)層面無(wú)感知對(duì)業(yè)務(wù)影響極小。但需要注意虛擬化平臺(tái)的某些特性比如動(dòng)態(tài)內(nèi)存氣球、設(shè)備透?jìng)髟趉exec切換后可能無(wú)法保持原有狀態(tài)測(cè)試時(shí)一定要驗(yàn)證這些高級(jí)特性是否仍然正常工作。8. 實(shí)操記錄一次線上內(nèi)核升級(jí)的完整復(fù)盤最后分享一次我實(shí)際操作的案例。環(huán)境是一臺(tái)某公司的測(cè)試服務(wù)器跑著一個(gè)對(duì)網(wǎng)絡(luò)性能要求很高的應(yīng)用內(nèi)核版本從5.10升到5.15但那次升級(jí)不能走正規(guī)的重啟流程因?yàn)闃I(yè)務(wù)要求盡量減少中斷時(shí)間而完整的硬件自檢要花掉將近十分鐘。我的操作流程是這樣先把新內(nèi)核和initrd準(zhǔn)備好檢查/boot目錄下的文件無(wú)誤然后執(zhí)行kexec -l預(yù)加載加載時(shí)手動(dòng)指定了root設(shè)備和幾個(gè)必要的啟動(dòng)參數(shù)沒(méi)有用--reuse-cmdline因?yàn)榕f內(nèi)核的某些參數(shù)比如舊驅(qū)動(dòng)專用的配置在新內(nèi)核上已經(jīng)不適用。加載完成后我用kexec -e執(zhí)行切換整個(gè)切換過(guò)程只花了大概兩秒鐘系統(tǒng)直接進(jìn)入新內(nèi)核。新內(nèi)核啟動(dòng)后我第一時(shí)間檢查了/var/log/kern.log確認(rèn)沒(méi)有明顯的驅(qū)動(dòng)報(bào)錯(cuò)然后又檢查了網(wǎng)絡(luò)狀態(tài)和業(yè)務(wù)進(jìn)程的運(yùn)行情況一切正常。整個(gè)過(guò)程從發(fā)出切換指令到業(yè)務(wù)完全恢復(fù)用了不到三十秒。如果走傳統(tǒng)重啟這個(gè)數(shù)字至少要膨脹到十分鐘。再分享一個(gè)反面案例。另一次我圖省事沒(méi)有手動(dòng)指定啟動(dòng)參數(shù)直接用了--reuse-cmdline結(jié)果新內(nèi)核繼承了舊內(nèi)核里一個(gè)已經(jīng)廢棄的驅(qū)動(dòng)參數(shù)導(dǎo)致驅(qū)動(dòng)加載失敗網(wǎng)絡(luò)接口起不來(lái)最后只能跑機(jī)房處理。所以現(xiàn)在我在kexec -l這一步從來(lái)不敢偷懶啟動(dòng)參數(shù)寧可花兩分鐘仔細(xì)核對(duì)也不要切換后花半小時(shí)收拾爛攤子。這里給你一個(gè)操作清單可以當(dāng)作checklist用確認(rèn)新內(nèi)核和initrd文件存在且完整檢查當(dāng)前內(nèi)核啟動(dòng)參數(shù)決定復(fù)用還是手動(dòng)指定手動(dòng)指定參數(shù)時(shí)確認(rèn)root設(shè)備路徑正確確認(rèn)預(yù)留內(nèi)存區(qū)域kdump場(chǎng)景沒(méi)有被新參數(shù)覆蓋切換前保存所有重要數(shù)據(jù)通知業(yè)務(wù)方執(zhí)行kexec -l后用kexec --status檢查加載狀態(tài)一切就緒后執(zhí)行kexec -e或systemctl kexec切換完成后立刻檢查內(nèi)核日志和關(guān)鍵服務(wù)狀態(tài)9. 個(gè)人經(jīng)驗(yàn)與建議kexec雖好用但別忽視細(xì)節(jié)我用kexec已經(jīng)有比較長(zhǎng)的時(shí)間了從最開始單純?yōu)榱思涌熘貑⑺俣鹊胶髞?lái)結(jié)合kdump做崩潰分析再到線上內(nèi)核版本的低中斷升級(jí)這個(gè)工具確實(shí)幫了大忙。但我也想說(shuō)kexec不是一個(gè)可以完全無(wú)腦使用的黑魔法它要求使用者對(duì)內(nèi)核啟動(dòng)機(jī)制、內(nèi)存布局和硬件狀態(tài)都有足夠的理解。給剛接觸kexec的朋友幾個(gè)建議。第一先在虛擬機(jī)或者測(cè)試機(jī)上反復(fù)練習(xí)整個(gè)加載、切換、回退流程不要一上來(lái)就在生產(chǎn)環(huán)境上操作。第二切換前務(wù)必備份當(dāng)前內(nèi)核的啟動(dòng)參數(shù)和根文件系統(tǒng)配置一旦新內(nèi)核出了問(wèn)題至少還能用傳統(tǒng)方式引導(dǎo)回舊內(nèi)核。第三如果條件允許配合串口控制臺(tái)使用。串口能讓你在屏幕黑掉的情況下也能看到內(nèi)核啟動(dòng)日志這對(duì)排錯(cuò)來(lái)說(shuō)簡(jiǎn)直是無(wú)價(jià)之寶。最后再分享一個(gè)小技巧如果你需要頻繁在不同的內(nèi)核版本之間切換測(cè)試建議在磁盤上保留至少兩個(gè)可引導(dǎo)的內(nèi)核版本并分別用GRUB菜單項(xiàng)固定好。這樣一來(lái)即使kexec切換失敗你重啟后還能通過(guò)GRUB選擇器回到原來(lái)的內(nèi)核。生產(chǎn)環(huán)境里的安全網(wǎng)越多越好這個(gè)習(xí)慣能幫你避免很多低級(jí)事故。