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