业务自动扩容出现机器问题,新扩容机器无法访问原有集群机器的服务问题。查了比较长时间,这里总结下排除信息和过程。初步结论是小集群中扩缩容节点使用了内网IP复用而ARP cache没有回收造成的网络不通的问题。
产研反馈:之前发现的网络问题又出现了(说明之前出现过3次以上),某服务到部分节点不通,试了下 这几个方向都是 ping 不通的,为了便于说明,把几台机器做个编号:
机器A: xx.xx.2.217
机器B:xx.xx.3.115
机器C:xx.xx.1.185
机器D:xx.xx.3.71
初步分析是基于aws自动扩容组新扩出来的机器A,通过内网IP无法ping到集群中原有机器BCD,部分能通部分不通。首先想到是arp问题,分析现象是机器B上的IP A217对应 的mac地址一直处于STALE状态,腐化但不被删除,缓存时间可长达数天,远远超过了arp cache默认时间 60s。集群中其他机器B缓存了该IP错误的mac地址,导致A无法调用B的服务。217能ping 到BCD等机器,三台机器能收到arp包,但无法收到他们的回包(tcpdump分析)。
部分能通信的机器上此时对应机器A 的mac 是 xx:xx:xx:59:91:b2,正常机器
# arp|grep 217
ip-xx-xx-2-217.ap-south ether xx:xx:xx:59:91:b2 C eth0
集群原有机器B、C、D上的mac addr是xx:xx:xx:35:94:dc ,缓存了不一样的mac ,如下:
# arp|grep 217
ip-xx-xx-2-217.ap-south ether xx:xx:xx:35:94:dc C eth0
xx:xx:xx:35:94:dc 可能是上次分配IP 217时绑定过的mac addr。mac 缓存记录状态持续处于STALE状态,如下:
# ip neigh ls all|grep 217
xx.xx.2.217 dev eth0 lladdr xx:xx:xx:35:94:dc STALE
在217 ping 185 ,第十个包才收到回应
$ ping xx.xx.1.185
PING xx.xx.1.185 (xx.xx.1.185) 56(84) bytes of data.
64 bytes from xx.xx.1.185: icmp_seq=10 ttl=64 time=0.127 ms //不清楚原因
64 bytes from xx.xx.1.185: icmp_seq=11 ttl=64 time=0.121 ms
64 bytes from xx.xx.1.185: icmp_seq=12 ttl=64 time=0.107 ms
怀疑是arp缓存过期慢问题,如果关闭arp缓存可能可以解决
担心风险:关闭后任何机器跟其他机器通信都要arp解析mac地址,担心对性能,arp广播数量暴涨影响。
怀疑跟aws auto scaling group+ spot有关
怀疑spot IP重分配太快,导致上一次没有在交换机被清理,下一次又用到了同一IP,导致现在问题。
准备关闭spot ,但为了arp 问题关闭spot貌似是在回避问题绕过问题,spot好技术不能使用。spot的分配时实际并没有频繁,10+次/天。
怀疑无回包可能是本机路由有缓存且是错误缓存
A217存活期间,怀疑是mac所在老机器还活着,且时不时跟集群现有机器b、c、d通信,导致mac一直未更新。查看路由是空的。
@ip-xx-xx-0-236:~$ route -Cn
Kernel IP routing cache
Source Destination Gateway Flags Metric Ref Use Iface
通过tcpdump观察,发现并没有这个其他机器用这个mac地址通讯
怀疑是路由缓存后“本地确认”问题
sudo iptables -A INPUT -s xx.xx.2.217 -j DROP
236:~$ ip -s -s neigh show |grep 2.217
xx.xx.2.217 dev eth0 lladdr xx:xx:xx:59:91:b2 used 240332/240326/240281 probes 1 STALE
236:~$ sudo iptables -A INPUT -s xx.xx.2.217 -j DROP
236:~$ date
Mon Aug 10 02:23:52 UTC 2020
236:~$ date
Mon Aug 10 03:16:43 UTC 2020
236:~$ sudo iptables -D INPUT -s xx.xx.2.217 -j DROP
发现依然是STALE状态
怀疑arp mac记录状态机在不断变化,续租更新到STALE状态
~$ watch -n1 "ip -s -s neigh show |grep 217"
Every 1.0s: ip -s -s neigh show |grep 217 Mon Aug 10 04:05:54 2020
xx.xx.2.217 dev eth0 lladdr xx:xx:xx:59:91:b2 used 246119/246114/246068 probes 1 STALE
状态没有变化,也就是没有经过delay、reachable、probe等状态
其实能想到,扩容机器下线很久后,都还有arp缓存,跟“续租”没啥关系;
怀疑过期时间参数配置错误,太长导致一直没更新
217机器A回收3天后,115等多台机器还是保留着这个arp cache 。
$ ip -s neigh list|grep 217
xx.xx.2.217 dev eth0 lladdr xx:xx:xx:35:94:dc used 173095/173065/173028 probes 4 STALE
ps: 173095/173065/173028
表示这条记录存在了过了48小时多。远超过net.ipv4.neigh.*.gc_interval = 30 时间30s。
当机器A回收很久后,机器BCD的arp cache记录没有被清理,机器BCD上的对应机器A217的mac addr是过期的错误的mac,下次如果有机器复用了IP217,BCD还是回包到老mac上,导致新A217无法跟BCD正常通信。
举一反三并验证问题:怀疑集群中还有大量这种情况。之前自动扩容出来,然后被缩容掉的ip还被集群中其他主机arp 缓存了,找了一台现有机器xx-xx-0-236分析
#ip -s -s neigh show |grep STA
xx.xx.0.77 dev eth0 lladdr 06:5f:b9:b3:d4:1e used 160634/160608/160583 probes 4 STALE
xx.xx.2.167 dev eth0 lladdr 06:36:73:af:f7:ee used 409277/409246/409226 probes 4 STALE
xx.xx.0.40 dev eth0 lladdr 06:80:0a:a6:c7:ba used 878/873/847 probes 1 STALE
xx.xx.1.179 dev eth0 lladdr 06:fd:14:c9:60:40 used 165297/165272/165240 probes 1 STALE
xx.xx.3.130 dev eth0 lladdr 06:62:80:a0:43:0a used 1230063/410256/410226 probes 4 STALE //长达14天
xx.xx.3.18 dev eth0 lladdr 06:d1:f4:27:6b:64 used 161097/161069/161043 probes 4 STALE
...
有大量IP是长时间被缓存的,是扩容后缩容了,被集群其他主机缓存了,长时间没被清理,多找了几台分析,证实了集群中大量存在STALE,基本确定是本机arp cache不删除问题。
确定没有回收,大概率是跟gc相关参数配置有关。
查看MAC cache gc内核参数:
net.ipv4.neigh.*.gc_interval = 30
net.ipv4.neigh.*.gc_stale_time = 60
net.ipv4.neigh.*.gc_thresh1 = 128
net.ipv4.neigh.*.gc_thresh2 = 512
net.ipv4.neigh.*.gc_thresh3 = 1024
net.ipv4.neigh.*.gc_stale_time = 60
gc_interval间隔是30s,gc_state_time=60s,应该很快被回收才对,但没有回收,应该是根本原因。
跟gc相关的内核参数有14个:
net.ipv4.conf.*.arp_accept = 0
net.ipv4.conf.*.arp_announce = 0
net.ipv4.conf.*.arp_filter = 0
net.ipv4.conf.*.arp_ignore = 0
net.ipv4.conf.*.arp_notify = 0
net.ipv4.conf.*.drop_gratuitous_arp = 0
net.ipv4.conf.*.proxy_arp = 0
net.ipv4.conf.*.proxy_arp_pvlan = 0
net.ipv4.neigh.*.gc_interval = 30
net.ipv4.neigh.*.gc_stale_time = 60
net.ipv4.neigh.*.gc_thresh1 = 128
net.ipv4.neigh.*.gc_thresh2 = 512
net.ipv4.neigh.*.gc_thresh3 = 1024
net.ipv4.neigh.*.gc_stale_time = 60
gc_interval,gc_thresh1,gc_thresh2,gc_thresh3,gc_stale_time。
gc_thresh3是arp_tbl中允许拥有的邻居数量的上限,一旦超过这个上限,并且表中没有可以清理掉的垃圾邻居,那么就无法创建新的邻居,这个 值缺省被置为1024。
gc_thresh2是第二个阀值,如果表中的邻居数量超过这个阀值,并且在需要创建新的邻居时,发现已经超过5秒时间表没有被刷 新过,则必须立即刷新arp_tbl表,进行强制垃圾回收,这个值缺省被置为512。
gc_thresh1是触发真正清理的条目数量下限阈值,它缺省被置为128。
gc_interval应该是常规的垃圾回收间隔时间,缺省置为30秒。间隔这个时间后执行gc动作,符合条件的缓存记录从arp_tbl表中删除。
gc_stale_time 是STALE状态的记录生存时间。
常规垃圾回收的定时器,其定时处理函数是neigh_periodic_timer。
neigh/default/gc_thresh1 - INTEGER Minimum number of entries to keep. Garbage collector will not purge entries if there are fewer than this number. Default: 128
我们的集群都较小小,gc_thresh*是针对cache条目数量的,可能都不到128
static void neigh_periodic_work(struct work_struct *work)
{
struct neigh_table *tbl = container_of(work, struct neigh_table, gc_work.work);
...
if (atomic_read(&tbl->entries) < tbl->gc_thresh1)
goto out;
所以当缓存中的条目数量少于gc_threash1时,根本不会执行gc过程,直接goto out跳出了
我们生产的集群都比较小,查看arp -a|wc -l,发现都在50-100之间,基本判断<128,没有达到进行真正回收的阈值下限。我们尝试 把gc 条件的条目数设置小一点,使之触发gc。
$sudo sysctl -w net.ipv4.neigh.default.gc_thresh1=30
结果很快回收了,1分钟左右
小结:之前的128是不合适的,设为30其实也不一定合适,还真有可能arp cache entries<30的,所以设置为0或1可能是更好的选择
在gc_interval间隔时间后,会触发执行arp cache gc,但真正要不要执行purge ,还要看cache 中的条目数量, gc_thresh1就是这个下限。少于这个数量就不会真正执行purge。
我们的集群都比较小,cache数量基本在50-100条左右,gc_thresh1默认值是128 ,导致一直没有真正删掉cache 条目
现象:ec2 A 217是新扩容机器 ,起来后跟集群中原有机器ec2 BCD通信,BCD会缓存A的mac addr。当 A 被回收后,BCD主机中对应A217 的arp cache 没有清理,被标识为STALE状态。当A之前用的IP 217被下次弹出的E 使用了,会出现E跟BCD服务调用不同的情况,从应用层看无法建立连接。从三层看是E能ping BCD, 但BCD无法回包到E。这些arp缓存记录长期不清理。
根因:是小集群中单台机器互相通信的机器数量少,没达到进行gc的条目数量,根因是机器的arp 参数配置不科学,没有根据小集群的特点进行配置。跟交换机缓存、路由缓存、arp续租等都没有关系。
GC有三层控制:1、触发gc动作,2、cache记录已经过期,3、cache列表中记录数达到gc_thresh*阈值。
从单台上来看很简单,有几种方法都可以:
定时arp flush 一次(按天/小时)比关闭arp cache风险小
缺点:但flush之前一段时间还是有业务风险
改基础镜像,每次开新机器都要自动使用小gc_thresh1参数
缺点:在底层改动,不确定是否适用其他业务,SA同学不同意
把gc 条件的条目数gc_thresh1(默认128)设置小一点(小集群中能达到的互相通信的机器数量,可能是个位数),使之真正清理cache记录。
缺点:必须全网每台机器改一遍,以后新扩的机器都要改。在ec2初始化完成后SRE验收时或是在使用部署服务时去改。
新实例启动时广播arp
缺点:原理可行但AWS vpc不支持广播因此无法执行
因为这是一个小集群,很容易出现arp cache数量小于默认参数128的情况
有人理解为关闭arp cache就好,借此机会深入复习理解ARP原理
在分析过程中也发现国外有用户碰到这个问题,貌似没有彻底解决。如:linux - When do STALE arp entries become FAILED when never used? - Server Fault
参考资料:
阿里云开发者社区-云计算社区-阿里云
Linux实现的ARP缓存老化时间原理解析_Netfilter,iptables/OpenVPN/TCP guard:-(-CSDN博客_arp老化时间
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
arp(7) - Linux manual page
https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/net/core/neighbour.c