问题描述
对阿里云ACK集群中的pod进行压测,压测方式是直接访问集群前的SLB, 压测表现是 SLB (CLB 7层监听)偶发返回499报错。
补充抓包后看到的表现:SLB后端抓包表现是,SLB传到后端的SYN包被后端服务节点直接ACK而结束三次握手,建联失败。
关键词: 偶发 、压测、SYN-ACK、ExternalTrafficPolicy:Cluster、七层SLB
环境
数据流向:
七层SLB -> Node: NodePort ( Svc ExternalTraffic:Cluster) -> Pod
环境:
集群版本:阿里云ACK 1.20.11-aliyun.1
节点操作系统:Aliyun Linux 2
SLB: CLB 7层监听
SLB 后端:手动挂的nodeport类型svc, 且ExternalTrafficPolicy:Cluster
架构:
SLB后端手动挂多集群的nodeport类型svc, 实现多集群的负载均衡。(此处不对业务架构设计做讨论)
问题分析
异常复杂网络问题,且偶发可复现,自然要抓包分析。但是该类问题通常只有tcpdump抓包是不够的,本文略掉中间的摸索过程,直接上有效的所有分析步骤。
1. 抓包准备:
为便于抓取后端数据,测试环境中slb svc 后缩容只有一个pod。
1) Node进行tcpdump抓包(200M一个 抓10个包 问题复现之前涵盖五分钟):
sudotcpdump-iany-nnvv-C200-W10-w/alios_499.pcap
2)Node抓ipvsadm 以及contrack表项状态 ;Pod抓netstat状态综合分析:
//直接运行在pod节点上的脚本foriin{1..2000}doecho$(date)>>node.txtsudoipvsadm-Ln-c|egrep"nodeip:nodeport">>node.txtsudoegrep"nodeport"/proc/net/nf_conntrack>>node.txtsleep1sdone//nsenter切换到pod网络命名空间:nsenter-n-p<pid> ,抓pod里面的netstatforiin{1..2000}doecho$(date)>>pod.txtnetstat-antpl|greppod-port>>pod.txtsleep1sdone
2. 问题复现:
通过监控SLB 499的日志,确认问题复现时间2022-xx-xx 17:05:42
则之前部署的有效抓包时间段 2022.xx.xx 17:00:42-17:06:01
3. 数据包分析:
1) 如何从海量数据包中找到问题数据流
方法一: 使用wireshark- statics->conversation -> limit to display-》packets排序找 “1”
由于三次握手没完成,SYN后没收到SYN,ACK, 因此使用 tcp.flags.syn == 1以及node/pod ip 过滤,找非成对单数的数据流,可以定位看到SLB源端口33322 , node 源端口30552 对应的数据流符合条件:
进而使用过滤条件 tcp.port == 33322 or tcp.port == 30552 可以定位到问题数据流 tcp.stream in {24182 24183},问题时间戳是17:05:41。
SLB-NODE是一个stream 24182, node-pod是新的stream 24183,其实只分析node向后转发给pod的24183即可。
方法二:当数据包很大时,可使用命令行tshark分析
过滤条件tcp.flags.syn == 1 and (ip.src == 10.1.72.105 or ip.dst == 10.1.72.105)
tshark -t ad -r alios_499.pcap6 -Y"tcp.flags.syn == 1 && (ip.src == 10.0.1.107 || ip.dst== 10.0.1.107) && (ip.src == 10.1.72.105 || ip.dst== 10.1.72.105)"-T fields -e"tcp.stream"-e"frame.number"-e"ip.addr"-e"tcp.port"| awk'{print $1}' |sort|uniq -c |awk '{ if ( $1 % 2 != 0) print $2 }'
基于上述两种方法,找到SLB 499对应的异常数据流为tcp.stream in {24182 24183},问题时间戳是17:05:41,看数据链路的端口为:
stream 24182:SLB源端口33322 -> Node目的端口:30718
stream 24183:Node源端口30552-> Pod目的端口7001
小知识:
- 由于SLB是7层,因此数据包流经SLB后,SLB会跟后端新起一个新stream,源IP 会SNAT修改为SLB内部IP后转发给后端。因此抓包看不到源客户端IP,而是SLB的IP。
- ExternalTrafficPolicy:Cluster 模式,数据包经过node转发给pod时,会被SNAT, 源IP被SNAT为node IP,因此节点上抓包会有两个stream (SLB-NODE ; NODE-POD)。
- 同一条数据流流经 SLB-Node-Pod 三个环节时的tcp.seq相同,也可以先找到node->pod的异常包后根据tcp.seq找slb-node包。
2)查node的ipvs连接以及contrack表定位
针对异常stream 24183:Node源端口30552-> Pod目的端口7001,可以发现,在异常数据包于17:05:41到达之前有一条使用相同端口五元组的“TIME_WAIT”状态的连接还没结束。新的SYN包经node转发给pod时,复用了端口30552,导致node到pod的两次数据流五元组相同 。查pod的netstat,也可以看到异常数据包到达pod时,前一条五元组相同的连接处于 “TIME_WAIT”。
基于节点内部ipvs/conntrack表的分析,最终找到node -pod 发生串流的两个 stream:
tcp.stream in {19697 24183} //NODE-POD的stream
17:05:36TIME_WAIT100.117.95.144:9742-10.0.1.107:9742->SNAT->10.0.1.107:30552->TCP->10.1.72.105:700117:05:41SYN_SENT100.117.95.189:33322-10.0.1.107:33322->SNAT->10.0.1.107:30552->TCP->10.1.72.105:7001
数据分析详情:
- 查node的contrack表:
可以发现node-pod这一段的五元组(红框中所示)中,17:05:36的表项正处于TIME_WAIT ,对应的SLB source 是100.117.95.144:9742 (slb-node段没重复);5秒后17:05:41的表项是新的stream,状态为SYN_SENT (该表项可以看出问题数据流是node-pod五元组重复的数据流),对应的slb source是100.117.95.189:33322。
- 查node的ipvs连接:
17:05:36的时候,在ipvs的session 中该五元组正在倒计时 01:58,还没结束。五秒后17:05:41新的session来的时候,前一个五元组倒计时还剩01分54秒。因此新syn包命中五元组重复的场景。
- 分析 pod中 netstat
可以看到pod:7001到node:30552端口的连接处于time_wait.
小知识:
- 五元组为 【源IP+源端口+协议+目的IP+目的端口】 组合。
- ExternalTrafficPolicy两种模式解析:
若SLB后端nodeport类型的svc采用ExternalTraffic:Cluster模式,SLB转发的数据包在经node转发给pod时会被SNAT,传入pod的数据流的源IP会变为node IP。节点的端口有限,因此在压测流量大的场景中,对于相同的pod ip:pod port,很容易发生nodeip:node port 五元组重复。
若SLB后端nodeport类型的svc采用ExternalTraffic:Local模式,SLB转发的数据包经node只会转发给本机pod,且数据包不会被SNAT,传入pod的数据流的源IP源端口依旧是SLB的IP跟端口。由于SLB是公有云的7层负载均衡,底层实则是整个集群做支撑,因此slb的源端口不容易发生重复。在以上抓包看到node-pod五元组重复的场景中也可以看到,slb-node的stream中,slb port其实每次都不同。
- TIME_WAIT:TCP连接中断过程中,主动关闭的一方在发送最后一个 ack 后就会进入 TIME_WAIT 状态停留2MSL(max segment lifetime),这个是TCP/IP链接关闭过程中必不可少的状态。如果在TIME_WAIT 状态继续收到相同五元组的SYN数据包,协议栈如何处理?本案例中是服务端回复SYN包一个ACK结束建联。
方案建议
本文不讨论架构优化,针对本次案例中串流问题的方案建议:
- 针对五元组重复的串流问题,通用解法是采用 externalTrafficPolicy =Local,这样node-pod的数据包不会被SNAT为node的ip:port,那么pod接收到数据包都会是slb的IP:PORT,由于slb位于公有云七层负载均衡的集群中,slb ip通常也是整个集群ip列表中的一个,就可以大大减少出现ip跟port都重复的概率。
- 若是terway模式的集群,SLB后端可以采用terway eni模式直接挂pod eni在SLB后端,slb 转发的数据包可以不用在node做周转,省去了ipvs/iptables这一层转换,slb后直达pod,除了对性能也可以有所提升,也可以避免被node snat后五元组重复的串流问题。
- 该问题只有在流量很密集的场景中偶发,比如上文中的压测场景,正常业务场景中很少命中这个问题,若不做配置改动,可视业务需求而忽略。
结尾
本次问题排查中参考《k8s网络诊断之我的流量去哪了》中对ipvs session/ conntrack表状态的跟踪,也对海量网络报文快速分析定位方法做了讲解。