【本文专栏于[头条号]、[知乎]同步发布,可关注同名账号订阅相关文章,每周固定更新】
【本篇文章共计4763字,阅读约需12分钟,其中涉及概念较多,建议先收藏再看。】
在前面的文章中,我们已经对kubernetes网络有了一定的了解,具体可详见我的头条文章《一篇文章为你图解kubernetes网络通信原理》。本篇将结合实际线上环境中的集群实例,为大家进一步解密kubernetes网络中的相关技术点。话不多说,一起开始本次的揭秘之旅吧。
先以一段github式的issue提问格式开始本篇的背景介绍。
问题描述:
就在8月的某天,有一台节点偷偷摸摸的宕机了(在监控上未看到数据断档,但在last reboot中发现有记录,姑且算是偷偷摸摸的)。我们称之为A节点。直到下一周用户反馈服务异常量大幅增加,严重影响使用。定位到这台节点访问部分机器失败,再顺腾摸瓜,发现该节点上的服务访问dns服务异常,具体表现为,三个dns的pod副本,分别起名B,C,D。 其中A节点的pod可以访问B节点的dns pod,但不能访问C,D(无法ping通)上的dns pod。这种场景下,A节点的pod访问域名时有部分概率失败,但又非完全失败,因此程序也没能把它剔除掉,导致这个问题服务一直在线上提供服务,造成严重影响。
一张图更清晰的表示问题场景:
环境信息:
kubernetes:v1.6.2
docker:1.12.6
linux:Linux version 3.10.0-327.el7.x86_64
flannel:v0.7.1
A节点(宕机节点)flannel日志:
I0826 08:30:19.613706 8 network.go:243] L3 miss but route for 10.244.88.4 not found
I0826 08:30:20.614662 8 network.go:243] L3 miss but route for 10.244.88.4 not found
I0826 08:30:21.616724 8 network.go:243] L3 miss but route for 10.244.88.4 not found
dns服务部署:
kube-dns-3257220715-0lsx1 3/3 Running 0 1y 10.244.67.4 B
kube-dns-3257220715-sz72c 3/3 Running 0 1y 10.244.8.13 C
kube-dns-3257220715-tkc3b 3/3 Running 0 1y 10.244.6.12 D
其他信息:
A节点上的pod运行正常,无错误日志。B,C,D上的dns服务也运行正常。
在拿到这个问题时,第一反应是从组件日志着手,捕捉有效信息。结果一无所获,各个kubernetes组件运行都很正常,排查一遍,唯一的日志信息就是A节点flannel的“L3 miss but route for 10.244.88.4 not found”,这一条会不会就是关键呢?何谓L3 miss?
线索到这里,我们一只脚已经踏进了VxLAN技术的领域,而L3 miss正是VxLAN发出的,这个事件指目标IP对应的mac地址在邻居表中未找到。
VxLAN是什么技术?
VxLAN全名Virtual eXtensible Local Area Network,是一种网络虚拟化技术,其主要功能就是在一套物理网络设备上虚拟出多个二层网络,将虚拟网络与物理基础设施分离,并实现无与伦比的网络可靠性和可扩展性。
相对于它的前身VLAN,其具有以下优势:
VxLAN通信原理
简要说来,VXLAN的通信原理是通过将逻辑网络中的数据帧封装在物理网络中进行传输,封装和解封装的过程由VTEP节点完成。VXLAN将逻辑网络中的数据帧添加在VXLAN头部之后就封装在物理网络中的UDP报文中进行传送。
kubernetes网络为什么会用到VxLAN?
在《一篇文章为你图解kubernetes网络通信原理》我们提到,容器平台的网络关键要实现逻辑网络与物理网络解耦,在物理层之上构建扁平化网络。Kubernetes中已经支持了多种能够实现overlay网络的CNI插件,其中VxLAN就是重要的CNI组件。在本次案例中,所使用的flannel组件,其默认基于VxLAN实现overlay网络。
Flannel 是一个简而精的构建容器三层网络的方案,它在每一台 host 上运行着叫 flanneld 的 daemon 进程,flanneld 负责申请容器网络的子网并存储在 k8s 或者 etcd 上,网络互通通过不同的 backend 组件实现,比如 vxlan、host-gw、udp等,它不关心 host 内部的容器间网络互通,而主要专注在 host 之间容器的互通。
在容器化网络中,VxLAN的位置位于图中标红处,这里的VTEP指VxLAN Tunnel Endpoint,即通道的两个端点。
而在部署flannel的kubernetes网络中,VxLAN位置如图所示:
Flannel中有两个基于tunnel协议的backend:UDP(默认实现)和VxLAN。
UDP backend负责承担数据转发(这里不展开介绍其实现)。
而VxLAN backend主要完成:
1 监听Kernel中L3 MISS并反序列化成Golang对象
2 根据L3 MISS和子网范围来配置合理的ARP entry
回到之前的问题
聊了这么多,那这条错误信息是否使我们要找的问题源头呢?为刨根究底,翻看flannel 0.7.1版本源码:
查找邻节点的位置:
func (n *network) handleL3Miss(miss *netlink.Neigh) {
rt := n.rts.findByNetwork(ip.FromIP(miss.IP))
if rt == nil {
log.V(0).Infof("L3 miss but route for %v not found", miss.IP)
return
}
if err := n.dev.AddL3(neigh{IP: ip.FromIP(miss.IP), MAC: rt.vtepMAC}); err != nil {
log.Errorf("AddL3 failed: %v", err)
} else {
log.V(2).Infof("L3 miss: AddL3 for %s succeeded", miss.IP)
}
}
处理Miss的位置:
func (n *network) handleL3Miss(miss *netlink.Neigh) {
rt := n.rts.findByNetwork(ip.FromIP(miss.IP))
if rt == nil {
log.V(0).Infof("L3 miss but route for %v not found", miss.IP)
return
}
if err := n.dev.AddL3(neigh{IP: ip.FromIP(miss.IP), MAC: rt.vtepMAC}); err != nil {
log.Errorf("AddL3 failed: %v", err)
} else {
log.V(2).Infof("L3 miss: AddL3 for %s succeeded", miss.IP)
}
}
该段代码是解析监听到的L3 miss事件时,发现该临接网络的硬件地址无法找到,进而去查找路由表中是否存在该IP,如果不存在,则返回,继续进行后续L3 miss的解析。
这里似乎不是问题的根源,单个节点解析失败并不会影响其他节点的解析。这条路算是走不通了。开始抓包,看看能否找到新的线索。
在A节点的pod里,去ping C节点的dns服务,分别对A节点的eth、docker0、flannel.1抓包,查看ICMP reply是否返回。
截取部分现象:
#tcpdump -i eth1 dst host C
14:40:48.919558 IP A.27422 > C.otv: OTV, flags [I] (0x08), overlay 0, instance 1
IP A > 10.244.6.12: ICMP echo request, id 26158, seq 36, length 64
#tcpdump -i flannel.1 src host 10.244.6.1 (C节点dns服务的docker0 ip)
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 65535 bytes
15:11:58.666687 IP 10.244.6.1 > A: ICMP echo reply, id 3694, seq 1068, length 64
抓取arp请求:(就不放图啦)
发现A节点的flannel.1收到了arp请求。但docker0没有请求收到。
马上查看A节点的arp:
10.244.6.0 ether ba:9f:5e:b2:c4:b7 C flannel.1
C节点上的flannel.1 ip与mac地址对应也没问题。
再查看C节点上的arp:
10.244.2.0 ether 72:bf:a8:79:90:6b CM flannel.1
再看看D节点的arp:
10.244.2.0 ether 72:bf:a8:79:90:6b CM flannel.1
至此,问题发现了,这里的ip与mac对应关系没有刷新,A宕机后flannel重启,mac地址改变但该B,C未更新。导致arp解析失败。
检查节点的arp表,并手动刷新异常arp记录
Arp -a 10.244.2.0 9a:db:fc:9d:7f:f8
查看arp表是否更新
# arp -n
10.244.2.0 ether 9a:db:fc:9d:7f:f8 C flannel.1
发现已更新,在尝试访问服务,已恢复正常
机器宕机的原因这里就不多描述了。我们知道,flannel对外通信,是将数据包重新封包成UDP对外发送,而其网关就是通过arp表找到对应的mac地址。
# ip neig show dev flannel.1
10.254.50.0 lladdr ba:10:0e:7b:74:89 PERMANENT
10.254.60.0 lladdr 92:f3:c8:b2:6e:f0 PERMANENT
知道了本侧网关地址,还需要知道目的IP地址。vxlan默认实现第一次确实是通过广播的方式,但flannel再次采用通过arp方式获取对应关系:
# bridge fdb show dev flannel.1
92:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent
ba:10:0e:7b:74:89 dst 10.100.139.247 self permanent
可以看出flannel的数据包的地址解析过程依赖于arp表,那这个表谁来维护?没错,也是flannel,从上面的记录中可以看到,无论是arp表还是FDB表都是permanent,它表明写记录是手动维护的。
传统的arp获取邻居的方式是通过广播获取,如果收到对端的arp相应则会标记对端为reachable,在超过reachable设定时间后,如果发现对端失效会标记为stale,之后会转入的delay以及probe进入探测的状态,如果探测失败会标记为Failed状态。而在flannel实现中,它利用了内核会将发送arp征询发送到用户空间的这一特征,flanneld获取到内核发送到用户空间的L3MISS,并且配合k8s或etcd返回这个IP地址对应的mac地址,设置reachable。
从分析可以看出,如果flanneld程序如果退出后,容器之间的通信将会中断。而一旦没有flanneld来维护这个arp表,那么这个arp记录不会自动更新,也就造成了这次问题的发生。