數千台設備的軟件和操作系統,每兩周升級一(yī)次,這是怎麽做到的

2019-09-30

1000 (1).jpg


任何曾經管理過幾十上百台物(wù)理服務器的人都知(zhī)道:确保所有服務器始終安裝最新安全更新,或者保證所有服務器的配置和狀态相一(yī)緻,這始終是一(yī)件很難完成的任務。爲了解決這個問題,系統管理員(yuán)通常會使用 Puppet 、 Salt 等工(gōng)具,或将應用程序部署到容器中(zhōng)。如果整個環境都能由你控制,這些當然都是很棒的方法,但如果你使用了類似 BCDR 一(yī)體(tǐ)機之類的設備(或者任何未部署在自己基礎架構内的一(yī)體(tǐ)機 / 服務器設施),這些方法往往就不怎麽實用了。除此之外(wài),替換系統内核、安裝大(dà)型系統升級,或安裝其他需要重啓的大(dà)型補丁,此時也無法适用這些方法。

當我(wǒ)們使用的 BCDR 設備面臨這些問題後,我(wǒ)們開(kāi)始尋找其他更可行的方法,并且真的有所收獲。近兩年來,我(wǒ)們爲超過 80,000 台設備使用了這種方法,效果一(yī)直很穩定。本文我(wǒ)将談談我(wǒ)們是如何通過鏡像、回環設備(Loop device)以及大(dà)量和 Grub 有關的“魔法”解決這個問題的。如果對此話(huà)題感興趣,歡迎繼續閱讀下(xià)去(qù)。

1

從頭到尾使用 Debian 軟件包?

我(wǒ)們的 BCDR 一(yī)體(tǐ)機始終運行了 Ubuntu,因此在更新軟件時,最自然的方法就是使用 Debian 軟件包。過去(qù)很長時間以來都是這樣做的:每兩周,我(wǒ)們會爲 Ubuntu 10.04/12.04(沒錯,我(wǒ)知(zhī)道你有疑問,請繼續讀下(xià)去(qù)!)構建所需的發布,經過全面測試後将其正式部署出去(qù)。

很長時間以來這樣做完全沒問題,但這種做法有一(yī)些很明顯的不足之處:

第三方依賴項的更新:使用少量 Debian 軟件包,這容易讓人覺得隻需要爲自己的軟件負責,而不需要爲一(yī)體(tǐ)機中(zhōng)運行的其他軟件負責。如果你隻使用了自己的 datto.deb,但此時 Apache、Samba、libc 甚至 PHP 的更新管理工(gōng)作其實同樣重要。鑒于我(wǒ)們作爲 Datto,本身所銷售的就是完整的一(yī)體(tǐ)機,當然也就需要負責管理整個棧,尤其是第三方軟件的安全補丁等内容。

服務重啓動 / 重引導:對于一(yī)些需要重啓動服務甚至重引導計算機的大(dà)型更新,依賴項問題也會變得異常棘手。當然,Debian 軟件包自己就應該能處理服務重啓動問題,但實際上并非所有軟件包都能妥善搞定。并且一(yī)旦需要重引導(例如需要升級系統内核),還需要确保不會打斷重要的設備任務(例如備份、虛拟化……),此外(wài)還要保證設備最終能引導成功(這事情并不像你想的那麽容易,下(xià)文将會詳細介紹!)。

發行版升級:如果整個操作系統的版本需要升級,這才是最麻煩的地方。舉例來說,如果隻使用 apt-get dist-upgrade 命令以及 reboot 命令将 Ubuntu 10.04 升級到 16.04,整個過程将變得漫長無比,并且很多時候可能會升級失敗(隻要你用過 usedapt-get dist-upgrade,那麽肯定會明白(bái))。

數千個版本和狀态:在 Debian 的升級模型中(zhōng),“設備”的實際行爲其實和普通計算機無異:剛剛創建好鏡像并部署後,一(yī)切都是嶄新的,一(yī)切都可以正常運轉。但随着鏡像越來越老,操作系統退化的問題就變得越嚴峻,導緻不同設備的狀态産生(shēng)巨大(dà)差異。能嚴重到什麽程度?我(wǒ)們的設備(在切換到 KVM 前)曾經使用了 40 個不同版本的 VirtualBox、25 個不同的 ZFS 版本,以及超過 80 個不同的 Linux 内核!

其實,實際遇到的問題遠比上面列出的更多,不過這裏就不拿更多問題來給大(dà)家添堵了。快速開(kāi)始介紹最有趣的内容:如何解決!

2

使用鏡像,而非軟件包!

鑒于會遇到這麽多問題,很明顯,我(wǒ)們需要用更好的解決方案來管理設備狀态和配置。産品中(zhōng)不同的設備配置 / 軟件包 / 版本數量不僅要降至最低,并且在每次升級時必需能保證能夠升級整個棧:不僅要能升級我(wǒ)們自己的軟件,還要能升級第三方軟件,甚至諸如 Libc 或系統内核等系統庫。

3

前提要求

随後我(wǒ)們開(kāi)始确定這個解決方案的前提要求,其實這些要求并不多:

所有設備沿用相同的升級路徑,并且隻存在一(yī)個升級路徑。

所有設備均可通過這種方式升級(哪怕操作系統盤較小(xiǎo)的老設備)。

從一(yī)個版本切換到另一(yī)個版本的過程必需滿足原子性要求(或盡可能滿足這種要求)。(如果升級失敗)能夠回滾到上一(yī)個版本。而這些要求還暗含了一(yī)個最重要的前提條件:不能繼續使用基于軟件包的升級方法了,并且(從字裏行間也能體(tǐ)會到)在升級過程中(zhōng)重引導一(yī)體(tǐ)機,這是可以接受的。

這些都是很大(dà)膽的念頭。我(wǒ)們确實做出了一(yī)個重大(dà)決定!

4

那麽鏡像到底是什麽?

爲了減少配置的數量,我(wǒ)們決定不再将我(wǒ)們的軟件及其所有依賴項看作不同個體(tǐ),而是将所有這一(yī)切組合成一(yī)個統一(yī)的可交付物(wù):鏡像。

那麽鏡像到底是什麽?鏡像(在我(wǒ)們的環境中(zhōng))是指一(yī)種 EXT4 文件系統,其中(zhōng)包含了引導和運行 BCDR 一(yī)體(tǐ)機所需的一(yī)切,例如:

Ubuntu 基礎操作系統(内核、系統庫……)

必需的第三方工(gōng)具和庫(Apache、KVM、ZFS……)

Datto 設備軟件(我(wǒ)們的營銷團隊将其稱之爲 IRIS)

下(xià)圖就顯示了一(yī)個這種鏡像所包含的内容:


我(wǒ)們對這種想法非常激動,因爲通過使用鏡像,隻需要一(yī)個數字,也就是鏡像的版本号(例如上圖中(zhōng)的“415”)就可以定義所安裝的每個軟件的具體(tǐ)版本。再也不用針對多種 ZFS 版本測試我(wǒ)們的軟件,更不用暗自祈禱我(wǒ)們的軟件能兼容所有 KVM 版本。太棒了!

5

基于鏡像的升級

做出所有這些重要決定後,我(wǒ)們依然需要通過某種方法來構建、分(fēn)發,并在設備上引導這些鏡像。具體(tǐ)怎麽做呢?

構建鏡像

通常來說,每次标記了一(yī)個新的發布(或發布候選)後,我(wǒ)們會自動構建鏡像:每次在 Git 中(zhōng)推送标簽後,一(yī)個 CI 工(gōng)作進程會開(kāi)始構建鏡像。構建過程本身也挺有趣,不過已經超出了本文的範圍,但爲了不吊大(dà)家胃口,下(xià)文将簡單介紹這個過程:

我(wǒ)們首先會爲自己的軟件構建 Debian 軟件包,并将其發布至一(yī)個 Debian 倉庫。随後使用 aptly (參閱“Datto packages”一(yī)圖)爲這個 Debian 倉庫創建快照,同時還會定期對一(yī)個上遊 Ubuntu 倉庫(“Upstream packages”)執行類似操作。随後使用 debootstrap 創建一(yī)個 Ubuntu 基準系統,并将我(wǒ)們的所有軟件及其依賴項安裝到一(yī)個 Chroot 中(zhōng)。一(yī)旦完成這些操作,會對其創建 Tar 歸檔并 Rsync 到我(wǒ)們自己的鏡像服務器。在鏡像服務器上,我(wǒ)們會提取出 Tarball 并 Rsync 給最新鏡像,這個最新鏡像位于一(yī)個格式化爲 EXT4 文件系統的 ZFS 卷(zvol)中(zhōng)。在将所有未使用的 EXT4 塊歸零後,會對包含該文件系統的 zvol 創建最終快照。

因此在鏡像服務器上可以看到類似下(xià)圖所示的内容:

上述 zvol 包含了我(wǒ)們 BCDR 一(yī)體(tǐ)機的 EXT4 文件系統。這就是一(yī)個鏡像,也是我(wǒ)們唯一(yī)需要交付的東西。它可以作爲一(yī)個整體(tǐ)進行測試,一(yī)旦通過了 QA 流程,就可以分(fēn)發到客戶的 BCDR 設備中(zhōng)了。

分(fēn)發鏡像

在成功構建鏡像後,又(yòu)該如何将其從我(wǒ)們的數據中(zhōng)心發送給超過 8 萬台設備?很簡單,我(wǒ)們使用了 ZFS send/recv !

我(wǒ)們的所有設備都具備 ZFS 池,其中(zhōng)存儲了設備的鏡像備份,并且之前我(wǒ)們就在大(dà)量使用 ZFS send/recv 爲這些備份提供離(lí)場保存能力。而此時隻不過是換種方向使用這種技術。

我(wǒ)們是這樣做的:需要升級時,會讓一(yī)部設備通過 HTTPS 下(xià)載 ZFS sendfile diff(之前曾經嘗試過直接通過 SSH 使用 ZFS send/recv,但這種方式無法進行緩存):

從上圖中(zhōng)可以看到,通常并不需要下(xià)載完整鏡像,因爲設備以前就升級過,已經在本地池中(zhōng)保存了鏡像的一(yī)個版本。這就很棒了:通過這種技術,我(wǒ)們可以進行差異化的操作系統升級,也就是說,設備隻需要下(xià)載鏡像中(zhōng)有變化的塊。

這是一(yī)種雙赢的結果,因爲不會過多占用客戶網絡帶寬,而我(wǒ)們自己的數據中(zhōng)心也可以節約一(yī)筆帶寬費(fèi)用。

下(xià)載好的鏡像會被導入本地 ZFS 池。這對于下(xià)一(yī)次升級很必要(可以确保隻需要下(xià)載有變化的内容):

引導鏡像

拿到鏡像後,如何引導至這個新的文件系統?如果我(wǒ)們構建的每個鏡像版本都是全新操作系統,又(yòu)該如何從一(yī)個版本引導至下(xià)一(yī)個版本?

6

ZFS-on-root、A/B 分(fēn)區和 A/B 文件夾

毫無疑問,這些問題的答案并不隻有一(yī)種。我(wǒ)們可以通過多種方法使用鏡像生(shēng)成可引導的系統,因此需要多次實驗找出一(yī)種最佳方法。

這個過程也很有趣,因此我(wǒ)準備簡要介紹每種方法,以及最終未選擇這些方法的原因:

ZFS-on-root 和 A/B 數據集:我(wǒ)們的鏡像備份操作中(zhōng)大(dà)量使用了 ZFS,因此一(yī)開(kāi)始很自然就覺得也可以将 ZFS 用作一(yī)體(tǐ)機的根文件系統。爲此可以将 BCDR 一(yī)體(tǐ)機的鏡像作爲一(yī)個 ZFS 數據集(而非上文提到的 zvol)來進行分(fēn)發,對其進行克隆并直接引導至 ZFS 的克隆副本。由于 Grub 的新版本已經可以支持讀取 ZFS,此外(wài)還提供了 ZFS initramfs 模塊,ZFS-on-root 絕對是可行的。如果要從一(yī)個鏡像升級到下(xià)一(yī)個(例如從一(yī)個 ZFS 數據集升級到下(xià)一(yī)個),隻需要更新 Grub 的配置并重引導就行。這種方式可以正常起效,但因爲引導至 ZFS,這是一(yī)種比較新的做法,我(wǒ)們認爲其成熟度還不足以滿足我(wǒ)們産品的需求。不予考慮。

簡單的 A/B 分(fēn)區:有些一(yī)體(tǐ)機和手機會使用兩個分(fēn)區,其中(zhōng)一(yī)個包含當前系統,另一(yī)個包含下(xià)一(yī)個系統。這種思路也很簡單:下(xià)載新鏡像,将其 Rsync 到不活躍分(fēn)區,更新 Grub,然後重引導。然而這種做法的問題在于,我(wǒ)們的有些設備不具備額外(wài)創建一(yī)個分(fēn)區所需的存儲空間(或者至少需要重建分(fēn)區)。我(wǒ)們在實驗中(zhōng)嘗試過在首次重引導過程中(zhōng),從 initramfs 内部将活躍根分(fēn)區拆分(fēn)爲兩個并且成功了(挺酷的對吧),但考慮到這将要用于我(wǒ)們的主要産品,該方法風險太大(dà)。不予考慮。

引導至 A/B 目錄:由于一(yī)些設備缺乏備用分(fēn)區,我(wǒ)們還實驗過将鏡像的兩個副本保存到根分(fēn)區中(zhōng)的兩個文件夾中(zhōng)(例如一(yī)個 /images/412 和一(yī)個 /images/415),随後修改 initramfs 引導至 /images/415,而非引導至 /。不管你信不信,雖然聽(tīng)起來挺瘋狂,但這樣做竟然也成功了,并且整個方法也超級簡單,隻要對 initramfs 進行少量修改:mount --bind /images/415 /root 改成這樣就行。一(yī)切都可以正常運轉,不過很多 Linux 工(gōng)具(df、mount……)會因爲根目錄不是 / 而遇到一(yī)些問題,所以這個方法也不予考慮。

7

循環往複,這就夠了!

在嘗試過用多種方法引導鏡像後,我(wǒ)們最終采取的做法似乎感覺有些無趣。不過無趣也是好事對吧!

我(wǒ)們發現,如果要引導一(yī)個鏡像,最簡單可靠的方法是利用 Grub 的回環引導(Loopback booting)機制,并配合 initramfs 對 Loop 的支持(請參閱 loop=…參數):

衆所周知(zhī),Grub 是種引導加載器(Boot loader)。它的責任是加載初始的 RAM 磁盤和内核。爲此,Grub 内置了對很多文件系統的讀取能力,并能通過 loopback 命令支持稍後将要提到的“文件系統中(zhōng)的文件系統”。loopback 命令可在根分(fēn)區找到鏡像文件并對其進行環回(Loop),這樣就可以照常使用 linux 和 initrd 命令找到内核和 RAM 磁盤。例如我(wǒ)們在設備 grub.cfg 文件中(zhōng)(通過 /etc/grub.d 中(zhōng)的鈎子)生(shēng)成的菜單項範例如下(xià)所示:

在這個例子中(zhōng),Grub 首先會通過 search 以及 UUID 尋找根分(fēn)區(就像對常規安裝的 Ubuntu 做的那樣)。随後會發現根分(fēn)區中(zhōng)的鏡像文件 /images/415.0.img,最後找到鏡像中(zhōng)的内核((loop)/vmlinuz)和 RAM 磁盤((loop)/initrd.img)。

整個過程異常簡單,但同時卻非常酷:引導加載器竟然能這樣做,這一(yī)點讓我(wǒ)大(dà)爲驚奇。

當 Grub 找到内核和初始 RAM 磁盤後,會将 RAM 磁盤載入内存(震驚!),随後挂載根文件系統,最後将控制權轉交給 init 進程。

在 Ubuntu 中(zhōng),initramfs-tools 軟件包提供了創建和修改初始 RAM 磁盤的工(gōng)具。幸虧該軟件包已經可以支持回環引導機制,因此一(yī)般來說除了需要在内核行傳遞 loop= 參數,其他什麽都不用做。如果設置了該參數,initramfs 會用回環的方式,使用 mount -o loop(參閱源代碼)将根文件系統加載至鏡像。考慮到代碼中(zhōng)有一(yī)條相當吓人的 FIXME 消息(# FIXME This has no error checking),我(wǒ)們認爲最好能提高它的彈性,爲其增加錯誤處理和 fsck 能力。不過大(dà)部分(fēn)情況下(xià),使用 initramfs 都可以順利引導并且不顯示任何信息。

就是這樣,一(yī)個簡單的解決方案,洋洋灑灑寫了這麽多。

這種方法在實踐中(zhōng)用起來是這樣的。如圖所示,該設備的根文件系統位于 /dev/loop0,該回環設備在 initramfs 中(zhōng)設置而來,指向了一(yī)個鏡像文件:

本例中(zhōng),鏡像是位于根分(fēn)區(如 /dev/sda1)下(xià)的 /images/412.0.img。請注意,如果鏡像中(zhōng)存在空的 /host 文件夾,initramfs 會将根分(fēn)區挂載在這裏:

8

鏡像間的升級

我(wǒ)們已經可以構建、分(fēn)發并引導鏡像。如果将這一(yī)切結合在一(yī)起就會發現,從一(yī)個鏡像到下(xià)一(yī)個鏡像的升級其實一(yī)點也不難:

清理老鏡像,下(xià)載新鏡像,導入到池,導出到鏡像文件。

将配置從當前鏡像遷移到下(xià)一(yī)個鏡像。

更新 Grub 以指向新鏡像。

重引導。

我(wǒ)們所做的就是這樣。爲此還開(kāi)發了一(yī)個名爲 upgradectl 的工(gōng)具:


upgradectl 通常可由我(wǒ)們的簽入進程遠程觸發:在設備正常運轉的過程中(zhōng),它可以下(xià)載并導出鏡像(第 1 步),借此在後台爲升級過程做準備。需要進行升級時(通常是夜間的設備閑置時段),實際的升級過程将非常快速地完成,因爲隻需要遷移配置,更新 Grub 并重引導(第 2-4 步)即可。一(yī)般來說,升級過程中(zhōng)的設備停機時間約爲 5-10 分(fēn)鍾,并且這主要取決于重引導所需的時間(大(dà)型設備可能需要更久,因爲需要 IPMI/BMC 初始化)。

當然,這一(yī)過程中(zhōng)也有數不勝數的問題和邊緣案例需要考慮:聽(tīng)起來确實簡單,但想要做對其實并不容易,尤其是考慮到我(wǒ)們現有的 8 萬台一(yī)體(tǐ)機中(zhōng),有些在生(shēng)産環境中(zhōng)連續運轉已經有超過 7 年時間了。

但這也造就了一(yī)些有趣的挑戰:我(wǒ)們已經将數千台設備從 Ubuntu 12.04(甚至 10.04)直接升級至 Ubuntu 16.04。如果升級過程因爲某些原因失敗,會通過一(yī)些邏輯來處理老鏡像的回滾。我(wǒ)們處理了完整的操作系統盤、有故障的硬件(磁盤、IPMI、RAM……)、配置爲 RAID 的操作系統盤以及 Grub 無法向其中(zhōng)寫入的問題,當然還有 ZFS 池出錯、Linux 進程挂起(D 狀态)、重引導挂起等各種問題。

但是你猜怎樣:這一(yī)切都是值得的。這就好像結束了一(yī)場爲期 7 年的寒冬之後進行的春季大(dà)掃除。我(wǒ)們讓這些設備重新煥發了生(shēng)機,并且這樣的工(gōng)作還将繼續,每兩周進行一(yī)次!

9

總結

本文介紹了如何将 BCDR 一(yī)體(tǐ)機的部署流程由基于 Debian 軟件包的方法改爲基于鏡像的方法。此外(wài)還介紹了構建、分(fēn)發鏡像的方法,以及如何使用 Grub 的 loopback 機制引導鏡像的做法。

雖然這種基于鏡像的升級方法的誕生(shēng)有我(wǒ)的全程參與,但這其中(zhōng)最讓人激動的一(yī)點在于:借助這種機制,我(wǒ)們甚至可以在不同内核,以及不同的操作系統大(dà)版本之間切換。每次發布升級後,我(wǒ)們都可以有效地引導至一(yī)個全新操作系統,這意味着系統不會随着時間的延長而退化,所有手工(gōng)改動都會被消除,甚至從技術上來看,還可以在願意的情況下(xià)切換使用不同的 Linux 發行版。

并且這一(yī)切都是在後台進行的,完全無需用戶介入,對用戶來說完全透明:每兩周對 8 萬個操作系統進行升級,這該有多酷啊!

責任編輯:中(zhōng)山網站建設
 【網訊網絡】國家高新技術企業》十年專注軟件開(kāi)發,網站建設,網頁設計,APP開(kāi)發,小(xiǎo)程序,微信公衆号開(kāi)發,定制各類企業管理軟件(OA、CRM、ERP、訂單管理系統、進銷存管理軟件等)!服務熱線:0760-88610046、13924923903,http://www.wansion.net

您的項目需求咨詢熱線:0760-88610046(國家高新技術企業)

*請認真填寫需求,我(wǒ)們會在24小(xiǎo)時内與您取得聯系。