數千台設備的軟件和操作系統,每兩(liǎng)周升級一次,這(zhè)是怎麼(me)做到的

2019-09-30

1000 (1).jpg


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

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

1

從頭到尾使用 Debian 軟件包?

我們的 BCDR 一體機始終運行了 Ubuntu,因此在更新軟件時,最自然的方法就是使用 Debian 軟件包。過(guò)去很長(cháng)時間以來都(dōu)是這(zhè)樣做的:每兩(liǎng)周,我們會爲 Ubuntu 10.04/12.04(沒(méi)錯,我知道(dào)你有疑問,請繼續讀下去!)構建所需的發(fā)布,經(jīng)過(guò)全面(miàn)測試後(hòu)將(jiāng)其正式部署出去。

很長(cháng)時間以來這(zhè)樣做完全沒(méi)問題,但這(zhè)種(zhǒng)做法有一些很明顯的不足之處:

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

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

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

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

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

2

使用鏡像,而非軟件包!

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

3

前提要求

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

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

所有設備均可通過(guò)這(zhè)種(zhǒng)方式升級(哪怕操作系統盤較小的老設備)。

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

這(zhè)些都(dōu)是很大膽的念頭。我們确實做出了一個重大決定!

4

那麼(me)鏡像到底是什麼(me)?

爲了減少配置的數量,我們決定不再將(jiāng)我們的軟件及其所有依賴項看作不同個體,而是將(jiāng)所有這(zhè)一切組合成(chéng)一個統一的可交付物:鏡像。

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

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

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

Datto 設備軟件(我們的營銷團隊將(jiāng)其稱之爲 IRIS)

下圖就顯示了一個這(zhè)種(zhǒng)鏡像所包含的内容:


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

5

基于鏡像的升級

做出所有這(zhè)些重要決定後(hòu),我們依然需要通過(guò)某種(zhǒng)方法來構建、分發(fā),并在設備上引導這(zhè)些鏡像。具體怎麼(me)做呢?

構建鏡像

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

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

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

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

分發(fā)鏡像

在成(chéng)功構建鏡像後(hòu),又該如何將(jiāng)其從我們的數據中心發(fā)送給超過(guò) 8 萬台設備?很簡單,我們使用了 ZFS send/recv !

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

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

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

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

下載好(hǎo)的鏡像會被導入本地 ZFS 池。這(zhè)對(duì)于下一次升級很必要(可以确保隻需要下載有變化的内容):

引導鏡像

拿到鏡像後(hòu),如何引導至這(zhè)個新的文件系統?如果我們構建的每個鏡像版本都(dōu)是全新操作系統,又該如何從一個版本引導至下一個版本?

6

ZFS-on-root、A/B 分區和 A/B 文件夾

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

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

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

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

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

7

循環往複,這(zhè)就夠了!

在嘗試過(guò)用多種(zhǒng)方法引導鏡像後(hòu),我們最終采取的做法似乎感覺有些無趣。不過(guò)無趣也是好(hǎo)事(shì)對(duì)吧!

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

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

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

整個過(guò)程異常簡單,但同時卻非常酷:引導加載器竟然能(néng)這(zhè)樣做,這(zhè)一點讓我大爲驚奇。

當 Grub 找到内核和初始 RAM 磁盤後(hòu),會將(jiāng) RAM 磁盤載入内存(震驚!),随後(hòu)挂載根文件系統,最後(hòu)將(jiāng)控制權轉交給 init 進(jìn)程。

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

就是這(zhè)樣,一個簡單的解決方案,洋洋灑灑寫了這(zhè)麼(me)多。

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

本例中,鏡像是位于根分區(如 /dev/sda1)下的 /images/412.0.img。請注意,如果鏡像中存在空的 /host 文件夾,initramfs 會將(jiāng)根分區挂載在這(zhè)裡(lǐ):

8

鏡像間的升級

我們已經(jīng)可以構建、分發(fā)并引導鏡像。如果將(jiāng)這(zhè)一切結合在一起(qǐ)就會發(fā)現,從一個鏡像到下一個鏡像的升級其實一點也不難:

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

將(jiāng)配置從當前鏡像遷移到下一個鏡像。

更新 Grub 以指向(xiàng)新鏡像。

重引導。

我們所做的就是這(zhè)樣。爲此還(hái)開(kāi)發(fā)了一個名爲 upgradectl 的工具:


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

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

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

但是你猜怎樣:這(zhè)一切都(dōu)是值得的。這(zhè)就好(hǎo)像結束了一場爲期 7 年的寒冬之後(hòu)進(jìn)行的春季大掃除。我們讓這(zhè)些設備重新煥發(fā)了生機,并且這(zhè)樣的工作還(hái)將(jiāng)繼續,每兩(liǎng)周進(jìn)行一次!

9

總結

本文介紹了如何將(jiāng) BCDR 一體機的部署流程由基于 Debian 軟件包的方法改爲基于鏡像的方法。此外還(hái)介紹了構建、分發(fā)鏡像的方法,以及如何使用 Grub 的 loopback 機制引導鏡像的做法。

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

并且這(zhè)一切都(dōu)是在後(hòu)台進(jìn)行的,完全無需用戶介入,對(duì)用戶來說完全透明:每兩(liǎng)周對(duì) 8 萬個操作系統進(jìn)行升級,這(zhè)該有多酷啊!

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

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

*請認真填寫需求,我們會在24小時内與您取得聯系。