OpenStack Nova如何支持Live Upgrade
寫在最前面:
OpenStack半年一個版本,升級是一個怎么也躲不開的問題,總是在想如何在復(fù)雜的OpenStack部署環(huán)境中做到業(yè)務(wù)平滑升級,同時又不影響到用戶,當前的OpenStack中又有那些特性支持live-upgrade,今天以Nova作為一個開端,看看Nova中是怎么做的。
代碼基線:Kilo
官方文檔
如今的OpenStack官方文檔已經(jīng)相對比較全面了,Operations Guide中就有一個章節(jié)專門描述了升級相關(guān)的指導(dǎo),并舉了幾個版本升級的例子,雖然比較簡單,但是大體的框架和步驟都已經(jīng)有了?;镜乃悸肪褪牵盒∫?guī)模驗證 -> 備份配置文件和數(shù)據(jù)庫 -> 更新配置文件 -> 利用包管理器升級安裝包 -> 停止進程 -> 更新數(shù)據(jù)庫 -> 啟動進程 -> 再次驗證。推薦大家都仔細閱讀一下。
upgrade levels
官方文檔中專門提到了一個概念 upgrade levels,這里涉及到Nova的***個live-upgrade特性。
先稍微繞開一點,當前由于對象化的引入,nova中的api, scheduler, conductor, compute四個進程都存在循環(huán)依賴,所以不可能簡單的劃分出一個依賴樹,從樹的葉子節(jié)點開始升級,從而保證API兼容性,這種升級的方式在nova中不可行。有循環(huán)依賴也就是說,從任何一端開始升級,都有可能發(fā)生高版本client向低版本server發(fā)送消息的可能。這里就需要用到upgrade levels。
簡單來說就是一個rpcapi端的版本控制機制,在升級之前,rpc client端設(shè)置一個版本閥值,當rpcapi需要發(fā)送消息時,通過can_send_version方法判斷,如果超過閥值就做自動降級處理,沒有做降級處理的消息,會被禁止發(fā)送,具體實現(xiàn)可以參考oslo_messaging的代碼。例子如下:
- def shelve_offload_instance(self, ctxt, instance,
- clean_shutdown=True):
- msg_args = {‘instance’: instance}
- if self.client.can_send_version(’3.37′):
- version = ’3.37′
- msg_args['clean_shutdown'] = clean_shutdown
- else:
- version = ’3.0′
- cctxt = self.client.prepare(server=_compute_host(None, instance),
- version=version)
- cctxt.cast(ctxt, ‘shelve_offload_instance’, **msg_args)
如果我們計劃從juno升級到kilo,首先需要設(shè)置所有rpcapi的upgrade levels為juno,這樣當我們升級的過程中,如果一個已經(jīng)升到kilo版本的conductor向juno版本的compute發(fā)送rpc消息,還是會使用和juno版本rpcapi接口兼容的消息。
#p#
API序列化
每一個rpcapi的都有版本號,每次的api修改都需要調(diào)整版本號,nova通過版本號來判斷client側(cè)和server側(cè)是否兼容。
整個rpc調(diào)用分為兩個步驟,rpcapi側(cè)將整個rpc調(diào)用序列化,然后在rpc server也就是manager側(cè)收到消息,再反序列化執(zhí)行。整個rpc message除了context之外,message字典結(jié)構(gòu)體中還包括四個部分:
- method
- args
- version
- namespace
version標識了rpcapi的版本,在rpc server側(cè)直接通過method名稱查找manager中的函數(shù),將args參數(shù)反序列化之后,調(diào)用method函數(shù)執(zhí)行。調(diào)用之前也會在 server側(cè),檢查rpcapi的version是否和manager的實現(xiàn)版本兼容,如果不兼容拋出異常。namespace用于區(qū)分 conductor中的ConductorAPI和ConductorTaskAPI。
可以看到如果我們修改代碼,調(diào)整了rpcapi的接口參數(shù),就一定要同時更新client/server側(cè)的版本號,并在rpc server的接口實現(xiàn)側(cè)提供參數(shù)默認值。
在rpcapi的層面,低版本client調(diào)用高版本server,兼容支持;高版本client調(diào)用低版本server,通過upgrade levels自動降級,未做自動降級處理的禁止發(fā)送,拋出異常。
API參數(shù)序列化
除了rpcapi在升級過程中需要考慮升級兼容性,args參數(shù)中的NovaObject對象,也需要考慮升級兼容性。所有的NovaObject都有版本號跟蹤每一次的修改。
在rpcapi發(fā)送消息之前,首先對接口參數(shù)進行序列化,如果參數(shù)是NovaObject類型,都通過NovaObject的 obj_to_primitive方法轉(zhuǎn)化成dict類型之后,保存在args,如果是原始類型,直接保存在args中。rpc server接收到消息之后,將args中的接口參數(shù)反序列化,如果是NovaObject對象,通過obj_from_primitive接口反序列化。
NovaObject類中存在一個_obj_classes類屬性,其中會保存一個NovaObject的多個版本的實現(xiàn),通過版本號從大到小排序,***版本就是當前代碼實現(xiàn)版本,較小版本由升級過程中低版本進程發(fā)送而來。
NovaObjectSerializer.deserialize_entity用于反序列化對象,如果server端當前實現(xiàn)版本大于發(fā)送過來的版本,直接兼容,反序列化對象。如果發(fā)送的版本大于server端實現(xiàn)版本,忽略最末端版本號,再次嘗試反序列化,如果仍然不能成功,就需要調(diào)用 conductor.object_backport方法,嘗試降級處理,將args參數(shù)轉(zhuǎn)化為server端可以處理的object。
#p#
升級順序
升級順序可能不同人分析有不同的結(jié)果,我這里提供一個建議順序和分析過程。
社區(qū)的operation guide中推薦的升級方式為先controller節(jié)點,然后compute節(jié)點。在我看來,這樣升級有一個好處就是可以盡早的發(fā)現(xiàn)問題,降低風(fēng)險和回退成本。controller節(jié)點進程最多也最復(fù)雜,首先升級可以盡早的發(fā)現(xiàn)問題,這時回退也只需要回退controller,如果有100個 compute都升級完成了,再升級controller,如果升級失敗,就需要回退100個compute節(jié)點。
先升級controller有個問題,就是client端的版本要大于server端,也就是4.0的conductor可能調(diào)用3.0的 compute,需要用upgrade_levels控制。如果compute進行數(shù)據(jù)庫操作,就會執(zhí)行NovaObject的方法,***調(diào)用到 conductor的object_class_action方法,這個方法有特殊處理,會將返回值版本轉(zhuǎn)化為調(diào)用端的版本。
綜合這幾點,推薦的一個升級順序為:
- conductor
- scheduler
- compute
- api
***升級api進程的原因是,希望所有新增接口和新增參數(shù)能在整個環(huán)境都支持的情況下,再提供給用戶使用。
升級順序并無一個定論,每種順序各有利弊,歡迎討論。升級在OpenStack中一直都是一個大問題,沒有***理論,只有***實踐,需要不斷的嘗試總結(jié)交流,希望能為OpenStack的升級成熟,盡自己的一份力。