0%

K8S问题排查-Pod间通过服务名访问异常

问题背景

K8S集群内,PodA使用服务名称访问PodB,请求出现异常。其中,PodA在node1节点上,PodB在node2节点上。

原因分析

先上tcpdump,观察请求是否有异常:

1
2
3
4
5
[root@node1 ~]# tcpdump -n -i ens192 port 50300
...
13:48:17.630335 IP 177.177.176.150.distinct -> 10.96.22.136.50300: UDP, length 214
13:48:17.630407 IP 192.168.7.21.distinct -> 10.96.22.136.50300: UDP, length 214
...

从抓包数据可以看出,请求源地址端口号为177.177.176.150:50901,目标地址端口号为10.96.22.136:50300 ,其中10.96.22.136是PodA使用server-svc这个serviceName请求得到的目的地址,也就是server-svc对应的serviceIP,那就确认一下这个地址有没有问题:

1
2
3
4
[root@node1 ~]# kubectl get pod -A -owide|grep server
ss server-xxx-xxx 1/1 Running 0 20h 177.177.176.150 node1
ss server-xxx-xxx 1/1 Running 0 20h 177.177.254.245 node2
ss server-xxx-xxx 1/1 Running 0 20h 177.177.18.152 node3
1
2
[root@node1 ~]# kubectl get svc -A -owide|grep server
ss server-svc ClusterIP 10.96.182.195 <none> 50300/UDP

可以看出,源地址没有问题,但目标地址跟预期不符,实际查到的服务名server-svc对应的地址为10.96.182.195,这是怎么回事儿呢?我们知道,K8S从v1.13版本开始默认使用CoreDNS作为服务发现,PodA使用服务名server-svc发起请求时,需要经过CoreDNS的解析,将服务名解析为serviceIP,那就登录到PodA内,验证域名解析是不是有问题:

1
2
3
4
5
6
7
8
9
10
[root@node1 ~]# kubectl exec -it -n ss server-xxx-xxx -- cat /etc/resolve.conf
nameserver 10.96.0.10
search ss.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

[root@node1 ~]# kubectl exec -it -n ss server-xxx-xxx -- nslookup server-svc
Server: 10.96.0.10

Name: ss
Address: 10.96.182.195

从查看结果看,域名解析没有问题,PodA内也可以正确解析出server-svc对应的serviceIP10.96.182.195,那最初使用tcpdump命令抓到的serviceIP10.96.22.136,难道这个地址是其他业务的服务,或者是残留的iptables规则,或者是有什么相关路由?分别查一下看看:

1
2
3
4
5
[root@node1 ~]# kubectl get svc -A -owide|grep 10.96.22.136

[root@node1 ~]# iptables-save|grep 10.96.22.136

[root@node1 ~]# ip route|grep 10.96.22.136

结果是,集群上根本不存在10.96.22.136这个地址,那PodA请求的目标地址为什么是它?既然主机上抓包时,目标地址已经是10.96.22.136,那再确认下出PodA时目标地址是什么:

1
2
3
4
5
6
7
[root@node1 ~]# ip route|grep 177.177.176.150
177.177.176.150 dev cali9afa4438787 scope link

[root@node1 ~]# tcpdump -n -i cali9afa4438787 port 50300
...
14:16:40.821511 IP 177.177.176.150.50902 -> 10.96.22.136.50300: UDP, length 214
...

原来出PodA时,目标地址已经是错误的serviceIP。而结合上面的域名解析的验证结果看,请求出PodA时的域名解析应该不存在问题。综合上面的定位情况,基本可以推测出,问题出在发送方

为了进一步区分出,是PodA内的所有发送请求都存在问题,还是只有业务自身的发送请求存在问题,我们使用nc命令在PodA内模拟发送一个UDP数据包,然后在主机上抓包验证(PodA内恰巧有nc命令,如果没有,感兴趣的同学可以使用/dev/{tcp|udp}模拟[1]):

1
2
3
4
5
6
[root@node1 ~]# kubectl exec -it -n ss server-xxx-xxx -- echo “test” | nc -u server-svc 50300 -p 9999

[root@node1 ~]# tcpdump -n -i cali9afa4438787 port 50300
...
15:46:45.871580 IP 177.177.176.150.50902 -> 10.96.182.195.50300: UDP, length 54
...

可以看出,PodA内模拟发送的请求,目标地址是可以正确解析的,也就把问题限定在了业务自身的发送请求存在问题。因为问题是服务名没有解析为正确的IP地址,所以怀疑是业务使用了什么缓存,如果猜想正确,那么重启PodA,理论上可以解决。而考虑到业务是多副本的,我们重启其中一个,其他副本上的问题环境还可以保留,跟开发沟通后重启并验证业务的请求:

1
2
3
4
5
6
7
[root@node1 ~]# docker ps |grep server-xxx-xxx | grep -v POD |awk '{print $1}' |xargs docker restart

[root@node1 ~]# tcpdump -n -i ens192 port 50300
...
15:58:17.150535 IP 177.177.176.150.distinct -> 10.96.182.195.50300: UDP, length 214
15:58:17.150607 IP 192.168.7.21.distinct -> 10.96.182.195.50300: UDP, length 214
...

验证符合预期,进一步证明了业务可能是使用了什么缓存。与开发同学了解,业务的发送使用的是java原生的API发送UDP数据,会不会是java在使用域名建立socket时默认会做缓存呢?

通过一番搜索,找了一篇相关博客[2],关键内容附上:

在通过DNS查找域名的过程中,可能会经过多台中间DNS服务器才能找到指定的域名,因此,在DNS服务器上查找域名是非常昂贵的操作。在Java中为了缓解这个问题,提供了DNS缓存。当InetAddress类第一次使用某个域名创建InetAddress对象后,JVM就会将这个域名和它从DNS上获得的信息(如IP地址)都保存在DNS缓存中。当下一次InetAddress类再使用这个域名时,就直接从DNS缓存里获得所需的信息,而无需再访问DNS服务器。

还真是,继续看怎么解决:

DNS缓存在默认时将永远保留曾经访问过的域名信息,但我们可以修改这个默认值。一般有两种方法可以修改这个默认值:

  1. 在程序中通过java.security.Security.setProperty方法设置安全属性networkaddress.cache.ttl的值(单位:秒)

  2. 设置java.security文件中的networkaddress.cache.negative.ttl属性。假设JDK的安装目录是C:/jdk1.6,那么java.security文件位于c:/jdk1.6/jre/lib/security目录中。打开这个文件,找到networkaddress.cache.ttl属性,并将这个属性值设为相应的缓存超时(单位:秒)

注:如果将networkaddress.cache.ttl属性值设为-1,那么DNS缓存数据将永远不会释放。

至此,问题定位结束。

解决方案

业务侧根据业务场景调整DNS缓存的设置。

参考资料

  1. https://blog.csdn.net/michaelwoshi/article/details/101107042
  2. https://blog.csdn.net/turkeyzhou/article/details/5510960