kubeedge之EdgeSite
kubeedge默认在云端部署edgeController和deviceController,然后通过websocket/quic隧道连接云端和边缘端,通过云端一个中心来统一调度应用到特定edge node上运行。但是,就像Rancher K3S的应用场景,有些时候边缘端也希望运行一套完整的k8s集群。K3S的方案只是提供了一套精简的k8s集群,而kubeedge的edgesite模式,除了运行k8s集群之外,还提供了对IOT设备的适配和支持。
架构
如上图所示,edgesite与传统的部署模式的差异在于edgeController与edged等之前边缘端的组件部署在一起;另外,k8s master虽然与下边的组件组没有画在一起,但是它们也被部署在边缘端。所以,没有了cloudHub和edgeHub之间基于隧道的通信。
这里可以看到,k8s master,也就是上面部分几乎是原生的k8s,不需要做任何改动;而下面部署就是kubeedge除cloudHub和edgeHub之外的所有组件。那代码能够直接复用?这在最后原理部分再来分析。
实验
这种架构,特别适合于已部署了一个k8s server端,在本地开发调试kubeedge组件的情况。在代码中加一些调试信息,本地快速编译,然后执行查看,方便快速理解代码逻辑。这里,我就要演示一把如何在本地运行kubeedge来对接到远端的k8s集群。
云端
我依然是使用在aliyun-vm上部署好的k3s集群,唯一需要注意的是,我在启动命令行中指定了参数,让api-server暴露了insecure-port 8080(这样本地就不需要指定各种证书信息了,当然为了安全,你也可以严格使用证书),具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
~ $ cat /etc/systemd/system/k3s.service [Unit] Description=Lightweight Kubernetes Documentation=https://k3s.io After=network-online.target [Service] ... ExecStart=/usr/local/bin/k3s \ server \ --kube-apiserver-arg insecure-bind-address=0.0.0.0 \ # 指定监听地址 --kube-apiserver-arg insecure-port=8080 # 指定insecure-port |
等待k3s启动之后,通过下面的node.yaml文件在k3s上kubectl apply -f node.yaml
添加节点,注意节点的名称,后续edge端的配置文件需要设置为该名字。
1 2 3 4 5 6 7 |
apiVersion: v1 kind: Node metadata: labels: name: edge-site node-role.kubernetes.io/edge: "" name: edge-site |
边缘端
也就是我的本地笔记本,在kubeedge源码包中,有edgesite
目录。需要进入到cmd
目录中编译代码(这需要golang环境,这块不再介绍)、准备配置文件,并执行。
1 2 3 4 5 6 7 8 9 10 11 |
~ $ ls Makefile README.md cmd conf ~ $ cd cmd # 如果是golang 1.12版本,需要手动关闭go mod模式,因为该项目是基于go vendor的 ~ $ GO111MODULE=off go build # 从上级目录复制conf目录中的配置文件到cmd目录中,并修改配置 ~ $ cp -rf ../conf ./ ~ $ ls conf edgeSite.yaml logging.yaml modules.yaml |
这里只需要修改edgeSite.yaml, 具体配置修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
mqtt: server: tcp://127.0.0.1:1883 # external mqtt broker url. internal-server: tcp://127.0.0.1:1884 # internal mqtt broker url. mode: 0 # 0: internal mqtt broker enable only. 1: internal and external mqtt broker enable. 2: external mqtt broker enable only. qos: 0 # 0: QOSAtMostOnce, 1: QOSAtLeastOnce, 2: QOSExactlyOnce. retain: false # if the flag set true, server will store the message and can be delivered to future subscribers. session-queue-size: 100 # A size of how many sessions will be handled. default to 100. controller: kube: master: http://${k3s-api-server}:8080 # 这里是k3s的insecure-port的访问URL namespace: "default" content_type: "application/vnd.kubernetes.protobuf" qps: 5 burst: 10 node_update_frequency: 10 node-id: edge-site node-name: edge-site # 该名称为预先在k8s上创建的node名称 context: send-module: metaManager receive-module: controller response-module: metaManager edged: register-node-namespace: default hostname-override: edge-site interface-name: eth0 node-status-update-frequency: 10 # second device-plugin-enabled: false gpu-plugin-enabled: false image-gc-high-threshold: 80 # percent image-gc-low-threshold: 40 # percent maximum-dead-containers-per-container: 1 docker-address: unix:///var/run/docker.sock runtime-type: docker version: 2.0.0 metamanager: context-send-group: controller context-send-module: controller edgesite: true |
然后就在cmd
目录下直接运行生成的cmd二进制文件了(记得要使用sudo,因为需要创建pods目录)。
1 |
~ $ sudo ./cmd |
原理
前面有讲到,从kubeedge的云边模式,切换到纯边缘部署,代码方面是否可以复用?答案是肯定的,这块kubeedge设计的非常好。要理解这块的原理,需要理解beehive的消息转发。
模块与消息
模块 | module name | group name |
---|---|---|
deviceTwin | twin | twin |
edged | edged | edged |
cloudHub | cloudhub | cloudhub |
edgeHub | websocket | hub |
eventbus | eventbus | bus |
servicebus | servicebus | bus |
metaManager | metaManager | meta |
edgeController | controller | controller |
deviceController | devicecontroller | controller |
模块之间通信的每一个消息都有如下关键组成:
- 消息头信息
- 消息ID
- 消息父ID(请求的返回消息也有同样的该ID)
- 时间戳(消息创建时间)
- 同步消息标识
- 消息路由信息
- 消息来源模块
- 消息目的广播组
- 操作
- 操作资源名称
在之前的架构中,metaManager的消息发送到controller需要经过edgeHub再到cloudHub,然后才能够送到controller。而当前在edgesite中直接取消掉了edgeHub和cloudHub,也就意味着,controller现在是直接将消息送到metaManager。
我们在来看一段controller读取配置文件时候的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const( DefaultContextSendModuleName = "cloudhub" DefaultContextReceiveModuleName = "controller" DefaultContextResponseModuleName = "cloudhub" ) func init() { if smn, err := config.CONFIG.GetValue("controller.context.send-module").ToString(); err != nil { ContextSendModule = constants.DefaultContextSendModuleName // cloudhub } else { ContextSendModule = smn } log.LOGGER.Infof(" send module name: %s", ContextSendModule) if rmn, err := config.CONFIG.GetValue("controller.context.receive-module").ToString(); err != nil { ContextReceiveModule = constants.DefaultContextReceiveModuleName // controller } else { ContextReceiveModule = rmn } log.LOGGER.Infof("receive module name: %s", ContextReceiveModule) if rmn, err := config.CONFIG.GetValue("controller.context.response-module").ToString(); err != nil { ContextResponseModule = constants.DefaultContextResponseModuleName // cloudhub } else { ContextResponseModule = rmn } log.LOGGER.Infof("response module name: %s", ContextResponseModule) } |
云边模式的情况下使用默认配置,也就是controller发送消息以及发送响应都是往cloudHub发送,而接收都是订阅模块名为controller的消息。在edgesite模式下,配置文件有所变化,我截取edgeSite.yaml
的关键部分:
1 2 3 4 5 6 7 8 9 10 |
controller: kube: ...... context: send-module: metaManager # controller消息不再中转,直接发送到metaManager receive-module: controller # 订阅controller模块消息 response-module: metaManager # response消息也一样 metamanager: context-send-group: controller # 边缘端消息发送到controller group context-send-module: controller # 边缘端消息发送到controller module |
至此,我们当值了解了edgesite的大概架构设计,适用场景,以及其代码的巧妙设计。当然,kubeedge还有更多目标,比如对serverless以及serviceMesh的支持等,待后续再慢慢分解。