背景
公司业务需要引入边缘计算终端,并做边缘管理与监控,故调研并最终选定 KubeEdge
技术选型
- 常规条件下,云边不在同一网段,边缘侧到云侧单向可达。
- 云侧作为管理端,需要能够将命令下达到边缘侧。包括新增/更新/删除容器实例、新增/更新/删除配置等
- 云侧作为管理端,需要能够主动与边缘侧建立连接。包括查看边缘侧容器实例日志,远程操作边缘侧容器实例等
- 边缘侧作为工作端,需要能够推送监测指标给到云侧,作为心跳等信息依据。
- 非技术性需求:运维友好、可靠性与通信安全
KubeEdge 架构
KubeEdge 架构图
云上部分
- CloudHub: CloudHub 是一个 Web Socket 服务端,负责监听云端的变化,缓存并发送消息到 EdgeHub。
- EdgeController: EdgeController 是一个扩展的 Kubernetes 控制器,管理边缘节点和 Pods 的元数据确保数据能够传递到指定的边缘节点。
- DeviceController: (暂未涉及)DeviceController 是一个扩展的 Kubernetes 控制器,管理边缘设备,确保设备信息、设备状态的云边同步。
边缘部分
- EdgeHub: EdgeHub 是一个 Web Socket 客户端,负责与边缘计算的云服务(例如 KubeEdge 架构图中的 Edge Controller)交互,包括同步云端资源更新、报告边缘主机和设备状态变化到云端等功能。
- Edged: Edged 是运行在边缘节点的代理,用于管理容器化的应用程序。(裁剪版的 kubelet)
- EventBus: (暂未涉及)EventBus 是一个与 MQTT 服务器 (mosquitto) 交互的 MQTT 客户端,为其他组件提供订阅和发布功能。
- ServiceBus: (暂未涉及)ServiceBus 是一个运行在边缘的 HTTP 客户端,接受来自云上服务的请求,与运行在边缘端的 HTTP 服务器交互,提供了云上服务通过 HTTP 协议访问边缘端 HTTP 服务器的能力。
- DeviceTwin: (暂未涉及)DeviceTwin 负责存储设备状态并将设备状态同步到云,它还为应用程序提供查询接口。
- MetaManager: MetaManager 是消息处理器,位于 Edged 和 Edgehub 之间,它负责向轻量级数据库 (SQLite) 存储/检索元数据。
云端对边缘侧常规管理
云端对边缘侧的所有常规管理均基于 Kubernetes 对象实现。
云端 CloudCore 容器实例启动时 (keadm init —advertise-address=218.108.216.78):
- CloudHub 组件开始监听
10000
端口 - CloudHub 组件开始监听
10002
端口- https://IP:10002/ca.crt: 提供云端根 CA 证书
- https://IP:10002/edge.crt: 为边缘侧签署并提供新证书
边缘侧 EdgeCore 实例启动时 (keadm join —token=404653ac1d6d82d37fa3180da1542468f7d41856e1c48c11139775941bd92e5e.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjAyODExNTR9.thxhGiJuCAF_HsPdsn8YdxYAlY6hVRmjJMcqT3D6PaI) :
- EdgeHub 组件以不验证服务器侧证书的方式向 https://IP:10002/ca.crt 请求云端根 CA 证书,并做
SHA256
校验是否匹配 token 前半部分 - EdgeHub 组件生成 CSR 请求 https://IP:10002/edge.crt 进行签署,生成边缘侧证书
- EdgeHub 组件基于边缘侧证书向云端
10000
端口以 mTLS 方式发起并建立 WebSocket 连接- 接受云端消息,路由分发到 EdgeCore 目标组件
- 集合 EdgeCore 其他组件的消息并推送到云端
- 心跳维持连接
- 特别地,云端对边缘侧的常规管理将通过 CloudHub → EdgeHub → MetaManager → EdgeD 链路下发指令
云端对边缘侧临时性管理
云端对边缘侧临时性管理动作,需要临时建立从云端到边缘侧的连接。但客观条件下,边缘侧与云端普遍单向可达。
Q: 怎么才能让云端发起的网络请求,顺利到达边缘侧设备?
A: 虚拟专用网络(VPN, Virtual Private Network)技术将专用网络延伸到公共网络上。用户能够在公共网络上访问部署在专用网络内的计算设备。
为使云端可以主动向边缘侧建立连接。CloudCore 与 EdgeCore 分别提供 CloudStream 与 EdgeStream 组件,并通过 TunnelServer 模块由边缘侧发起向云端建立一条 WebSocket 隧道,云端 TunnelServer 同时负责维护众多边缘侧设备的隧道会话。
- 用户利用 kubectl 向 Kubernetes API Server 发起查询某边缘侧容器实例日志的请求。
- Kubernetes API Server 检索 Node 资源,组装日志查询请求:
https://IP:PORT/containerLogs/{podNamespace}/{podID}/{containerName}
。请求被主机捕获并做 DNAT 转发到10003
端口。其中 IP:PORT 提取自.status.addresses[?].address
.status.daemonEndpoints.kubeletEndpoint.Port
- CloudStream 组件 StreamServer 模块接收请求,组装成消息对象(组装时会改写请求
SCHEMA://IP:PORT
为[http://127.0.0.1:10350](http://127.0.0.1:10350)
),交给 TunnelServer 模块经路由下发到指定边缘侧设备 - 某边缘侧设备 EdgeStream 组件 TunnelServer 模块接收消息并推送给 StreamServer 模块
- 某边缘侧设备 EdgeStream 组件 StreamServer 模块接收消息并重新组装 HTTP 请求,最终向 EdgeD 组件发起查询日志请求并回复响应。
包括获取容器日志、在容器中执行命令、获取监测指标三类动作都利用这套隧道链路完成。其中在容器中执行命令稍显复杂,表现为 WebSocket over WebSocket。
KubeEdge 云边安装
获取 keadm 工具
docker run --rm kubeedge/installation-package:v1.11.1 cat /usr/local/bin/keadm > /usr/local/bin/keadm && chmod +x /usr/local/bin/keadm
云侧配置
keadm init
支持一键将 KubeEdge
云上部分部署到 Kubernetes
集群中
keadm init --advertise-address=${公网IP} --profile version=v1.11.1 --kube-config=$HOME/.kube/config --set cloudCore.modules.cloudHub.dnsNames[0]="'*.local.ffutop.com'"
云侧部署完成后
kubectl -n kubeedge get pods
可以查看部署后的容器实例 cloudcore-${HASH}-${HASH}
额外地:
- 需要将部署后的服务端口
10000
、10002
、10004
映射到公网 IP - 利用 Kubernetes 集群的 rootCA 签署新的证书(需包含 alternativeNames:
*.local.ffutop.com
),改写 Kubernetes Secret 资源cloudcore
,后重启cloudcore
容器实例 - 需配置将
Node.status.addresses[?].address
的域名指向 cloudcore10003
端口绑定的 IP(需保证 Kubernetes API Server 可达)
边缘侧配置
在云侧执行 keadm gettoken
,可以获得供边缘侧节点首次连接所需的令牌
keadm gettoken
在边缘侧执行 keadm join
将一键安装 edgecore
和 mqtt
keadm join --cloudcore-ipport=${公网IP}:10000 --edgenode-name=${唯一标识}.local.ffutop.com --token=27a37ef16159f7d3be8fae95d588b79b3adaaf92727b72659eb89758c66ffda2.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTAyMTYwNzd9.JBj8LLYWXwbbvHKffJBpPd5CyxqapRQYDIXtFZErgYE
边缘侧一键启动无法改写默认配置项,故需要执行下述步骤启用 EdgeStream 组件,以实现对边缘侧的临时性管理
sed -i "s/edgeStream:\n\t\tenable: false/edgeStream:\n\t\tenable: true/" /etc/kubeedge/config/edgecore.yaml
systemctl restart edgecore.service
实际效果
可实现对节点的常规远程管控,包括:
列表查询所有边缘节点
[root@kubernetes ~]# kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
192.168.100.27 Ready master 2d8h v1.24.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=192.168.100.27,kubernetes.io/os=linux,kubernetes.io/role=master
hz001.local.ffutop.com Ready agent,edge 26h v1.22.6-kubeedge-v1.11.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=hz001.local.ffutop.com,kubernetes.io/os=linux,node-role.kubernetes.io/agent=,node-role.kubernetes.io/edge=,type=edge
hz002.local.ffutop.com Ready agent,edge 26h v1.22.6-kubeedge-v1.11.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=hz002.local.ffutop.com,kubernetes.io/os=linux,node-role.kubernetes.io/agent=,node-role.kubernetes.io/edge=,type=edge
获取节点监测指标
[root@kubernetes ~]# kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
192.168.100.27 189m 4% 4152Mi 55%
hz001.local.ffutop.com 75m 0% 1612Mi 10%
hz002.local.ffutop.com 37m 0% 487Mi 6%
向指定边缘节点部署容器实例
[root@kubernetes ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-67f548f75b-tgvq7 1/1 Running 2 (6h57m ago) 26h 192.168.0.200 hz001.local.ffutop.com <none> <none>
cloudcore-5f48f6bcc6-npctm 1/1 Running 0 131m 192.168.100.27 192.168.100.27 <none> <none>
可实现对节点的临时性远程管控,包括:
在容器中执行命令
[root@kubernetes ~]# kubectl exec -it busybox-67f548f75b-tgvq7 -- sh
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f4 brd ff:ff:ff:ff:ff:ff
inet **192.168.0.200**/24 brd 192.168.0.255 scope global noprefixroute enp1s0
valid_lft forever preferred_lft forever
inet6 fe80::c48f:2fad:58b:23cb/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: enp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f5 brd ff:ff:ff:ff:ff:ff
4: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f6 brd ff:ff:ff:ff:ff:ff
5: enp4s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f7 brd ff:ff:ff:ff:ff:ff
6: enp5s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f8 brd ff:ff:ff:ff:ff:ff
7: enp6s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:a5:12:b8:5f:f9 brd ff:ff:ff:ff:ff:ff
8: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue qlen 1000
link/ether 52:54:00:7e:16:52 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
valid_lft forever preferred_lft forever
9: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 qlen 1000
link/ether 52:54:00:7e:16:52 brd ff:ff:ff:ff:ff:ff
10: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
link/ether 02:42:3a:64:de:cc brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:3aff:fe64:decc/64 scope link
valid_lft forever preferred_lft forever
56: veth856637a@if55: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0
link/ether fe:7c:a3:af:33:49 brd ff:ff:ff:ff:ff:ff
inet6 fe80::fc7c:a3ff:feaf:3349/64 scope link
valid_lft forever preferred_lft forever
查看容器日志
[root@kubernetes ~]# kubectl logs busybox-67f548f75b-tgvq7