LWN:Ubuntu內(nèi)核bug導(dǎo)致容器崩潰!
關(guān)注了就能看到更多這么棒的文章哦~
An Ubuntu kernel bug causes container crashes
July 5, 2022
This article was contributed by Jordan Webb
DeepL assisted translation
https://lwn.net/Articles/899420/
一些運(yùn)行 Ubuntu 20.04 的系統(tǒng)管理員在 6 月 8 日的時(shí)候度日如年,當(dāng)時(shí) Ubuntu 發(fā)布了包含一個(gè)特別讓人討厭的 bug 的內(nèi)核 package,這個(gè) bug 是由一個(gè) Ubuntu 專用的內(nèi)核 patch 引起的。這個(gè)錯(cuò)誤導(dǎo)致在啟動(dòng) Docker 容器時(shí)出現(xiàn) kernel panic。相關(guān)的 fix 包于 6 月 10 日發(fā)布,但人們對(duì)處理該 patch 時(shí)出現(xiàn)的問(wèn)題疑惑;具體來(lái)說(shuō),人們都沒(méi)有想到,已經(jīng)超過(guò)使用壽命好幾個(gè)月的的內(nèi)核 5.13 竟然出現(xiàn)在運(yùn)行 Ubuntu 20.04 的機(jī)器上,而 20.04 本應(yīng)是一個(gè)長(zhǎng)期支持版本。
Ubuntu's kernel release lifecycle
除非是采用滾動(dòng)發(fā)布模式,否則 Linux 發(fā)行項(xiàng)目通常都會(huì)選擇固定在一個(gè)內(nèi)核分支上,并在發(fā)行版的生命周期內(nèi)堅(jiān)持使用它。例如,像 Ubuntu 20.04 那樣搭載 5.4 內(nèi)核的發(fā)行版,可能會(huì)收到來(lái)自后續(xù) 5.4.x 內(nèi)核的更新,但在發(fā)行版重新發(fā)布新的主要版本之前,不太可能會(huì)升級(jí)到 5.15。出于這個(gè)原因,這類項(xiàng)目通常喜歡甚至是迫切需要由 stable kernel 團(tuán)隊(duì)來(lái)指定為長(zhǎng)期維護(hù)的一個(gè) branch。對(duì)發(fā)行版的維護(hù)者來(lái)說(shuō),能確定他們所發(fā)布的軟件版本在上游是有支持的,那么晚上就更容易入睡。
Debian 和 Red Hat 在為他們的發(fā)行版挑選內(nèi)核時(shí)都遵守了這些規(guī)則。Ubuntu 聲稱也是如此,至少在其 LTS(長(zhǎng)期支持)版本中是這樣做的。這些版本每?jī)赡臧l(fā)布一次,并會(huì)支持五年。這些版本都使用一個(gè) long-term stable kernel;Ubuntu 版本在有效期內(nèi)會(huì)提供更新。
在 Ubuntu 的兩個(gè) LTS 版本之間,Ubuntu 也會(huì)以每六個(gè)月一次的時(shí)間間隔來(lái)發(fā)布非 LTS 版本。與 LTS 版本相比,這些版本只會(huì)支持 6-9 個(gè)月,并在新版本發(fā)布后的 3 個(gè)月內(nèi)就會(huì)被宣布終止使用(EOL)。由于它們的保質(zhì)期相對(duì)較短,Ubuntu 并不會(huì)確保在這些版本中使用 long-term kernel。最近發(fā)布的非 LTS 版本的 Ubuntu 21.10 中就搭載了 Linux 5.13,這不是一個(gè)長(zhǎng)期分支。事實(shí)上,5.13 分支在 2021 年 9 月 18 日被宣布為 EOL,甚至比 Ubuntu 21.10 在 10 月 14 日發(fā)布的日子還要早一個(gè)月。
優(yōu)先考慮穩(wěn)定性的用戶會(huì)高度重視 LTS 版本帶來(lái)的這個(gè)長(zhǎng)期支持窗口期,但在硬件世界中,五年都快相當(dāng)于是永恒了,尤其是在像 graphics 這樣快速發(fā)展的領(lǐng)域。為了支持更加新的硬件,Ubuntu 定期為其 LTS 版本發(fā)布新的硬件支持(HWE, hardware enablement)軟件棧。這些都是從最新(可能是非 LTS)版本中移植回來(lái)的軟件包所組成的。HWE 棧包括更新過(guò)的內(nèi)核包,也可能包括更新了的 Xorg 和 Mesa 包。
根據(jù) Ubuntu 的說(shuō)法,在 Ubuntu 的新桌面版本中,HWE 棧是默認(rèn)啟用的,但在服務(wù)器安裝中則需要明確選擇才會(huì)開(kāi)。這種選擇政策似乎也只適用于從 ISO 鏡像安裝的用戶;亞馬遜 AWS、Azure 和谷歌云上的默認(rèn) Ubuntu 20.04 鏡像都預(yù)裝了 HWE 內(nèi)核。許多系統(tǒng)管理員(包括我)也為他們的服務(wù)器選擇了 HWE 棧,要么是因?yàn)榭释媚切┲挥休^新內(nèi)核才有的功能,要么是需要一個(gè)能與他們的硬件配合工作的內(nèi)核。
如果單獨(dú)考慮這些決策時(shí),在非 LTS 版本中不要求使用 long-term kernel 的決定以及發(fā)布 HWE 內(nèi)核來(lái)擴(kuò)展 LTS 版本的硬件支持的決定都顯得很合理。但是,這兩個(gè)決定結(jié)合起來(lái)會(huì)導(dǎo)致一個(gè)有點(diǎn)令人吃驚的情況;運(yùn)行 "長(zhǎng)期支持 "發(fā)行版的用戶可能最終運(yùn)行一個(gè)被內(nèi)核開(kāi)發(fā)者認(rèn)為生命周期已經(jīng)終止的 Linux 版本。
截至目前,在 Ubuntu 20.04 上運(yùn)行 HWE 內(nèi)核的用戶會(huì)開(kāi)始使用一個(gè)從 21.10 backport 過(guò)來(lái)的 5.13 內(nèi)核。Ubuntu 22.04,也就是下一個(gè) LTS 版本,包括了 5.15 內(nèi)核,這是一個(gè)長(zhǎng)期穩(wěn)定的分支。目前 20.04 的用戶可以使用這個(gè)名字 "hwe-20.04-edge"。據(jù)推測(cè),它將在 7 月 14 日之前的某個(gè)時(shí)候取代 21.10 的內(nèi)核,即 hwe-20.04,屆時(shí) Ubuntu 21.10 本身就會(huì)超出服務(wù)期了。不過(guò)現(xiàn)在,以及在過(guò)去的幾個(gè)月,任何在 20.04 上運(yùn)行 HWE 內(nèi)核的人都在運(yùn)行一個(gè)基于 5.13 的內(nèi)核。由于 HWE 內(nèi)核是所有三個(gè)主要云服務(wù)商默認(rèn)安裝的內(nèi)核,那么它的問(wèn)題會(huì)影響到 Ubuntu 的一大部分用戶。
A tale of four filesystems
HWE 內(nèi)核允許在 Ubuntu LTS 上使用較新的硬件和內(nèi)核功能,但似乎這可能會(huì)在穩(wěn)定性方面帶來(lái)一些代價(jià)。內(nèi)核 crash 的根本原因在于有四種文件系統(tǒng)之間的共同作用,盡管它們都不是傳統(tǒng)意義上的文件系統(tǒng)(即把數(shù)據(jù)寫入持久性存儲(chǔ)的東西)。
第一個(gè)是 overlayfs。顧名思義,overlayfs 允許將一個(gè)目錄(用 overlayfs 的說(shuō)法是 "upper" 目錄)中的文件來(lái)覆蓋在另一個(gè)目錄("lower" 目錄)中的文件之上。這導(dǎo)致這個(gè) mount 點(diǎn)其實(shí)包含了上層和下層目錄中所有文件;如果兩個(gè)目錄都包含一個(gè)同名的文件,overlayfs 會(huì)顯示上層目錄中的這個(gè)版本。對(duì) overlayfs mount 進(jìn)行改變都會(huì)反映在上層目錄中。overlayfs 提供的功能對(duì) Docker 這樣的容器運(yùn)行時(shí)系統(tǒng)(container runtime)特別有價(jià)值,它將容器鏡像文件存儲(chǔ)為一系列的 layer;overlayfs 提供了一種有效的方法,可以從這些 layer 中來(lái)構(gòu)建出 container 的根目錄。從 2014 年的 3.18 版本起,這個(gè)功能已經(jīng)成為內(nèi)核的一部分。
第二個(gè)文件系統(tǒng)是 AUFS,它做了 overlayfs 所做的一切,甚至更多,但它的實(shí)現(xiàn)明顯更復(fù)雜。AUFS 的代碼量約為 35000 行,而 overlayfs 約為 12000 行。AUFS 在 2008 年首次被提交到內(nèi)核中,但從未被合并進(jìn)來(lái);從那時(shí)起就一直在 kernel tree 外維護(hù)著。Ubuntu 在其 20.10 版本的內(nèi)核中包含了 AUFS,但在 21.04 中放棄了它。
第三個(gè)文件系統(tǒng)是 shiftfs,它最初是由 James Bottomley 在 2018 年創(chuàng)建的,允許重新映射 mount 文件系統(tǒng)中的用戶和組 ID,雖然它從未被合并到上游,但自 5.0 內(nèi)核系列以來(lái),它一直被包含在 Ubuntu 的代碼中。Canonical 的 LXD 項(xiàng)目可以使用 shiftfs 來(lái)加速非特權(quán)容器的創(chuàng)建動(dòng)作,容器內(nèi)的 root 用戶被映射到容器外的 root 用戶;否則,文件系統(tǒng)需要改變其用戶和組 ID 才能實(shí)現(xiàn)這個(gè)效果。不過(guò) shiftfs 不太可能出現(xiàn)在 Linus Torvalds 的代碼中,因?yàn)樗墓δ芡耆?5.12 版本的內(nèi)核中加入的 ID-mapped mounts 替代了。LXD 后來(lái)也被修改為使用 ID-mapped mounts。
我們故事中的第四個(gè)也是最后一個(gè)文件系統(tǒng)是 procfs。眾所周知,在 Linux 系統(tǒng)上運(yùn)行的每個(gè)進(jìn)程在/proc 中有一個(gè)相應(yīng)的目錄。這些目錄中的每一個(gè)都包含了一個(gè)名為 map_files 的子目錄,它有一系列的符號(hào)鏈接。每個(gè)鏈接都對(duì)應(yīng)了進(jìn)程地址空間中被映射到一個(gè)文件的地址范圍;每個(gè)鏈接的名稱表示被映射的地址范圍,而目標(biāo)是被映射到該范圍的文件。比如說(shuō)
$ ls -l /proc/$$/map_files/
total 0lr-------- 1 jordan everybody 64 Jun 22 16:21 55e0cc120000-55e0cc14d000 -> /usr/bin/bash
lr-------- 1 jordan everybody 64 Jun 22 16:21 55e0cc14d000-55e0cc1fe000 -> /usr/bin/bash
...
map_files 子目錄最主要的使用者可能是 Checkpoint/Restore In Userspace(CRIU)工具,它可以通過(guò)將進(jìn)程的整個(gè)狀態(tài)序列化存儲(chǔ)到磁盤來(lái)作為 "checkpoint",然后從這些序列化的狀態(tài)來(lái)重新創(chuàng)建進(jìn)程來(lái) "恢復(fù) "它。
What does this patch 多?
在創(chuàng)建 Docker 容器時(shí)引起內(nèi)核 panic 的補(bǔ)丁是為了糾正在一起使用 overlayfs 和 shiftfs 時(shí)的一個(gè)問(wèn)題。如果一個(gè)進(jìn)程從這樣的 mount 中 map 一個(gè)文件,map_files 中的符號(hào)鏈接會(huì)指向該文件的原始 "unshifted" 版本,而不是 shiftfs mount 中的路徑。這就破壞了 checkpoint 和 restore Docker 容器的功能,因?yàn)?map_files 中的符號(hào)鏈接所指向的文件是在容器內(nèi)沒(méi)有安裝的文件系統(tǒng)。
這個(gè)問(wèn)題在 2020 年初被發(fā)現(xiàn),并在 Ubuntu 20.04 發(fā)布后不久被修復(fù)。當(dāng)時(shí),AUFS 被包含在 Ubuntu 的內(nèi)核中。AUFS 的開(kāi)發(fā)者還面臨著一個(gè)挑戰(zhàn),就是如何區(qū)分文件的真實(shí)名稱以及 AUFS mount 內(nèi)的別名。為了解決這個(gè)問(wèn)題,AUFS 補(bǔ)丁在內(nèi)核的 vm_area_struct 中引入了一個(gè)叫做 vm_prfile 的額外字段,該字段被填寫為 AUFS 的文件名。為了解決 overlayfs 和 shiftfs 的問(wèn)題,Ubuntu 的開(kāi)發(fā)者需要在這種復(fù)合 mount 中跟蹤文件的別名,而且,由于 AUFS 已經(jīng)為類似的目的添加了 vm_prfile,他們就選擇重新使這個(gè)字段,而不是再加一個(gè)。他們知道他們的 fix 方法依賴于 AUFS 的啟用,所以他們也選擇在#ifdef 塊中保護(hù)一下,如果 AUFS 沒(méi)有被配置到內(nèi)核中,那么這個(gè) patch 就沒(méi)有用了。
How things went wrong
當(dāng) Ubuntu 的開(kāi)發(fā)者將 5.8 內(nèi)核分支的 shiftfs 相關(guān) patch 移植到 5.13 和 5.15 內(nèi)核時(shí),修正 map_files 和 shiftfs 問(wèn)題的補(bǔ)丁被遺漏了,因?yàn)樗蕾囉?AUFS,而 AUFS 已經(jīng)從 Ubuntu 的內(nèi)核中刪除。當(dāng)這些 kernel 被移植回 Ubuntu 20.04 時(shí),AUFS 繼續(xù)被支持,缺失的 patch 就被注意到了,同時(shí)也就用在了 Ubuntu 的 5.13 和 5.15 樹(shù)上。
不幸的是,overlayfs 的內(nèi)部結(jié)構(gòu)隨著時(shí)間的推移發(fā)生了變化,最終導(dǎo)致該 patch 不再正確了。結(jié)果,當(dāng) overlayfs 上的文件被映射到內(nèi)存中時(shí),patch 添加的函數(shù)試圖用 fput() 來(lái)釋放對(duì)結(jié)構(gòu)文件的引用,但由于先前的 fput()調(diào)用使得該結(jié)構(gòu)已經(jīng)被釋放了。這導(dǎo)致了內(nèi)核的 panic。
在 Ubuntu 21.10 上,5.13 是默認(rèn)內(nèi)核,這并沒(méi)有引起任何問(wèn)題。由于 AUFS 沒(méi)有被啟用,patch 引入的代碼周圍的 #ifdef 阻止了它被編譯進(jìn)內(nèi)核。這個(gè)問(wèn)題在為 Ubuntu 20.04 重建 5.13 和 5.15 時(shí)出現(xiàn)。由于 HWE 內(nèi)核需要支持它所取代的內(nèi)核所支持的所有功能,所以在這些構(gòu)建中啟用了 AUFS,包含了多余的 fput()的代碼就被編譯進(jìn)去了。
這個(gè)問(wèn)題是在 5 月份被注意到的,幾乎是在 patch 被重新加入后就立即發(fā)現(xiàn)了。然而,5.13 似乎被忽略了;該 patch 在 Ubuntu 的 5.15 branch 中被 revert 掉了,并被替換為不調(diào)用 fput() 的版本,但這個(gè)不正確的版本仍留在 5.13 branch 中,并進(jìn)入了 5.13 HWE 內(nèi)核。
根據(jù) changelog,有問(wèn)題的內(nèi)核包是在 6 月 3 日構(gòu)建的,盡管它在之后的一段時(shí)間內(nèi)可能沒(méi)有被發(fā)布到 Ubuntu 的軟件包庫(kù)中。這個(gè)問(wèn)題在 6 月 8 日被報(bào)告出來(lái)。在 6 月 10 日提供更新的軟件包之前,受影響的用戶唯一可用的解決辦法是手動(dòng)回滾到以前的內(nèi)核。
Conclusion
無(wú)論在什么時(shí)候,只要是維護(hù)一個(gè)樹(shù)外的內(nèi)核 patch 都是一項(xiàng)艱巨的任務(wù)。在 Linux 對(duì)用戶空間的兼容性提供鋼鐵一般的保證的同時(shí),它對(duì)不同版本的內(nèi)部?jī)?nèi)核接口穩(wěn)定方面是完全不提供保證的。由于要跟上內(nèi)核中其他地方的變動(dòng),那些沒(méi)有被合并的代碼往往很快就被淘汰了。
當(dāng) Ubuntu 在其 LTS 版本中發(fā)布樹(shù)外補(bǔ)丁時(shí),就意味著它的內(nèi)核開(kāi)發(fā)者要對(duì)這些 patch 進(jìn)行至少五年的維護(hù),而且通常是需要在內(nèi)核的多個(gè)分支中同時(shí)進(jìn)行的。有時(shí),這些賭注會(huì)得到回報(bào);Ubuntu 在合并前就將 overlayfs 納入了自己的內(nèi)核,現(xiàn)在它被上游維護(hù)了。另一方面,盡管 Ubuntu 在 2021 年放棄了對(duì) AUFS 的支持,但由于該發(fā)行版在 20.04 中提供了 AUFS,所以他們?cè)?2025 年之前都要繼續(xù)支持 AUFS。他們最新的 LTS 版本,22.04,仍然包含了對(duì) shiftfs 的支持;這些 patch 將在 Ubuntu 的代碼樹(shù)中持續(xù)存在至少到 2027 年。正如這個(gè) patch 的問(wèn)題所表明的,保持這些 patch 的更新不是簡(jiǎn)單的工作;內(nèi)核其他部分的變化可能而且必定會(huì)導(dǎo)致問(wèn)題,這些需要非常仔細(xì)地砍進(jìn)去。
根據(jù)這些時(shí)間表,對(duì)于 Ubuntu 的內(nèi)核開(kāi)發(fā)者來(lái)說(shuō),事情似乎不會(huì)在短時(shí)間內(nèi)變得簡(jiǎn)單。事實(shí)上,注定會(huì)變得更難;由于內(nèi)核現(xiàn)在提供了類似的功能,人們對(duì)這些 kernel tree 外的替代品的興趣可能會(huì)減弱,這將把維護(hù)的負(fù)擔(dān)更多地放在 Ubuntu 的肩上。沒(méi)有回報(bào)的賭注就會(huì)變成債務(wù),并產(chǎn)生復(fù)利,造成持續(xù)的損失。
最后,Ubuntu 似乎至少在某種程度上成為了自己造成的復(fù)雜性的受害者。Ubuntu 的開(kāi)發(fā)者很快發(fā)現(xiàn)并 fix 了這個(gè)問(wèn)題,但只是在受影響的一個(gè) branch 中完成了 fix。不幸的是,被遺漏的那個(gè) branch 就是被發(fā)布給用戶的那個(gè)。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長(zhǎng)按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開(kāi)源社區(qū)的各種新近言論~
