系統級服務的無擾動升級(non distruptive upgrade,下文簡稱為熱升級)對服務的快速迭代開發有非常重要的意義。虛擬交換機(vSwitch)作為虛擬網絡的入口,需求多變,但頻繁升級斷網會影響虛機上運行的業務。此外,一般每臺宿主機上只有一個虛擬交換機,在架構上也不好做主備。因此熱升級技術對 vSwitch 的快速迭代至關重要。
本文介紹了我們在 DPDK based Open vSwtich(下簡稱 ovs-dpdk)上的熱更新技術的實踐,希望和業界同行共同探討。
現狀
- Open vSwitch(下簡稱 ovs)已有的“熱升級”方案基本是為 ovs-kernel 而實現的。在 ovs-kernel 中,vswitchd 進程是慢路徑,kernel 模塊是快速路徑。升級 vswitchd 進程時可不替換 kernel 模塊,讓大部分流量經過 kernel 中 flow cache 轉發,減少網絡擾動。升級 kernel 模塊則可能會有較長的斷網風險。這塊的詳細信息可以參考鏈接: http://docs.openvswitch.org/en/latest/intro/install/general/?highlight=hot%20upgrade#hot-upgrading
- ovs-dpdk 中,快速路徑和慢速路徑都集成在 vswitchd 進程中,如果簡單的重啟 vswtichd,由于 DPDK 的初始化(主要是大頁初始化,1G 大頁耗時約 600ms)、網卡的初始化(實測 Mellanox CX5 驅動初始化耗時接近 1s)都比較耗時,因此會有秒級斷網。
- 為了實現快速的迭代開發,降低升級導致的斷網帶來的業務擾動,需要開發 ovs-dpdk 的熱升級特性。
方案與折衷
實現熱更新有很多實現方式:
- 插件式升級(形象的說就是熱補丁):將主要的包處理邏輯變成動態鏈接庫,通過熱加載插件的方式,避免耗時的 dpdk 初始化和網卡初始化。(DPDK 主從模式也可被認為是這種方式的變體)
優點:常規升級斷網時間非常小,可以做到納秒級。
缺點:框架升級和插件升級成為了兩種升級,框架和插件之間必然有相互調用的 API 和共享 數據結構,開發人員需要保持框架和插件之間的 ABI(Application Binary Interface)一致。一個簡單的例子是流表的結構體可能會在框架和插件之間共享,如 果升級修改了流表的結構體,框架也需要升級,否則會因為不一致而導致內存錯誤。
- 雙進程升級(形象的說就是熱替換):通過硬件資源冗余,讓新老 ovs 在升級中并存,老的 ovs 繼續進行轉發,使得流量不會斷,新的 ovs 完成初始化之后,接管流量。
優點:新舊 ovs 是兩個進程,不需要做任何兼容,只需要遵守新舊進程之間的信息同步的通 信協議即可。
缺點:需要資源冗余,需要網卡和內存冗余以同時滿足新舊 ovs 的升級需求(2x 內存加 2x VF),斷網時間也會更長一些。
業界一般采用雙進程實現熱升級,這種方式的覆蓋面更廣,工程實現上相對來說也更容易一些。下圖描述了雙進程熱升級的一般流程。
OVS 熱更新的工作原理并不復雜,就是實現上需要考慮不少瑣碎的工程細節。主要通過代碼上精細的控制使得升級斷網時間較小。
二段式設計
我們把熱升級的整個流程分成兩個階段(二段式),這里的設計有點借鑒虛擬機進行熱遷移的過程。在第一階段,網絡的轉發服務不會停止,并且一旦升級出現故障,整個系統是可以自動回滾的。只有到達第二階段,網絡會出現斷網。在第二階段,如果出現問題,只能斷網重啟服務,系統不再具有回滾能力。在實現上,我們給 ovs 開啟了一個 JSON-RPC 服務用于熱升級。當 ovs 進程啟動時,會自動檢測是否有老進程的存在。如果存在,則主動通過 JSON-RPC 發起熱升級請求。接收到熱升級請求的 ovs 進程則開啟兩階段升級:
- 第一階段:
- 老 ovs 進程釋放各種獨占資源:比如 ovsdb 的數據庫鎖,pid 文件改名,unixctl server path 等資源。此時老 ovs 不能通過 ovsdb 獲取最新配置,但是 PMD 線程依舊運行,轉發并未停止。
- 釋放資源之后,新老進程開始進行狀態同步,主要是一些 OVS 的 megaflow 同步,網絡 tap 設備的 fd 同步等。
- 同時,老的 ovs 會將所有的 OpenFlow 規則進行備份。
- 這一切完成之后,老進程會返回 JSON-RPC 的請求的響應。如果上述過程無問題,則返回成功,升級繼續。否則返回失敗,新進程會退出,升級失敗。同時老進程會狀態回滾,將之前釋放的資源又重新申請回來,回到正常的工作狀態。
- 新進程獲得各種狀態信息,開始正常初始化。包括 DPDK 初始化內存,ovs 從 ovsdb 中獲取網橋、網卡等各種配置開始初始化等。在這個過程中,如果出現異常,新進程 crash,會導致 JSON-RPC 這個 unix socket 連接中斷。一旦老進程檢測到這個連接中斷,就認為新進程初始化失敗,自動回滾。
- 新進程加載之前備份的 OpenFlow 規則。這之間的 OpenFlow 規則變更,會由 local controller 記錄并下發。
- 新進程發起第二階段 JSON-RPC 請求。
- 第二階段:
- 老進程退出。
- 新進程開啟 pmd 線程開始轉發。
- 升級結束。
下圖簡單的描述了升級時序圖:
由升級時序圖可以看出,熱升級第一階段里新 ovs 進程完成了最耗時的初始化工作,同時老 ovs 進程一直在進行轉發,并沒有斷網。斷網只是在第二階段老 ovs 退出和新 ovs 啟動 PMD 線程之間發生。
在升級過程中,我們利用了 MLNX 網卡一個特性:同一個網卡設備可以被兩個獨立的 dpdk 進程同時打開,并且流量會被同時鏡像到兩個進程中去。如果使用其他的網卡,可以通過多 VF 配合流表規則切換的方法達到同樣效果。這里就不再展開敘述了。
斷網時間評估
我們考察了兩種虛擬網卡后端的斷網時間:
- 采用 representer + VF 的方式,
- 采用 vhost-user + virtio 的方式。
測試方法
兩臺宿主機上兩臺虛機互相 ping,使用ping -i 0.01。兩次 ping 之間相隔 10ms,最后統計在升級中,ping 包未回的數量即表示升級斷網時間。 iperf -i 1測試,觀察 TCP 吞吐是否會受到影響。
representer + VF 方式
ping 測試
10 次試驗結果如下:
1524 packets transmitted, 1524 received, +36 duplicates, 0% packet loss, time 16747ms
623 packets transmitted, 622 received, +29 duplicates, 0% packet loss, time 6830ms
662 packets transmitted, 662 received, +30 duplicates, 0% packet loss, time 7263ms
725 packets transmitted, 724 received, +28 duplicates, 0% packet loss, time 7955ms
636 packets transmitted, 635 received, +28 duplicates, 0% packet loss, time 6973ms
752 packets transmitted, 750 received, +27 duplicates, 0% packet loss, time 8251ms
961 packets transmitted, 961 received, +31 duplicates, 0% packet loss, time 10551ms
737 packets transmitted, 737 received, +29 duplicates, 0% packet loss, time 8084ms
869 packets transmitted, 869 received, +27 duplicates, 0% packet loss, time 9543ms
841 packets transmitted, 840 received, +28 duplicates, 0% packet loss, time 9228ms
發現最多出現 1 個丟包,說明斷網時間最長 10ms。但是發現了很多 duplicates 的包,這個是因為 mlnx 網卡在 switchdev 模式下有一個 bug:當出現雙進程時,本應該流量被鏡像到另一個進程,結果 mlnx 網卡讓一個進程收到了兩個重復包,而另一個進程沒有包。目前 mlnx 已經確認了這個問題。
iperf 測試
觀測到升級期間,有 1s 速率減半,由滿速率 22Gbps 到 13Gbps,估計是和 MLNX bug 引發的重傳包和升級斷網有關。1s 之后迅速回到 22Gbps。
vhost-user + virtio 模式
ping 測試
斷網時間在 70~80ms 左右。通過對日志分析,發現是老進程在退出時,反復進行了 vhost 重連導致,這塊通過繼續優化,斷網時間會進一步降低。這里就不再展開了。
iperf 測試
和 VF 方式結果類似,也是吞吐會有一定的下降,升級完成后立刻恢復。