2019-09-30
任何曾經管理(lǐ)過幾十上百台物理(lǐ)服務器的(de)人都知道(dào):确保所有服務器始終安裝最新安全更新,或者保證所有服務器的(de)配置和(hé)狀态相一(yī)緻,這始終是一(yī)件很難完成的(de)任務。為(wèi)了解決這個問題,系統管理(lǐ)員通常會使用 Puppet 、 Salt 等工具,或将應用程序部署到容器中。如(rú)果整個環境都能由你控制,這些當然都是很棒的(de)方法,但如(rú)果你使用了類似 BCDR 一(yī)體機之類的(de)設備(或者任何未部署在自(zì)己基礎架構內(nèi)的(de)一(yī)體機 / 服務器設施),這些方法往往就不怎麽實用了。除此之外,替換系統內(nèi)核、安裝大型系統升級,或安裝其他需要重啓的(de)大型補丁,此時也無法适用這些方法。
當我們使用的(de) BCDR 設備面臨這些問題後,我們開始尋找其他更可(kě)行的(de)方法,并且真的(de)有所收獲。近兩年(nián)來,我們為(wèi)超過 80,000 台設備使用了這種方法,效果一(yī)直很穩定。本文我将談談我們是如(rú)何通過鏡像、回環設備(Loop device)以及大量和(hé) Grub 有關的(de)“魔法”解決這個問題的(de)。如(rú)果對此話題感興趣,歡迎繼續閱讀下去(qù)。
1
從頭到尾使用 Debian 軟件包?
我們的(de) BCDR 一(yī)體機始終運行了 Ubuntu,因此在更新軟件時,最自(zì)然的(de)方法就是使用 Debian 軟件包。過去(qù)很長(cháng)時間以來都是這樣做(zuò)的(de):每兩周,我們會為(wèi) Ubuntu 10.04/12.04(沒錯,我知道(dào)你有疑問,請繼續讀下去(qù)!)構建所需的(de)發布,經過全面測試後将其正式部署出去(qù)。
很長(cháng)時間以來這樣做(zuò)完全沒問題,但這種做(zuò)法有一(yī)些很明顯的(de)不足之處:
第三方依賴項的(de)更新:使用少量 Debian 軟件包,這容易讓人覺得隻需要為(wèi)自(zì)己的(de)軟件負責,而不需要為(wèi)一(yī)體機中運行的(de)其他軟件負責。如(rú)果你隻使用了自(zì)己的(de) datto.deb,但此時 Apache、Samba、libc 甚至 PHP 的(de)更新管理(lǐ)工作其實同樣重要。鑒于我們作為(wèi) Datto,本身所銷售的(de)就是完整的(de)一(yī)體機,當然也就需要負責管理(lǐ)整個棧,尤其是第三方軟件的(de)安全補丁等內(nèi)容。
服務重啓動 / 重引導:對于一(yī)些需要重啓動服務甚至重引導計算機的(de)大型更新,依賴項問題也會變得異常棘手。當然,Debian 軟件包自(zì)己就應該能處理(lǐ)服務重啓動問題,但實際上并非所有軟件包都能妥善搞定。并且一(yī)旦需要重引導(例如(rú)需要升級系統內(nèi)核),還需要确保不會打斷重要的(de)設備任務(例如(rú)備份、虛拟化……),此外還要保證設備最終能引導成功(這事情并不像你想的(de)那麽容易,下文将會詳細介紹!)。
發行版升級:如(rú)果整個操作系統的(de)版本需要升級,這才是最麻煩的(de)地(dì)方。舉例來說,如(rú)果隻使用 apt-get dist-upgrade 命令以及 reboot 命令将 Ubuntu 10.04 升級到 16.04,整個過程将變得漫長(cháng)無比,并且很多時候可(kě)能會升級失敗(隻要你用過 usedapt-get dist-upgrade,那麽肯定會明白)。
數千個版本和(hé)狀态:在 Debian 的(de)升級模型中,“設備”的(de)實際行為(wèi)其實和(hé)普通計算機無異:剛剛創建好鏡像并部署後,一(yī)切都是嶄新的(de),一(yī)切都可(kě)以正常運轉。但随着鏡像越來越老,操作系統退化的(de)問題就變得越嚴峻,導緻不同設備的(de)狀态産生巨大差異。能嚴重到什麽程度?我們的(de)設備(在切換到 KVM 前)曾經使用了 40 個不同版本的(de) VirtualBox、25 個不同的(de) ZFS 版本,以及超過 80 個不同的(de) Linux 內(nèi)核!
其實,實際遇到的(de)問題遠比上面列出的(de)更多,不過這裏就不拿更多問題來給大家添堵了。快速開始介紹最有趣的(de)內(nèi)容:如(rú)何解決!
2
使用鏡像,而非軟件包!
鑒于會遇到這麽多問題,很明顯,我們需要用更好的(de)解決方案來管理(lǐ)設備狀态和(hé)配置。産品中不同的(de)設備配置 / 軟件包 / 版本數量不僅要降至最低(dī),并且在每次升級時必需能保證能夠升級整個棧:不僅要能升級我們自(zì)己的(de)軟件,還要能升級第三方軟件,甚至諸如(rú) Libc 或系統內(nèi)核等系統庫。
3
前提要求
随後我們開始确定這個解決方案的(de)前提要求,其實這些要求并不多:
所有設備沿用相同的(de)升級路徑,并且隻存在一(yī)個升級路徑。
所有設備均可(kě)通過這種方式升級(哪怕操作系統盤較小的(de)老設備)。
從一(yī)個版本切換到另一(yī)個版本的(de)過程必需滿足原子(zǐ)性要求(或盡可(kě)能滿足這種要求)。(如(rú)果升級失敗)能夠回滾到上一(yī)個版本。而這些要求還暗含了一(yī)個最重要的(de)前提條件:不能繼續使用基于軟件包的(de)升級方法了,并且(從字裏行間也能體會到)在升級過程中重引導一(yī)體機,這是可(kě)以接受的(de)。
這些都是很大膽的(de)念頭。我們确實做(zuò)出了一(yī)個重大決定!
4
那麽鏡像到底是什麽?
為(wèi)了減少配置的(de)數量,我們決定不再将我們的(de)軟件及其所有依賴項看作不同個體,而是将所有這一(yī)切組合成一(yī)個統一(yī)的(de)可(kě)交付物:鏡像。
那麽鏡像到底是什麽?鏡像(在我們的(de)環境中)是指一(yī)種 EXT4 文件系統,其中包含了引導和(hé)運行 BCDR 一(yī)體機所需的(de)一(yī)切,例如(rú):
Ubuntu 基礎操作系統(內(nèi)核、系統庫……)
必需的(de)第三方工具和(hé)庫(Apache、KVM、ZFS……)
Datto 設備軟件(我們的(de)營銷團隊将其稱之為(wèi) IRIS)
下圖就顯示了一(yī)個這種鏡像所包含的(de)內(nèi)容:
我們對這種想法非常激動,因為(wèi)通過使用鏡像,隻需要一(yī)個數字,也就是鏡像的(de)版本号(例如(rú)上圖中的(de)“415”)就可(kě)以定義所安裝的(de)每個軟件的(de)具體版本。再也不用針對多種 ZFS 版本測試我們的(de)軟件,更不用暗自(zì)祈禱我們的(de)軟件能兼容所有 KVM 版本。太棒了!
5
基于鏡像的(de)升級
做(zuò)出所有這些重要決定後,我們依然需要通過某種方法來構建、分發,并在設備上引導這些鏡像。具體怎麽做(zuò)呢(ne)?
構建鏡像
通常來說,每次标記了一(yī)個新的(de)發布(或發布候選)後,我們會自(zì)動構建鏡像:每次在 Git 中推送标簽後,一(yī)個 CI 工作進程會開始構建鏡像。構建過程本身也挺有趣,不過已經超出了本文的(de)範圍,但為(wèi)了不吊大家胃口,下文将簡單介紹這個過程:
我們首先會為(wèi)自(zì)己的(de)軟件構建 Debian 軟件包,并将其發布至一(yī)個 Debian 倉庫。随後使用 aptly (參閱“Datto packages”一(yī)圖)為(wèi)這個 Debian 倉庫創建快照,同時還會定期對一(yī)個上遊 Ubuntu 倉庫(“Upstream packages”)執行類似操作。随後使用 debootstrap 創建一(yī)個 Ubuntu 基準系統,并将我們的(de)所有軟件及其依賴項安裝到一(yī)個 Chroot 中。一(yī)旦完成這些操作,會對其創建 Tar 歸檔并 Rsync 到我們自(zì)己的(de)鏡像服務器。在鏡像服務器上,我們會提取出 Tarball 并 Rsync 給最新鏡像,這個最新鏡像位于一(yī)個格式化為(wèi) EXT4 文件系統的(de) ZFS 卷(zvol)中。在将所有未使用的(de) EXT4 塊歸零後,會對包含該文件系統的(de) zvol 創建最終快照。
因此在鏡像服務器上可(kě)以看到類似下圖所示的(de)內(nèi)容:
上述 zvol 包含了我們 BCDR 一(yī)體機的(de) EXT4 文件系統。這就是一(yī)個鏡像,也是我們唯一(yī)需要交付的(de)東西。它可(kě)以作為(wèi)一(yī)個整體進行測試,一(yī)旦通過了 QA 流程,就可(kě)以分發到客戶的(de) BCDR 設備中了。
分發鏡像
在成功構建鏡像後,又該如(rú)何将其從我們的(de)數據中心發送給超過 8 萬台設備?很簡單,我們使用了 ZFS send/recv !
我們的(de)所有設備都具備 ZFS 池,其中存儲了設備的(de)鏡像備份,并且之前我們就在大量使用 ZFS send/recv 為(wèi)這些備份提供離(lí)場保存能力。而此時隻不過是換種方向使用這種技術。
我們是這樣做(zuò)的(de):需要升級時,會讓一(yī)部設備通過 HTTPS 下載 ZFS sendfile diff(之前曾經嘗試過直接通過 SSH 使用 ZFS send/recv,但這種方式無法進行緩存):
從上圖中可(kě)以看到,通常并不需要下載完整鏡像,因為(wèi)設備以前就升級過,已經在本地(dì)池中保存了鏡像的(de)一(yī)個版本。這就很棒了:通過這種技術,我們可(kě)以進行差異化的(de)操作系統升級,也就是說,設備隻需要下載鏡像中有變化的(de)塊。
這是一(yī)種雙赢的(de)結果,因為(wèi)不會過多占用客戶網絡帶寬,而我們自(zì)己的(de)數據中心也可(kě)以節約一(yī)筆(bǐ)帶寬費用。
下載好的(de)鏡像會被導入本地(dì) ZFS 池。這對于下一(yī)次升級很必要(可(kě)以确保隻需要下載有變化的(de)內(nèi)容):
引導鏡像
拿到鏡像後,如(rú)何引導至這個新的(de)文件系統?如(rú)果我們構建的(de)每個鏡像版本都是全新操作系統,又該如(rú)何從一(yī)個版本引導至下一(yī)個版本?
6
ZFS-on-root、A/B 分區和(hé) A/B 文件夾
毫無疑問,這些問題的(de)答案并不隻有一(yī)種。我們可(kě)以通過多種方法使用鏡像生成可(kě)引導的(de)系統,因此需要多次實驗找出一(yī)種最佳方法。
這個過程也很有趣,因此我準備簡要介紹每種方法,以及最終未選擇這些方法的(de)原因:
ZFS-on-root 和(hé) A/B 數據集:我們的(de)鏡像備份操作中大量使用了 ZFS,因此一(yī)開始很自(zì)然就覺得也可(kě)以将 ZFS 用作一(yī)體機的(de)根文件系統。為(wèi)此可(kě)以将 BCDR 一(yī)體機的(de)鏡像作為(wèi)一(yī)個 ZFS 數據集(而非上文提到的(de) zvol)來進行分發,對其進行克隆并直接引導至 ZFS 的(de)克隆副本。由于 Grub 的(de)新版本已經可(kě)以支持讀取 ZFS,此外還提供了 ZFS initramfs 模塊,ZFS-on-root 絕對是可(kě)行的(de)。如(rú)果要從一(yī)個鏡像升級到下一(yī)個(例如(rú)從一(yī)個 ZFS 數據集升級到下一(yī)個),隻需要更新 Grub 的(de)配置并重引導就行。這種方式可(kě)以正常起效,但因為(wèi)引導至 ZFS,這是一(yī)種比較新的(de)做(zuò)法,我們認為(wèi)其成熟度還不足以滿足我們産品的(de)需求。不予考慮。
簡單的(de) A/B 分區:有些一(yī)體機和(hé)手機會使用兩個分區,其中一(yī)個包含當前系統,另一(yī)個包含下一(yī)個系統。這種思路也很簡單:下載新鏡像,将其 Rsync 到不活躍分區,更新 Grub,然後重引導。然而這種做(zuò)法的(de)問題在于,我們的(de)有些設備不具備額外創建一(yī)個分區所需的(de)存儲空間(或者至少需要重建分區)。我們在實驗中嘗試過在首次重引導過程中,從 initramfs 內(nèi)部将活躍根分區拆分為(wèi)兩個并且成功了(挺酷的(de)對吧(ba)),但考慮到這将要用于我們的(de)主要産品,該方法風險太大。不予考慮。
引導至 A/B 目錄:由于一(yī)些設備缺乏備用分區,我們還實驗過将鏡像的(de)兩個副本保存到根分區中的(de)兩個文件夾中(例如(rú)一(yī)個 /images/412 和(hé)一(yī)個 /images/415),随後修改 initramfs 引導至 /images/415,而非引導至 /。不管你信不信,雖然聽起來挺瘋狂,但這樣做(zuò)竟然也成功了,并且整個方法也超級簡單,隻要對 initramfs 進行少量修改:mount --bind /images/415 /root 改成這樣就行。一(yī)切都可(kě)以正常運轉,不過很多 Linux 工具(df、mount……)會因為(wèi)根目錄不是 / 而遇到一(yī)些問題,所以這個方法也不予考慮。
7
循環往複,這就夠了!
在嘗試過用多種方法引導鏡像後,我們最終采取的(de)做(zuò)法似乎感覺有些無趣。不過無趣也是好事對吧(ba)!
我們發現,如(rú)果要引導一(yī)個鏡像,最簡單可(kě)靠的(de)方法是利用 Grub 的(de)回環引導(Loopback booting)機制,并配合 initramfs 對 Loop 的(de)支持(請參閱 loop=…參數):
衆所周知,Grub 是種引導加載器(Boot loader)。它的(de)責任是加載初始的(de) RAM 磁盤和(hé)內(nèi)核。為(wèi)此,Grub 內(nèi)置了對很多文件系統的(de)讀取能力,并能通過 loopback 命令支持稍後将要提到的(de)“文件系統中的(de)文件系統”。loopback 命令可(kě)在根分區找到鏡像文件并對其進行環回(Loop),這樣就可(kě)以照常使用 linux 和(hé) initrd 命令找到內(nèi)核和(hé) RAM 磁盤。例如(rú)我們在設備 grub.cfg 文件中(通過 /etc/grub.d 中的(de)鈎子(zǐ))生成的(de)菜單項範例如(rú)下所示:
在這個例子(zǐ)中,Grub 首先會通過 search 以及 UUID 尋找根分區(就像對常規安裝的(de) Ubuntu 做(zuò)的(de)那樣)。随後會發現根分區中的(de)鏡像文件 /images/415.0.img,最後找到鏡像中的(de)內(nèi)核((loop)/vmlinuz)和(hé) RAM 磁盤((loop)/initrd.img)。
整個過程異常簡單,但同時卻非常酷:引導加載器竟然能這樣做(zuò),這一(yī)點讓我大為(wèi)驚奇。
當 Grub 找到內(nèi)核和(hé)初始 RAM 磁盤後,會将 RAM 磁盤載入內(nèi)存(震驚!),随後挂載根文件系統,最後将控制權轉交給 init 進程。
在 Ubuntu 中,initramfs-tools 軟件包提供了創建和(hé)修改初始 RAM 磁盤的(de)工具。幸虧該軟件包已經可(kě)以支持回環引導機制,因此一(yī)般來說除了需要在內(nèi)核行傳遞 loop= 參數,其他什麽都不用做(zuò)。如(rú)果設置了該參數,initramfs 會用回環的(de)方式,使用 mount -o loop(參閱源代碼)将根文件系統加載至鏡像。考慮到代碼中有一(yī)條相當吓人的(de) FIXME 消息(# FIXME This has no error checking),我們認為(wèi)最好能提高(gāo)它的(de)彈性,為(wèi)其增加錯誤處理(lǐ)和(hé) fsck 能力。不過大部分情況下,使用 initramfs 都可(kě)以順利引導并且不顯示任何信息。
就是這樣,一(yī)個簡單的(de)解決方案,洋洋灑灑寫了這麽多。
這種方法在實踐中用起來是這樣的(de)。如(rú)圖所示,該設備的(de)根文件系統位于 /dev/loop0,該回環設備在 initramfs 中設置而來,指向了一(yī)個鏡像文件:
本例中,鏡像是位于根分區(如(rú) /dev/sda1)下的(de) /images/412.0.img。請注意,如(rú)果鏡像中存在空的(de) /host 文件夾,initramfs 會将根分區挂載在這裏:
8
鏡像間的(de)升級
我們已經可(kě)以構建、分發并引導鏡像。如(rú)果将這一(yī)切結合在一(yī)起就會發現,從一(yī)個鏡像到下一(yī)個鏡像的(de)升級其實一(yī)點也不難:
清理(lǐ)老鏡像,下載新鏡像,導入到池,導出到鏡像文件。
将配置從當前鏡像遷移到下一(yī)個鏡像。
更新 Grub 以指向新鏡像。
重引導。
我們所做(zuò)的(de)就是這樣。為(wèi)此還開發了一(yī)個名為(wèi) upgradectl 的(de)工具:
upgradectl 通常可(kě)由我們的(de)簽入進程遠程觸發:在設備正常運轉的(de)過程中,它可(kě)以下載并導出鏡像(第 1 步),借此在後台為(wèi)升級過程做(zuò)準備。需要進行升級時(通常是夜間的(de)設備閑置時段),實際的(de)升級過程将非常快速地(dì)完成,因為(wèi)隻需要遷移配置,更新 Grub 并重引導(第 2-4 步)即可(kě)。一(yī)般來說,升級過程中的(de)設備停機時間約為(wèi) 5-10 分鍾,并且這主要取決于重引導所需的(de)時間(大型設備可(kě)能需要更久,因為(wèi)需要 IPMI/BMC 初始化)。
當然,這一(yī)過程中也有數不勝數的(de)問題和(hé)邊緣案例需要考慮:聽起來确實簡單,但想要做(zuò)對其實并不容易,尤其是考慮到我們現有的(de) 8 萬台一(yī)體機中,有些在生産環境中連續運轉已經有超過 7 年(nián)時間了。
但這也造就了一(yī)些有趣的(de)挑戰:我們已經将數千台設備從 Ubuntu 12.04(甚至 10.04)直接升級至 Ubuntu 16.04。如(rú)果升級過程因為(wèi)某些原因失敗,會通過一(yī)些邏輯來處理(lǐ)老鏡像的(de)回滾。我們處理(lǐ)了完整的(de)操作系統盤、有故障的(de)硬件(磁盤、IPMI、RAM……)、配置為(wèi) RAID 的(de)操作系統盤以及 Grub 無法向其中寫入的(de)問題,當然還有 ZFS 池出錯、Linux 進程挂起(D 狀态)、重引導挂起等各種問題。
但是你猜怎樣:這一(yī)切都是值得的(de)。這就好像結束了一(yī)場為(wèi)期 7 年(nián)的(de)寒冬之後進行的(de)春季大掃除。我們讓這些設備重新煥發了生機,并且這樣的(de)工作還将繼續,每兩周進行一(yī)次!
9
總結
本文介紹了如(rú)何将 BCDR 一(yī)體機的(de)部署流程由基于 Debian 軟件包的(de)方法改為(wèi)基于鏡像的(de)方法。此外還介紹了構建、分發鏡像的(de)方法,以及如(rú)何使用 Grub 的(de) loopback 機制引導鏡像的(de)做(zuò)法。
雖然這種基于鏡像的(de)升級方法的(de)誕生有我的(de)全程參與,但這其中最讓人激動的(de)一(yī)點在于:借助這種機制,我們甚至可(kě)以在不同內(nèi)核,以及不同的(de)操作系統大版本之間切換。每次發布升級後,我們都可(kě)以有效地(dì)引導至一(yī)個全新操作系統,這意味着系統不會随着時間的(de)延長(cháng)而退化,所有手工改動都會被消除,甚至從技術上來看,還可(kě)以在願意的(de)情況下切換使用不同的(de) Linux 發行版。
并且這一(yī)切都是在後台進行的(de),完全無需用戶介入,對用戶來說完全透明:每兩周對 8 萬個操作系統進行升級,這該有多酷啊!
責任編輯:中山網站建設
【網訊網絡】國(guó)家高(gāo)新技術企業》十年(nián)專注軟件開發,網站建設,網頁設計,APP開發,小程序,微信公衆号開發,定制各類企業管理(lǐ)軟件(OA、CRM、ERP、訂單管理(lǐ)系統、進銷存管理(lǐ)軟件等)!服務熱線:0760-88610046、13924923903,http://www.wansion.net
上一(yī)篇:2025年(nián)網絡安全産業規模或超2000億
下一(yī)篇:NET Core 3.0 正式公布:新特性詳細解讀
*請認真填寫需求,我們會在24小時內(nèi)與您取得聯系。