雲端組件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
- 通過命令行創建一個Deployment
- api server就會做對應的存儲(所有集群狀態的查詢都是通過api server與etcd的交互進行的)
- etcd會有一個反饋給api server
- api server會把這個事件反饋給訂閱了這個事件的組件,比如deployment相關事件會反饋給deployment controller組件
- deployment controller watch到變化后,根據定義文件去發出創建相應pod事件
- api server修改etcd中pod的信息
- etcd接着反饋給api server資源變更事件
- api server把pod create事件反饋給scheduler
- scheduler會根據它加載的調度策略在集群中找到一個合適節點 ,更新pod字段信息
- api server修改etcd中pod信息
- etcd返回給api server pod Bound(update)事件
- 訂閱相關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發送到邊緣去,然后邊緣節點再去做后續的處理。
實際上整個過程就是一個分發塞隊列的過程。

- MessageDispatcher: 下行消息分發中心,也是下行消息隊列的生產者,DispatchMessage函數中實現
- NodeMessageQueue:每個邊緣節點有一個專屬的消息隊列,總體構成一個隊列池,以Node UID作為區分,ChannelMessageQueue結構體實現
- WriteLoop:負責將消息寫入底層鏈接,是上述消息隊列的消費者
- Connection server:接收邊緣節點訪問,支持websocket協議和quick協議連接
- Http server:為邊緣節點提供證書服務,如證書簽發與證書輪轉
上行-通過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保存,或者采用業界其他常用的持久化方式來做。

