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