概念
云边通信:云端容器,通过serviceName/clusterIP,访问边缘容器;
边云通信:相对上面而言,反过来,边缘容器,通过serviceName/clusterIP,访问云端容器;
边边通信:边缘主机上的容器,通过serviceName/clusterIP,访问该边缘主机或其他边缘主机的容器;
edgemesh架构
kubeedge相比kubernetes,主要的提供了跨子网构建集群的方案;其最强大的特点就是让kubernetes不在局限于一个物理机房之内;集群中的主机节点可以在不同的私有局域网内。
不同私有网络之下的主机要进行相互通信,最主要的就是受限于防火墙NAT形态:
全锥形NAT
受限锥形NAT
端口受限锥型NAT
对称型NAT
上面的各组网方案之中,依次安全性增强,在对称锥形的网络中,所有主机都在各自的防火墙背后,直接穿透访问就非常困难,所以直接edgemesh提供了两种穿透访问方案:
部署
edgemesh-server: Deployment 云端
edgemesh-agent: DaemonSet 云端+边缘
直接穿透访问
对于全锥形NAT、受限性NAT、部分端口受限锥形NAT访问可以直接采用如下方案;
中继穿透访问
对于直接穿透访问不了的请求,edgemesh-agent就会将流量转发到edgemesh-server上进行中继,因为所有的edgemesh-agent在启动后,就会于云端的edgemesh-server建立一个隧道网络;
edgemesh原理
在kubernetes+kubeedge组成的边缘云集群上,无论云端or边缘主机,都部署了edgemesh-agent,其有两个主要功能:
对外暴露53端口,实现了通过serviceName查询k8s:service配置获取相应clusterIP的功能;
-
在主机上创建一个DummyDevice网卡,默认暴露在169.254.96.16:40001上一个端口,并且通过iptables配置了PREROUTING和OUTPUT的EDGE-MESH链,用于劫持所有的流量访问。
在如下的iptables的NAT表设置,可以看出在EDGE-MESH链中配置的相关服务目标地址:即不需要EDGEMESH代理的服务就会RETURN到它的下一行去做代理,即走了DOCKER->其他的代理服务(kube-proxy、kube-router...),这些服务的声明是要在k8s创建servcie的时候,增加noproxy=edgemesh的标签,就会被edgemesh-agent写入到主机iptables的这个位置。这些服务其实就是不需要云边通信的服务;
当需要被edgemesh-agent代理的流量,即我们认为的需要云边通信、边云通信、边边通信,劫持到流量,流量会被内核劫持转发到169.254.96.16:40001上,然后经过edgemesh-agent的20006端口与远端的edgemesh-agent:20006或edgemesh-server:20004端口进行转发,最终目标端的edgemesh再将流量转发到被访问的业务容器中;
[root@edge001 ~]# iptables -t nat -nvL --line
Chain PREROUTING (policy ACCEPT 9734 packets, 833K bytes)
num pkts bytes target prot opt in out source destination
1 32317 3700K EDGE-MESH all -- * * 0.0.0.0/0 0.0.0.0/0 /* edgemesh root chain */
2 14436 930K DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 9734 packets, 833K bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 278 packets, 18244 bytes)
num pkts bytes target prot opt in out source destination
1 893 60171 EDGE-MESH all -- * * 0.0.0.0/0 0.0.0.0/0 /* edgemesh root chain */
2 47 3390 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 278 packets, 18244 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
2 0 0 RETURN all -- * * 192.168.122.0/24 224.0.0.0/24
3 0 0 RETURN all -- * * 192.168.122.0/24 255.255.255.255
4 0 0 MASQUERADE tcp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
5 0 0 MASQUERADE udp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
6 0 0 MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24
Chain DOCKER (2 references)
num pkts bytes target prot opt in out source destination
1 27 1896 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
Chain EDGE-MESH (2 references)
num pkts bytes target prot opt in out source destination
1 0 0 RETURN all -- * * 0.0.0.0/0 10.254.165.225 /* ignore kubeedge/cloudcore service */
2 0 0 RETURN all -- * * 0.0.0.0/0 10.242.247.106 /* ignore kube-system/traefik-web-ui service */
3 0 0 RETURN all -- * * 0.0.0.0/0 10.243.116.144 /* ignore kube-system/traefik-ingress-service service */
4 0 0 RETURN all -- * * 0.0.0.0/0 10.255.163.200 /* ignore kube-system/prometheus-service service */
5 0 0 RETURN all -- * * 0.0.0.0/0 10.240.83.24 /* ignore kube-system/metrics-server service */
6 0 0 RETURN all -- * * 0.0.0.0/0 10.254.210.250 /* ignore kube-system/kube-dns service */
7 0 0 RETURN all -- * * 0.0.0.0/0 10.240.0.1 /* ignore default/kubernetes service */
8 0 0 EDGE-MESH-TCP tcp -- * * 0.0.0.0/0 10.240.0.0/12 /* tcp service proxy */
Chain EDGE-MESH-TCP (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 to:169.254.96.16:40001
正常访问情况分析
直接穿透请求端抓包
环境:
mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 5.2.14-1.el7.elrepo.x86_64
mkedge2 10.201.82.131 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64
用例:
在mkedge2上启动tcp-echo-cloud容器,通过mkedge3上的busybox容器进行远程telnet tcp-echo-cloud 2701
说明:
从实际流量抓包看来看,从办公室机房能够直接访问到天坛机房主机,不需要中继;
抓包详情
mkedge3发起请求tcp抓包
mkedge2上接收载荷tcp抓包
中继穿透请求端抓包
环境:
mkmaster1 10.200.50.118 国贸机房 edgemesh-server, edgemesh-agent role: master kernel: 5.2.14-1.el7.elrepo.x86_64
mkworker3 10.202.42.112 亦庄机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64
mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 3.10.0-514.el7.x86_64
用例:
在mkworker3 上启动tcp-echo-cloud容器,通过mkedge3上的busybox容器进行远程telnet tcp-echo-cloud 2701
说明:
从实际流量抓包看来看,从办公室机房不能直接访问到亦庄机房主机,需要国贸机房的edgemesh-server中继;
抓包详情
mkedge3发起请求tcp抓包
mkmaster1进行中继tcp抓包
mkworker3上接收载荷tcp抓包
失败访问情况分析
先说结论,怀疑主机默认内核4.15.6-1.el7.elrepo.x86_64 (centos7) 存在调用系统内核函数getsockopt的严重bug!!!
失败穿透请求抓包
环境:
mkmaster1 10.200.50.118 国贸机房 edgemesh-server, edgemesh-agent role: master kernel: 5.2.14-1.el7.elrepo.x86_64
mkworker1 10.201.82.139 天坛机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64
mkworker2 10.201.83.74 天坛机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64
mkedge1 10.201.82.148 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64
mkedge2 10.201.82.131 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64
mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 3.10.0-514.el7.x86_64
用例:
在mkworker1、mkworker2、mkedge1、mkedge2上启动同时启动tcp-echo-cloud容器,通过mkedge1上的busybox容器进行远程访问这些服务,发现一个都不通;
现状:
从实际流量抓包看来看,从业务容器发送请求给到edgemesh-agent:40001端口,之后流量就不见了踪影;
在反覆启停这些容器,经过多次测试,的确无法进行正常通信;
怀疑是iptables的问题,因丢失了DOCKER链之后,重启docker服务也无法创建规则链;
重启edgemesh-agent服务,发现其容器无法被清除,一直是Running状态,如果docker rm -f强制停止,会导致edgemesh-agent在宿主机上留下僵尸进程;
[图片上传失败...(image-2899d1-1641166544225)]
初始排查阶段
究竟是Go的版本不对?容器镜像问题?逐个进行了排查:
给edgemesh-agent:proxy.go的Run()函数中,realServerAddress(&conn)调用之前,增加了打印日志,同时在这个函数调用中,增加了一些日志输出标记位,重新制作容器进行测试,发现输出日志555
,但未输出666
,同时进程状态异常变为了D
:
func (proxy *EdgeProxy) Run() {
// ensure ipatbles
proxy.Proxier.Start()
// start tcp proxy
for {
conn, err := proxy.TCPProxy.Listener.Accept()
if err != nil {
klog.Warningf("get tcp conn error: %v", err)
continue
}
klog.Info("!!! has workload !!!")
ip, port, err := realServerAddress(&conn)
...
}
}
// realServerAddress returns an intercepted connection's original destination.
func realServerAddress(conn *net.Conn) (string, int, error) {
tcpConn, ok := (*conn).(*net.TCPConn)
if !ok {
return "", -1, fmt.Errorf("not a TCPConn")
}
klog.Info("111")
file, err := tcpConn.File()
if err != nil {
return "", -1, err
}
defer file.Close()
klog.Info("222")
// To avoid potential problems from making the socket non-blocking.
tcpConn.Close()
*conn, err = net.FileConn(file)
if err != nil {
return "", -1, err
}
klog.Info("333")
fd := file.Fd()
klog.Info("444")
var addr sockAddr
size := uint32(unsafe.Sizeof(addr))
err = getSockOpt(int(fd), syscall.SOL_IP, SoOriginalDst, uintptr(unsafe.Pointer(&addr)), &size)
if err != nil {
return "", -1, err
}
klog.Info("777")
var ip net.IP
switch addr.family {
case syscall.AF_INET:
ip = addr.data[2:6]
default:
return "", -1, fmt.Errorf("unrecognized address family")
}
klog.Info("888")
port := int(addr.data[0])<<8 + int(addr.data[1])
if err := syscall.SetNonblock(int(fd), true); err != nil {
return "", -1, nil
}
return ip.String(), port, nil
}
func getSockOpt(s int, level int, name int, val uintptr, vallen *uint32) (err error) {
klog.Info("555")
_, _, e1 := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if e1 != 0 {
err = e1
}
klog.Info("666")
return
}
然后就开始怀疑是这个内核调用出现了问题,先编写了一个go程序:https://gitee.com/Hu-Lyndon/gogetsockopt.git
如下是在mkedge3宿主机上,kernel: 3.10.0-514.el7.x86_64,一个终端上先将程序启动监听18011端口,然后在另外一个连接mkedge3终端上,直接telnet这个端口,程序能够正确通信,并输出打印日志address: 192.168.127.1:18011
如下是在mkedge1上测试,kernel: 4.15.6-1.el7.elrepo.x86_64, 发现telnet之后,无法输出打印日志,并且程序在第三个终端窗口里也无法kill -9
,并且进程状态变为了D
;
不死心,程序在mkedge2上依然如上;
不死心,找了台内核升级为kernel: 4.19.12-1.el7.elrepo.x86_64的主机,结果是正确的;
再次排查阶段
不死心:虽然严重怀疑了kernel: 4.15.6-1.el7.elrepo.x86_64,那么就近是Go的问题,还是系统内核的问题呢?于是又写了一个C程序来测试:https://gitee.com/Hu-Lyndon/unix-network-programming,在compile-on-kernel4.15.6分支的/sockopt/sockoptchk.c
依然出现了上面出现的问题,说明应该就是kernel的问题了,需要推荐运维以后不要安装这个版本的内核了,目前看4.19.12以上的版本或者干脆就是3.10的内核都是打过patch的。
相关资料
https://go.dev/play/p/GMAaKucHOr
https://elixir.bootlin.com/linux/latest/C/ident/sys_getsockopt
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/socket.c