從黑盒角度看,CloudCore就是k8s的一個插件,它是非侵入的來擴展k8s的一部分功能,將原來雲上的節點映射到邊緣端進行管理,一個CloudCore能夠管理多個邊緣節點。node
CloudCore裏面有EdgeController、DeviceController、CSI Driver、Admission Webhook以及CloudHub這些組件。web
upstream處理上行數據(與原生k8s中kubelet上報自身信息一致,這裏主要上報邊緣節點node狀態和pod狀態);downstream處理下行數據。雲邊協同採用的是在websocket之上封裝了一層消息,並且不會全量的同步數據,只會同步和本節點最相關最須要的數據,邊緣節點故障重啓後也不會re-list,由於邊緣採用了持久化存儲,直接從本地恢復。本地數據怎麼實時保持最新?就是經過downstream不斷從雲上發生變動的相關數據往邊緣去同步。後端
workload controller是k8s中管理各類應用類型的控制器,管理各類應用的生命週期,以goroutine方式運行在controller manager中,是k8s原生的組件。api
0. workload controllers(這裏用deployment controller舉例) 會watch全部deployment對象,scheduler主要watch未分配節點的pod對象,kubelet watch的過濾條件是調度到本節點的podwebsocket
圖中黃色框就是kubeedge中經過CloudCore加邊緣節點組件作等價替換的範圍。框架
前面部分都同樣,系統起來的時候,CloudCore裏面的EdgeController會watch不少資源,對於pod來講,它會watch全部pod,但它裏面會有一個過濾,可是過濾不會反映在list-watch上, 只在內部作下發的時候處理。異步
12. CloudCore收到pod變動通知後,會在內部循環中作條件的判斷,看pod中的nodeName字段是否是在它所管理的邊緣節點範圍。若是是,它會作一個事件的封裝發送到CloudHub中去socket
13. CloudHub對事件作完消息的封裝和編碼後會經過websocket通道發送到每一個邊緣的節點,邊緣節點的EdgeHub收到消息後解開去查看pod的信息,而後發送到MetaManager組件post
14. MetaManager會把收到的pod進行本地持久化編碼
15. MetaManager在把pod信息發送到Edged(輕量化的kubelet),去拉起應用
這個徹底是一個operator的典型設計和實現,有一個自定義的API對象以及有一個相應的自定義controller去管理該對象的生命週期。
關於設備的API有兩個:DeviceModel(來定義一種型號的設備),另外一個是Device設備實例的API,這兩個的關係就像是類和對象的關係。
DeviceController的內部設計跟EdgeController是很相像的,主要也是上行和下行。
邊緣存儲所須要的工做量會大不少,主要由於存儲的後端自己交互上有一些額外的操做。
通過幾種方案的選擇Kubeedge最終把kubernetes社區提供的存儲相關組件放到雲上去,把存儲方案提供商相關組件放到邊緣去。這裏有一個問題:當進行Provisioner操做和Attacher操做的時候所調用的存儲後端在邊緣,這裏採起的作法是假裝一個存儲後端,即CSI Driver from KubeEdge這個組件的外部行爲。在Provisionner看來,經過UDS訪問的CSI Driver就是一個真正的存儲方案的Driver,但其實是kubeedge裏面假裝出來。它的實際實現是把這個請求按照雲邊協同的消息格式作封裝傳給CloudHub直到邊緣的Edged,這裏CSI Volume Plugin是以前kubelet的關於存儲的一段代碼,在Edged相應對等的位置有一個csi的實現,它會將消息解開去調用處在邊緣的存儲後端。
CloudHub的實現上比較簡單。MessageDispatcher在下發元數據的時候會用到,KubeEdge的設計是每一個節點上經過websocket須要維護一個長鏈接,因此會有一個鏈接池這麼一層。在這個鏈接池之上每一個websocket會有一個對應的MessageQueue,由於從雲上下發到邊緣上的數據會比較多的,雖說比原生的kubernetes的list-watch下發的少,但同一時刻不可能只有一個數據等着下發。
EdgeController、DeviceController下發的數據會通過MessageDispatcher分發到每個節點對應的待發送隊列中,由於每一個EdgeNode有它本身關心的數據,若是是一些通用的數據好比configMap,那麼dispatcher就會往每個隊列中去丟消息的副本。待發送隊列會將消息經過websocket發送到邊緣去,而後邊緣節點再去作後續的處理。
實際上整個過程就是一個分發塞隊列的過程。
上行會更簡單一點,由於上行會直接到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保存,或者採用業界其餘經常使用的持久化方式來作。