KubeEdge雲邊協同設計原理

尹瑞星發表於2021-04-02

雲端元件CloudCore與k8s Master的關係

 

從黑盒角度看,CloudCore就是k8s的一個外掛,它是非侵入的來擴充套件k8s的一部分功能,將原來雲上的節點對映到邊緣端進行管理,一個CloudCore可以管理多個邊緣節點。

CloudCore裡面有EdgeController、DeviceController、CSI Driver、Admission Webhook以及CloudHub這些元件。

 

EdgeController詳解

 

upstream處理上行資料(與原生k8s中kubelet上報自身資訊一致,這裡主要上報邊緣節點node狀態和pod狀態);downstream處理下行資料。雲邊協同採用的是在websocket之上封裝了一層訊息,而且不會全量的同步資料,只會同步和本節點最相關最需要的資料,邊緣節點故障重啟後也不會re-list,因為邊緣採用了持久化儲存,直接從本地恢復。本地資料怎麼實時保持最新?就是通過downstream不斷從雲上發生變更的相關資料往邊緣去同步。

kubernetes中拉起應用的過程

 

workload controller是k8s中管理各種應用型別的控制器,管理各種應用的生命週期,以goroutine方式執行在controller manager中,是k8s原生的元件。

  0. workload controllers(這裡用deployment controller舉例) 會watch所有deployment物件,scheduler主要watch未分配節點的pod物件,kubelet watch的過濾條件是排程到本節點的pod

  1. 通過命令列建立一個Deployment
  2. api server就會做對應的儲存(所有叢集狀態的查詢都是通過api server與etcd的互動進行的)
  3. etcd會有一個反饋給api server
  4. api server會把這個事件反饋給訂閱了這個事件的元件,比如deployment相關事件會反饋給deployment controller元件
  5. deployment controller watch到變化後,根據定義檔案去發出建立相應pod事件
  6. api server修改etcd中pod的資訊
  7. etcd接著反饋給api server資源變更事件
  8. api server把pod create事件反饋給scheduler
  9. scheduler會根據它載入的排程策略在叢集中找到一個合適節點 ,更新pod欄位資訊
  10. api server修改etcd中pod資訊
  11. etcd返回給api server pod Bound(update)事件
  12. 訂閱相關node name pod的kubelet就會收到事件通知,然後執行拉起動作

圖中黃色框就是kubeedge中通過CloudCore加邊緣節點元件做等價替換的範圍。

KubeEdge拉起邊緣應用

前面部分都一樣,系統起來的時候,CloudCore裡面的EdgeController會watch很多資源,對於pod來說,它會watch所有pod,但它裡面會有一個過濾,但是過濾不會反映在list-watch上, 只在內部做下發的時候處理。

  12. CloudCore收到pod變更通知後,會在內部迴圈中做條件的判斷,看pod中的nodeName欄位是不是在它所管理的邊緣節點範圍。如果是,它會做一個事件的封裝傳送到CloudHub中去

  13. CloudHub對事件做完訊息的封裝和編碼後會通過websocket通道傳送到每個邊緣的節點,邊緣節點的EdgeHub收到訊息後解開去檢視pod的資訊,然後傳送到MetaManager元件

  14. MetaManager會把收到的pod進行本地持久化

  15. MetaManager在把pod資訊傳送到Edged(輕量化的kubelet),去拉起應用  

 

Device CRD和DeviceController的設計

這個完全是一個operator的典型設計和實現,有一個自定義的API物件以及有一個相應的自定義controller去管理該物件的生命週期。

DeviceModel裝置模版抽象

關於裝置的API有兩個:DeviceModel(來定義一種型號的裝置),另一個是Device裝置例項的API,這兩個的關係就像是類和物件的關係。

 

 DeviceInstance裝置例項的定義

 

 

DeviceController

 

 DeviceController的內部設計跟EdgeController是很相像的,主要也是上行和下行。

邊緣儲存的整合與設計

邊緣儲存所需要的工作量會大很多,主要因為儲存的後端本身互動上有一些額外的操作。

k8s推薦的CSI部署方式

 

 KubeEdge中CSI部署方案

 

經過幾種方案的選擇Kubeedge最終把kubernetes社群提供的儲存相關元件放到雲上去,把儲存方案提供商相關元件放到邊緣去。這裡有一個問題:當進行Provisioner操作和Attacher操作的時候所呼叫的儲存後端在邊緣,這裡採取的做法是偽裝一個儲存後端,即CSI Driver from KubeEdge這個元件的外部行為。在Provisionner看來,通過UDS訪問的CSI Driver就是一個真正的儲存方案的Driver,但實際上是kubeedge裡面偽裝出來。它的實際實現是把這個請求按照雲邊協同的訊息格式做封裝傳給CloudHub直到邊緣的Edged,這裡CSI Volume Plugin是之前kubelet的關於儲存的一段程式碼,在Edged相應對等的位置有一個csi的實現,它會將訊息解開去呼叫處在邊緣的儲存後端。

 

CloudHub與EdgeHub的通訊機制

下行-通過CloudHub下發後設資料

 

 

CloudHub的實現上比較簡單。MessageDispatcher在下發後設資料的時候會用到,KubeEdge的設計是每個節點上通過websocket需要維護一個長連線,所以會有一個連線池這麼一層。在這個連線池之上每個websocket會有一個對應的MessageQueue,因為從雲上下發到邊緣上的資料會比較多的,雖然說比原生的kubernetes的list-watch下發的少,但同一時刻不可能只有一個資料等著下發。

EdgeController、DeviceController下發的資料會經過MessageDispatcher分發到每一個節點對應的待傳送佇列中,因為每個EdgeNode有它自己關心的資料,如果是一些通用的資料比如configMap,那麼dispatcher就會往每一個佇列中去丟訊息的副本。待傳送佇列會將訊息通過websocket傳送到邊緣去,然後邊緣節點再去做後續的處理。

實際上整個過程就是一個分發塞佇列的過程。

上行-通過CloudHub重新整理狀態

 

上行會更簡單一點,因為上行會直接到Controller裡去,沒有經過佇列的處理了,controller在通過api-server去做相應的變更通知,這裡controller本身內部會有訊息處理的佇列 。因此上行時候不會經過待傳送佇列以及MessageDispatcher。

CloudHub與Controller的通訊是用beehive模組間通訊的框架來實現的。

訊息格式的封裝

 

訊息格式的封裝是雲邊協同設計的核心,雲邊協同裡面封裝的訊息其實是K8s的API物件,kubernetes中採用的是宣告式api設計,物件上某個欄位的變化實際上都是一個期望值或者是最終的一個狀態。之所以選擇把整個k8s的api物件原封不動的丟進Message結構體裡,就是為了保留這種設計的理念,即最終物件的變化需要產生什麼樣的動作,相應元件會去處理比較來產生差異,然後去更新,而不是提前計算好差異在往下丟。提前計算好差異往下丟帶來的問題是:計算差異的時候需要感知befor、after這兩個物件,before物件的獲取會有一個時間差,如果在獲取處理的過程中這個物件被其他元件更新發生變化,這時候計算的差異就是不準的。所以把這個物件原封不動往下丟,丟到最後在去做diff。

宣告式 API是 Kubernetes 專案編排能力“賴以生存”的核心所在:
首先,“宣告式”指的就是隻需要提交一個定義好的 API 物件來“宣告”,所期望的狀態是什麼樣子;
其次,“宣告式 API”允許有多個 API 寫端,以 PATCH 的方式對 API 物件進行修改,而無需關心本地原始 YAML 檔案的內容;
最後,也是最重要的,有了上述兩個能力,Kubernetes 專案才可以基於對 API 物件的增、 刪、改、查,在完全無需外界干預的情況下,完成對“實際狀態”和“期望狀態”的調諧 (Reconcile)過程。

 

Header主要用來存message的id和parentID用來形成會話的資訊,比如邊緣發起一個查詢,雲端做響應 。message是多次的割裂的請求,parentId用來說明是對哪一個message的響應來形成一個關聯。Sync這個欄位是一個比較高階的設計:雖然大多數情況下訊息的傳送都是非同步的,但也會有同步處理響應的情況,比如前面儲存方案的整合,provisioner和attacher對於儲存後端的呼叫是一個同步呼叫,它需要立刻獲取這個volume是否成功的被儲存後端獲取。

Route結構體主要存訊息的來源和目的模組,Resouce欄位的作用:儲存所操作物件的資訊,因為一個完整的kubernetes api物件資料量還是比較大的,序列化/反序列化的代價還是比較高的,用Resource欄位來標記它操作的是kubernetes中的哪個API物件,這樣在訊息的轉發處理時候看一下Resource中的內容就可以直接處理訊息的轉發,以比較低的代價完成訊息的路由。Operation是http中動作欄位put/post/get等。

 

訊息可靠性設計

 

 

KubeEdge基於websocket能夠實現高時延、低頻寬的情況下能夠良好的工作,但是websocket本身不能保證訊息不丟失,因此在雲邊協同過程中還需要引入可靠性的機制。功能還在開發中,目前這個設計的理念。其實它有好幾種方案:一種是雲上能夠主動發現邊緣是否連線正常,可以選擇在協議層做一些設計;另一種就是採用一種簡單的響應方式來確認。

這裡需要權衡的幾點:訊息的丟失對雲和邊的狀態的一致性會不會有影響;重複傳送的資料會不會有影響。

雖然KubeEdge保留了Kubernetes宣告式API的理念,但是它精簡了很多不必要的master和node的互動過程,因此如果你少發了一次訊息,並不能在一個很短的週期內有一個新的一次互動把這個更新的內容帶到節點上去。所以帶來的問題是:如果你丟失了一個訊息,你可能很長的一段時間雲和邊的狀態是不同步的,當然最簡單粗暴的解決方式就是重發;第二個問題是如果重複發了訊息會怎樣,這就體現了宣告式API的好處,因為宣告式API體現的是最終的一個狀態,而不是一個差異值或變化值,那麼重複傳送的資料並不會造成什麼影響。

基於這幾點考慮呢,可以去引入ACK機制,邊緣節點收到訊息後發一個ACK,雲上收到ACK後就認為訊息傳送成功了,否則會反覆Retry。如果發生CloudHub當機等使得訊息沒有傳送成功,那麼這些訊息就會丟失,未來CloudHub還會做水平擴容,儘可能做一個無狀態的實現,把訊息做一個持久化,把傳送成功的訊息刪除掉,目前這一塊在選型上還沒有確定,初步考慮是新引入一個CRD,通過CRD儲存,或者採用業界其他常用的持久化方式來做。

 

相關文章