Linode 實時遷移詳解
當開發(fā)者將工作負載部署到云計算平臺時,往往并不需要考慮運行這些服務的底層硬件。在人們對“云” 的理想化印象中,硬件維護和物理限制往往是無形的,然而硬件不可避免需要時不時進行維護,這可能會導致停機。為避免這樣的停機時間被轉(zhuǎn)嫁給我們的客戶,并真正實現(xiàn)云的承諾,Linode 提供了一種名為實時遷移(Live Migration)的工具。
借助實時遷移技術,Linode 實例可在不中斷服務的前提下在不同物理服務器之間移動。通過實時遷移工具移動 Linode 實例時,遷移過程對 Linode 實例中運行的進程是完全不可見的。如果一臺主機的硬件需要維護,即可通過實時遷移,將該主機上的所有 Linode 實例無縫轉(zhuǎn)移到另一臺主機中。遷移操作完成后,即可開始修理物理硬件,全過程中并不產(chǎn)生會影響到客戶的停機時間。
這幾乎成為一種決定性的技術,也成為云技術和非云技術之間的轉(zhuǎn)折點。本文我們將深入了解這項技術背后的細節(jié)。
為慶祝 Linode 加入 Akamai 解決方案大家庭,現(xiàn)在注冊 Linode,就可免費獲得價值 100 美元的使用額度,可以隨意使用 Linode 云平臺提供的各種服務。立即點擊這里了解詳情并注冊吧↓↓↓
實時遷移的工作原理
和大部分新項目類似,Linode 的實時遷移也是這樣啟動的:進行大量研究,創(chuàng)建一系列原型,獲得同事和管理層的大量幫助。我們的第一步是調(diào)查 QEMU 如何處理實時遷移。QEMU 是 Linode 使用的一種虛擬化技術,而實時遷移也是 QEUM 的一項功能。因此我們團隊的重點是將這項技術引入 Linode,而非重新發(fā)明一個類似的技術。
那么實時遷移技術到底是如何以 QEMU 的方式實現(xiàn)的?整個過程分為以下四步:
- 啟動目標 QEUM 實例,該實例的各項參數(shù)與需要遷移的源 QEUM 實例完全相同。
- 對磁盤進行實時遷移。數(shù)據(jù)傳輸過程中,對磁盤內(nèi)容進行的任何更改也會提交至目標磁盤。
- 對內(nèi)存數(shù)據(jù)進行實時遷移。遷移過程中,內(nèi)存內(nèi)容的任何變化也會提交至目標內(nèi)存。如果這一過程中磁盤內(nèi)容也出現(xiàn)了變化,相關變化同樣會被提交至目標 QEUM 實例的磁盤中。
- 執(zhí)行割接點。當 QEMU 確認有足夠多的內(nèi)存頁可以放心進行割接后,源和目標 QEMU 實例將會暫停。QEMU 會復制最后幾頁內(nèi)存數(shù)據(jù)和機器狀態(tài),機器狀態(tài)包括 CPU 緩存和下一條 CPU 指令。隨后,QEMU 會讓目標開始運行,這樣目標實例就可以從源實例停止時的狀態(tài)恢復運行了。
這些步驟概括介紹了 QEMU 實時遷移的執(zhí)行過程。然而依然需要通過包含很多手工操作的方式來精確指定目標 QEMU 實例的啟動方式。此外,上述過程中的每個操作都必須在正確的時間執(zhí)行。
Linode 實現(xiàn)實時遷移的方式
在分析過 QEMU 開發(fā)者已經(jīng)實現(xiàn)的技術后,我們該考慮具體用怎樣的方式將其實給 Linode。這個答案恰恰是我們工作的重中之重。
在實時遷移工作流程的第 1 步,需要啟動目標 QEMU 實例以接受傳入的實施遷移連接。在實現(xiàn)這一步時,我們最初的想法是拿到當前 Linode 實例的配置文件,隨后將其應用到目標計算機。理論上這應該很簡單,但進一步思考就會發(fā)現(xiàn),實際情況要復雜很多。尤其是,配置文件雖然可以告訴我們 Linode 實例是如何啟動的,但并不一定可以完整描述啟動后的 Linode 實例的完整狀態(tài)。例如,用戶可以在 Linode 實例啟動完畢后通過熱插拔的方式連接塊存儲設備,但這種情況并不會記錄到配置文件中。
為了在目標主機上創(chuàng)建 QEMU 實例,必須對當前運行的 QEMU 實例進行剖析。我們通過檢查 QMP 接口的方式對運行中的 QEMU 實例進行剖析,該接口為我們提供了與 QEMU 實例布局情況有關的豐富信息,但它無法幫助我們從來賓系統(tǒng)的視角了解實例內(nèi)部正在發(fā)生的事情。例如,對于本地 SSD 和塊存儲,它只能告訴我們磁盤鏈接到哪里,以及虛擬磁盤連接到哪個虛擬化 PCI 插槽上。在查詢 QMP 以及檢查并分析了 QEMU 接口后,可以構(gòu)建一個 Profile 來描述如何在目標位置創(chuàng)建一個完全相同的實例。
在目標計算機上,我們將收到完整的描述信息,借此了解源實例到底是什么樣,隨后就可以在目標位置忠實重建這個實例,但此時還有一個差異。這個差別主要在于,目標 QEMU 實例在啟動時使用了一個選項,該選項可以讓 QEMU 接受傳入的遷移。
至此,實時遷移的記錄過程已經(jīng)基本結(jié)束,接下來需要看看 QEMU 是如何實現(xiàn)這些操作的。QEMU 進程樹由一個控制進程和多個工作進程組成,其中一個工作進程負責返回 QMP 調(diào)用或處理實時遷移等任務,其他進程需要一對一映射至來賓 CPU。來賓環(huán)境與 QEMU 端的功能相互隔離,具體行為類似于獨立的系統(tǒng)。
從這個意義來看,我們需要處理三層內(nèi)容:
- 第 1 層是管理層;
- 第 2 層是 QEMU 進程的一部分,負責處理所有操作;
- 第 3 層是實際的來賓層,負責與 Linode 用戶進行交互。
目標實例啟動并準備好接受傳入的遷移后,目標硬件會告知源硬件開始發(fā)送數(shù)據(jù)。源端會在收到這個信號后開始進行處理,并會在軟件中告知 QEMU 開始傳輸磁盤內(nèi)容。軟件會自主監(jiān)控磁盤傳輸進度,借此檢查傳輸操作是否完成,并會在磁盤傳輸完成后自動開始遷移內(nèi)存內(nèi)容。此時軟件依然會自主監(jiān)控內(nèi)存遷移進度,并在內(nèi)存遷移完畢后自動切換至割接模式。上述全過程都是通過 Linode 的 40Gbps 網(wǎng)絡進行的,因此網(wǎng)絡方面的操作都可以快速完成。
割接:關鍵環(huán)節(jié)
割接操作是實時遷移過程中最重要的一環(huán),只有理解了它,才能完全理解實時遷移操作。
在割接點狀態(tài)下,QEMU 已經(jīng)確認做好了所有準備,可以進行割接并在目標計算機上運行。源 QEMU 實例會讓兩端暫停運行,這意味著:
- 來賓系統(tǒng)被“時停”。如果來賓系統(tǒng)橫在運行時間同步服務(如 NTP),NTP 會在遷移完成后自動重新同步時間。這是因為系統(tǒng)時鐘會產(chǎn)生幾秒鐘的落后。
- 網(wǎng)絡請求停止。如果網(wǎng)絡請求是 TCP 請求(如 SSH 或 HTTP),基本上不會產(chǎn)生可感知的連接中斷;如果網(wǎng)絡請求是 UDP 請求(如流媒體視頻),可能會導致少量丟幀。
由于時間和網(wǎng)絡請求均已停止,我們希望割接能盡量快速完成。然而為保證成功割接,還需要進行一些檢查:
- 確保實時遷移順利完成不出錯。如果出錯則要進行回滾,解除源 Linode 實例的暫停狀態(tài),不再進一步執(zhí)行其他操作。開發(fā)過程中,我們在這方面進行了大量實驗并解決了很多錯誤,雖然這為我們造成了很多頭疼的問題,但最終都順利解決了。
- 確保關閉源實例的網(wǎng)絡,并在目標實例上正確連接。
- 讓我們的其余基礎設施清楚得知遷移后的 Linode 實例是通過哪臺物理計算機運行的。
由于割接過程時間有限,我們希望能盡快完成上述操作。解決了這些問題后,即可繼續(xù)進行割接了。源 Linode 實例會自動會自動收到 “割接完成” 信號并讓目標實例運行起來。目標 Linode 實例會從源實例暫停時的狀態(tài)恢復運行。源和目標實例上的其余內(nèi)容則會被清理。如果目標 Linode 實例在未來某個時間需要再次進行實時遷移,則會重復執(zhí)行上述步驟。
邊緣案例概述
實時遷移的大部分過程都是直接實現(xiàn)的,但考慮到邊緣案例后,該功能本身的開發(fā)也進行了大量擴展。這個項目的順利完成很大程度上要歸功于管理團隊,他們堅信該工具有著極大的愿景,并提供了完成該任務所需的各項資源,另外當然也離不開堅信該項目能夠成功完成的大量員工。
我們在下列這些領域遇到了很多邊緣案例:
- 通過開發(fā)內(nèi)部工具為 Linode 客戶支持人員和硬件運維團隊進行與實時遷移有關的協(xié)調(diào)工作。這些工具與我們當時正在使用的其他同類工具較為類似,但也有些許差異,我們?yōu)榇送度肓舜罅块_發(fā)工作:
- 該工具必須能自動檢查數(shù)據(jù)中心內(nèi)部的所有硬件設施,進而確定哪臺主機可以成為每個需要遷移的 Linode 實例的最佳目標。在進行這種選擇和決策時,考慮的相關規(guī)格包括可用 SSD 存儲空間及內(nèi)存分配情況等。
- 目標計算機的物理處理器必須與傳入的 Linode 實例相兼容。尤其是 CPU 必須具備用戶所運行的軟件必不可少的某些功能(我們將其稱之為 CPU 標記)。例如,AES 就是這樣的一種功能,該功能提供了基于硬件加速的加密能力。實時遷移的目標計算機 CPU 必須支持與源計算機相同的 CPU 標記。我們發(fā)現(xiàn)這是一種極為復雜的邊緣用例,下文將介紹我們所采用的方法。
- 優(yōu)雅地處理故障,包括最終用戶的干預或?qū)崟r遷移過程中網(wǎng)絡連接丟失等情況。下文還將詳細介紹這些信息。
- 緊跟 Linode 平臺自身的變化,而這是一個持續(xù)不斷的長期過程。對于 Linode 平臺當前和未來所支持的每個功能,我們都需要確保這些功能可與實時遷移兼容。詳細信息請繼續(xù)閱讀下文。
CPU 標記
在向來賓操作系統(tǒng)呈現(xiàn) CPU 方面,QEMU 有不同的選項。其中一個選項可將主機 CPU 的型號和功能(即 CPU 標記)直接傳遞給來賓系統(tǒng)。通過使用該選項,來賓即可不受約束地使用 KVM 虛擬化系統(tǒng)所支持的全部能力。當 Linode 首次采用 KVM 時(當時還沒有實時遷移功能),為了實現(xiàn)最大化性能,我們就使用了該選項。然而在開發(fā)實時遷移功能的過程中,該選項為我們造成了很多挑戰(zhàn)。
在實時遷移的測試環(huán)境中,源和目標主機是兩臺完全相同的計算機。但在現(xiàn)實世界中,我們的硬件集群并非 100% 完全相同的,計算機之間的某些配置差異可能導致產(chǎn)生不同的 CPU 標記。這很重要,因為當一個程序被載入 Linode 的操作系統(tǒng)后,Linode 會向該程序呈現(xiàn) CPU 標記,為了充分利用這些標記,程序可以將軟件中的特定部分載入內(nèi)存。如果一個 Linode 實例被實時遷移到不支持該 CPU 標記的目標計算機,程序?qū)罎?。這可能導致來賓操作系統(tǒng)崩潰,甚至導致 Linode 重啟動。
我們發(fā)現(xiàn)有三個因素會影響到計算機的 CPU 標記如何呈現(xiàn)給來賓系統(tǒng):
- 取決于購買時間,不同 CPU 之間會存在一些細微差異。取決于 CPU 制造商何時發(fā)布新硬件,年底購買的 CPU 可能與年初購買的具備不同標記。為了擴容,Linode 會持續(xù)購買新硬件,但就算兩個硬件訂單購買了同款 CPU,它們的 CPU 標記也可能存在差異。
- 不同的 Linux 內(nèi)核可能會向 QEMU 傳遞不同的標記。尤其是,參與實時遷移的源計算機的 Linux 內(nèi)核可能會向 QEMU 傳遞與目標計算機的 Linux 內(nèi)核不同的標記。為源計算機更新 Linux 內(nèi)核必須重啟動,因此無法在實時遷移之前通過升級內(nèi)核版本的方式解決這種不匹配問題,因為這會導致計算機上的 Linode 實例停機。
- 同理,不同的 QEMU 版本也會影響所呈現(xiàn)的 CPU 標記。更新 QEMU 同樣需要重啟動計算機。
因此在實現(xiàn)實時遷移時,我們必須設法防止程序因為 CPU 標記的不匹配而崩潰??尚械倪x項有兩個:
- 讓 QEMU 模擬 CPU 標記。但這可能導致原本快速運行的軟件運行速度變慢,并且完全無法調(diào)查原因。
- 收集源計算機的 CPU 標記列表,確保目標計算機具備完全相同的標記,隨后再進行遷移。這種方式更復雜,但不會影響用戶程序的運行速度。我們最終選擇了這種方式。
在決定對源和目標的 CPU 標記進行匹配后,我們使用下列兩種方法的組合最終實現(xiàn)了目標:
- 第一種方法更簡單。將源硬件的所有 CPU 標記發(fā)送給目標硬件,當目標硬件設置新的 QEMU 實例時,會通過檢查來確保自己至少擁有和源 Linode 實例相同的標記。如果不匹配,將不進行實時遷移。
- 第二種方法更復雜,但可以避免因為 CPU 標記不匹配導致的遷移失敗。在發(fā)起實施遷移前,我們會對具備可兼容 CPU 標記的硬件創(chuàng)建一個列表,隨后從該列表中選擇硬件來創(chuàng)建目標計算機。
第二種方法必須能快速執(zhí)行,并且讓我們的工作變得更復雜。某些情況下,我們需要針對超過 900 臺計算機檢查最多 226 個 CPU 標記。為所有這 226 個 CPU 標記編寫檢查代碼本就很困難,而這些代碼還需要不斷進行維護。但 Linode 的創(chuàng)始人 Chris Aker 提出的一個驚人想法最終解決了這個問題。
方法的關鍵在于為所有 CPU 標記創(chuàng)建一個列表,并將其表示為一個二進制字符串。隨后,可以使用 Bitwise and(“按位與”)運算來對比字符串。我們可以用下面這個簡單的例子來演示這個算法。下面這段 Python 代碼可以使用 “按位與” 對比兩個數(shù):
>>> 1 & 1
1
>>> 2 & 3
2
>>> 1 & 3
1
要理解為何“按位與” 運算能產(chǎn)生這樣的結(jié)果,首先需要將數(shù)字用二進制形式表示。一起看看十進制的“2” 和“3” 在用二進制形式表示的情況下,“按位與” 是如何處理的:
>>> # 2: 00000010
>>> # &
>>> # 3: 00000011
>>> # =
>>> # 2: 00000010
“按位與” 會對比二進制的“數(shù)”,也就是兩個不同數(shù)字中的“位”。該操作會從上述數(shù)字最右邊的位開始向左處理:
- “2” 和“3” 最右側(cè)(第一)位分別是“0” 和“1”,0 & 1 的按位與結(jié)果為 “0”。
- “2” 和“3” 右數(shù)第二位都是“1”,1 & 1 的按位與結(jié)果為 “1”。
- 這兩個數(shù)的所有其他位都是“0”,0 & 0 的按位與結(jié)果為 “0”。
因此完整結(jié)果的二進制表示就是 00000010,也就是十進制的 “2”。
對于實時遷移,CPU 標記完整列表會表示為一個二進制字符串,其中每一位都代表一個標記。如果一個位為 “0”,代表對應的標記不存在;如果某個位為“1”,則代表標記存在。例如,一個位可以代表 AES 標記,另一個位可以代表 MMX 標記。這些標記在二進制字符串中的位置會維護并記錄在案,隨后用于我們數(shù)據(jù)中心內(nèi)的所有計算機。
相比維護一組 if 語句來檢查某個 CPU 標記是否存在,這種列表的維護工作無疑更簡單也更高效。例如,假設總共需要追蹤并檢查 7 個 CPU 標記,這些標記可以存儲在一個 8 位數(shù)字中(多出的一位供未來進行擴展)。例如這樣的字符串可能類似于 00111011,最右側(cè)的一位代表 AES 已啟用,右數(shù)第二位代表 MMX 已啟用,右數(shù)第三位代表其他標記已啟用,以此類推。
在下文的代碼片段中,我們可以看到哪些硬件支持這些標記的組合,這段代碼可以在一個周期內(nèi)返回所有匹配的結(jié)果。如果用一組 if 語句進行對比,則需要更多周期才能獲得所需結(jié)果。如果進行實時遷移的源計算機包含 4 個 CPU 標記,這種情況下就需要 203400 個周期才能找到匹配的硬件。
實時遷移操作會在源和目標計算機上針對 CPU 標記字符串執(zhí)行 “按位與” 操作。如果兩個計算機的 CPU 標記字符串運算結(jié)果相等,意味著目標計算機是兼容的。該過程可參考下列 Python 代碼片段:
>>> # The b'' syntax below represents a binary string
>>>
>>> # The s variable stores the example CPU flag
>>> # string for the source:
>>> s = b'00111011'
>>> # The source CPU flag string is equivalent to the number 59:
>>> int(s.decode(), 2)
59
>>>
>>> # The d variable stores the example CPU flag
>>> # string for the source:
>>> d = b'00111111
>>> # The destination CPU flag string is equivalent to the number 63:
>>> int(d.decode(), 2)
63
>>>
>>> # The bitwise and operation compares these two numbers:
>>> int(s.decode(), 2) & int(d.decode(), 2) == int(s.decode(), 2)
True
>>> # The previous statement was equivalent to 59 & 63 == 59.
>>>
>>> # Because source & destination == source,
>>> # the machines are compatible
請注意,在上述代碼片段中,目標計算機比源計算機支持更多的標記。此時可以認為目標計算機是兼容的,因為源的所有 CPU 標記都已包含在目標計算機中,這一點可以由 “按位與” 運算提供保證。
我們的內(nèi)部工具可以使用上述算法得到的結(jié)果為可兼容的硬件構(gòu)建一個列表。該列表會展示給我們的客戶支持和硬件運維團隊,這些團隊可以使用我們的內(nèi)部工具來編排不同的運維任務:
- 使用該工具為特定 Linode 實例挑選兼容性最高的硬件。
- 在不指定目標的情況下發(fā)起 Linode 實例實時遷移。隨后,同一數(shù)據(jù)中心內(nèi)兼容性最高的硬件會被自動選中并立即開始遷移。
- 通過一個任務,對一臺主機上的所有 Linode 實例發(fā)起實時遷移。針對主機執(zhí)行維護任務前通常會執(zhí)行這樣的操作。我們的工具會自動為所有實例選擇目標,并為每個實例進行必要的編排。
- 將需要維護的多臺計算機指定為一個列表,我們的工具可以跨越所有主機,自動為所有實例編排實時遷移。
僅僅為了讓軟件能夠“跑起來”,就需要進行大量的開發(fā)工作……
失敗處理
軟件領域有一個話題很少被人討論:優(yōu)雅地處理失敗。軟件至少應該能夠“跑起來”。為了實現(xiàn)這一點,往往需要進行大量開發(fā)工作,實時遷移功能的開發(fā)也是如此。我們花了很多時間考慮如果該工具無法正常運行要怎么辦,以及該如何優(yōu)雅地處理這種情況。我們考慮了很多場景,并確定了具體的應對方式:
- 如果客戶希望從 Cloud Manager 訪問 Linode 的某項功能該如何處理?例如用戶可能會重啟動 Linode 或為實例連接 Block Storage Volume。
o 解決方法:客戶完全可以這樣做。實時遷移會被打斷,無法繼續(xù)處理。這種處理方法是合適的,因為實時遷移可以稍后重試。
- 目標 Linode 啟動失敗怎么辦?
o 解決方法:通知源硬件,通過專門設計的內(nèi)部工具在數(shù)據(jù)中心內(nèi)自動選擇另一個硬件。此外還將通知運維團隊,以便調(diào)查失敗的目標硬件。這種情況曾在生產(chǎn)環(huán)境中發(fā)生過,我們的實時遷移可以順利應對。
- 如果遷移過程中網(wǎng)絡連接丟失該怎么辦?
o 解決方法:自主監(jiān)測實時遷移進度,如果過去一分鐘內(nèi)沒有產(chǎn)生任何進展,將會取消實時遷移并通知運維團隊。這種情況在測試環(huán)境之外從未發(fā)生過,但我們也針對這種場景做好了充分準備。
- 如果互聯(lián)網(wǎng)的其他部分斷開了,但源和目標硬件依然在運行和通信,并且源和目標 Linode 實例都在正常運行,此時會發(fā)生什么情況?
o解決方法:如果實時遷移未進行到關鍵環(huán)節(jié),將停止實時遷移,并在稍后重試。
o如果已進行到關鍵環(huán)節(jié),則將繼續(xù)遷移。這一點很重要,因為源 Linode 已被暫停,目標 Linode 需要處于啟動狀態(tài)才能繼續(xù)恢復操作。
這些場景都已在測試環(huán)境中進行了模擬,我們認為上述行為也是不同情況下的最佳應對措施。
緊跟技術變化的步伐
成功進行了數(shù)十萬次實時遷移后,我們不免會考慮:“實時遷移的開發(fā)工作何時才能結(jié)束?” 隨著時間推移,實時遷移這項技術的使用范圍會越來越廣,并且會不斷完善,因此該項目似乎會永遠持續(xù)下去?;卮疬@個問題的一種方法是考慮該項目的大部分工作什么時候會結(jié)束。答案也很簡單:為了獲得可靠、可信賴的軟件,我們的工作還將持續(xù)很久。
隨著時間推移,Linode 會增加新的功能,我們也許要繼續(xù)努力保證實時遷移可以兼容這些功能。引入某些新功能時,可能無需圍繞實時遷移執(zhí)行新的開發(fā)工作,但我們可能依然需要測試該功能是否可以按照預期正常工作。對于某些功能,則可能需要在開發(fā)的早期階段,針對實時遷移進行必要的兼容性測試和相關工作。
和其他幾乎所有軟件類似,對于同一件事,通過不斷研究,總能發(fā)現(xiàn)更好的實現(xiàn)方法。例如,從長遠來看,為實時遷移功能開發(fā)更多模塊化的集成方法,無疑可以降低維護負擔?;蛘呶覀兩踔量赡軐崟r遷移的相關功能納入到底層代碼中,從而使其成為 Linode 一項拆箱即用的功能。
我們的團隊已經(jīng)考慮過所有這些選項,并且堅信驅(qū)動 Linode 平臺的工具是活躍的,還會繼續(xù)努力,使其不斷進化和發(fā)展。
這篇文章的內(nèi)容感覺還行吧?有沒有想要立即在 Linode 平臺上親自嘗試一下?別忘了,現(xiàn)在注冊可以免費獲得價值 100 美元的使用額度,快點自己動手體驗本文介紹的功能和服務吧↓↓↓
歡迎關注 Akamai ,第一時間了解高可用的 MySQL/MariaDB 參考架構(gòu),以及豐富的應用程序示例。