从黑盒角度看,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保存,或者采用业界其余经常使用的持久化方式来作。