伺服器神秘掛起:一場驚心動魄的核心探案

米开朗基杨發表於2024-06-25

2024年6月17日,我們的運維團隊突然收到了一連串的告警。監控大屏上,代表著不同 Sealos 可用區的綠點中,零星地閃爍起了一兩個紅點。

“奇怪,怎麼有幾臺伺服器突然 hang 住了?” 值班的小輝皺起了眉頭。

這次故障的詭異之處在於它的隨機性。並非所有節點都受到影響,而是在不同可用區中,時不時地有個別伺服器突然 “失聯”。更令人不解的是,這些突然 hang 住的伺服器資源都很充足,CPU、記憶體、磁碟 IO 等各項指標都處於健康水平。

“正常情況下它們不是都會自愈麼?” 小謝問道。

“不會,我們已經觀察了10分鐘,還是沒有恢復正常。” 小楊搖搖頭,“這不太正常。”

常規手段失效,真兇難尋

面對這種間歇性但嚴重的故障,我們立即啟動了應急預案。首先,我們祭出了我們的 “傳家寶”——dmesg 命令,希望能從系統日誌中找到一些蛛絲馬跡。

然而,這次 dmesg 沒有給我們任何有價值的資訊。日誌中看不到任何異常,伺服器看起來完全正常,太詭異了。我們對比了之前遇到的所有已知問題,發現這次的情況與以往都不同,問題主要發生在北京廣州節點,但發生的機率較低,這更增加了排查的難度。

我們繼續深入排查,檢查了網路連線、系統負載、程序狀態等多個方面,但所有指標都顯示正常。

“等等,我發現了一個共同點!” 小李突然喊道,“所有出問題的節點,核心版本都是 5.15.0-91-generic!

這個發現讓我們眼前一亮,難道真兇就藏在這個特定的核心版本中?

意外的 “核心恐慌”

為了進一步確認猜測,我們聯絡了雲服務提供商的技術支援團隊。在他們的協助下,我們終於在串列埠日誌中發現了關鍵線索。

就在系統 hang 住之前,日誌中列印了一段特殊的堆疊資訊:

這段堆疊資訊清楚地顯示,系統觸發了一個核心 bug,導致了嚴重的核心恐慌。正常情況下,當發生核心 panic 時,系統應該會崩潰並進入 kdump 流程,將 Vmcore 寫入磁碟,然後重新啟動。但是,我們的系統卻陷入了持續的 hang 狀態,這顯然不太對勁。

進一步分析 kdump 的日誌,我們發現了問題的真相:

原來,kdump 在啟動第二核心時出現了異常。我們懷疑,這可能是由於 crashkernel 配置的記憶體不足,導致第二核心啟動失敗,系統最終陷入了 hang 狀態。

好傢伙,這不就像是消防車在趕往火災現場的路上自己也出了故障。。。

核心升級大法

既然找到了問題的根源,接下來就是對症下藥的時候了。我們的解決方案很直接——升級核心到已經修復了這個 bug 的最新版本:

apt install linux-image-5.15.0-112-generic

然而,事情並沒有像我們預想的那樣一帆風順。在升級核心後的恢復過程中,我們又遇到了一個新的挑戰——Cilium (我們使用的網路方案) 開始頻繁重啟,並報出了這樣的錯誤:

Cilium 的小插曲

仔細檢視 Cilium 的錯誤日誌,我們發現問題出在一個叫做 “kube-ipvs0” 的網路介面上。這個 “kube-ipvs0” 裝置其實是 Kubernetes 的 kube-proxy 元件在使用 IPVS 模式時建立的。

等等,我們的叢集不是已經不再使用 kube-proxy 了嗎?

經過一番排查後發現,原來在我們遷移到 Cilium 網路方案的過程中,忘記了清理這個遺留的網路介面。就是這個小小的疏忽導致了 Cilium 無法正確獲取節點上的 IPv4 地址,進而引發了我們遇到的錯誤。

Cilium 原始碼中的相關片段:

// https://github.com/cilium/cilium/blob/8c7e442ccd48b9011a10f34a128ec98751d9a80e/pkg/datapath/loader/loader.go#L183
if option.Config.EnableIPv4Masquerade && bpfMasqIPv4Addrs != nil {
    ipv4 := bpfMasqIPv4Addrs[ifName] // nil checking is missing here
    opts["IPV4_MASQUERADE"] = uint64(byteorder.NetIPv4ToHost32(ipv4)) // ipv4 is nil
}

這段程式碼中,Cilium 嘗試獲取網路裝置的 IPv4 地址,但是由於 “kube-ipvs0” 裝置並不包含有效的 IP 地址,導致了空指標異常,最終就會導致 ip 進行型別轉換時溢位:

func NetIPv4ToHost32(ip net.IP) uint32 {
        ipv4 := ip.To4()
        _ = ipv4[3] // Assert length of ipv4.
        return Native.Uint32(ipv4)
}

參考 issue:https://github.com/cilium/cilium/issues/30746

解決方法很簡單:刪除這個多餘的網路介面。

ip link delete kube-ipvs0

完成這個操作後,Cilium 終於恢復了正常,我們的服務也重新上線了。

總結

這次的故障給我們上了很重要的一課:要保持警惕,不放過任何細節。

即使是一個看似無害的遺留網路裝置,也可能成為系統穩定性的隱患。在進行重大架構變更時,我們需要更加細緻地清理舊的元件和配置。

相關文章