腾小云导读
作者常常帮助用户处理各种 K8s 各类「疑难杂症」,积累了丰厚经验。本文将分享几个网络相关问题的排查和处理思路,深入剖析并打开相关知识,实用性较强。此外,本文几个状况是在运用 TKE 时遇到的。不同厂商的网络环境或许不相同,文中会对不同问题的网络环境进行阐明。欢迎持续往下阅览。
看目录点保藏,随时涨技能
1跨 VPC 拜访 NodePort 常常超时
2 LB 压测 CPS 低
3 DNS 解析偶然 5S 延时
4 Pod 拜访另一个集群的 apiserver 有延时
5 DNS 解析反常
6 Pod 偶然存活查看失利
7 拜访 externalTrafficPolicy 为 Local 的 Service 对应 LB 有时超时
8 结语
在大众号后台回复 「k8s」,免费阅览本文作者所著kubernetes实践指南书。
接下来,本篇将特别介绍7个 K8s 常见问题及其处理计划。希望对你有帮助。
01、跨 VPC 拜访 NodePort 常常超时
现象: 从 VPC a 拜访 VPC b 的 TKE 集群的某个节点的 NodePort,有时分正常,有时分会卡住直到超时。
原因怎样查?
当然是先抓包,抓 server 端 NodePort 的包,发现反常时 server 能收到 SYN,但没呼应 ACK:
重复履行 netstat -s | grep LISTEN 发现 SYN 被丢掉数量不断添加:
剖析:
|
再细心看下 client 地点环境,发现 client 是 VPC a 的 TKE 集群节点,捋一下:
|
由于 TKE 集群中有个叫 ip-masq-agent 的 daemonset,它会给 node 写 iptables 规矩,默许 SNAT 意图 IP 是 VPC 之外的报文,所以 client 拜访 server 会做 SNAT,也便是这儿跨 VPC 比较同 VPC 拜访 NodePort 多了一次 SNAT。
假如是由于多了一次 SNAT 导致的这个问题,应该跟内核参数有关。由于是 server 收到包没回包,所以应该是 server 地点 node 的内核参数问题。比照这个 node 和 普通 TKE node 的默许内核参数,发现这个 node net.ipv4.tcp_tw_recycle = 1,这个参数默许是封闭的。我跟用户沟通后发现,这个内核参数的确在做压测的时分调整过。
解说一下,TCP 主动封闭衔接的一方在发送最终一个 ACK 会进入 TIME_AWAIT 状况,再等候 2 个 MSL 时刻后才会封闭(由于假如 server 没收到 client 第四次挥手承认报文,server 会重发第三次挥手 FIN 报文,所以 client 需求停留 2 MSL的时长来处理或许会重复收到的报文段;一同等候 2 MSL 也能够让由于网络不晓畅产生的滞留报文失效,防止新树立的衔接收到之前旧衔接的报文),了解更具体的进程请参考 TCP 四次挥手。
参数 tcp_tw_recycle 用于快速收回 TIME_AWAIT 衔接,通常在添加衔接并发才能的场景会敞开,比方主张很多短衔接,快速收回可防止 tw_buckets 资源耗尽导致无法树立新衔接 time wait bucket table overflow。
查得 tcp_tw_recycle 有个坑,在 RFC1323 有段描绘:
An additional mechanism could be added to the TCP, a per-host cache of the last timestamp received from any connection. This value could then be used in the PAWS mechanism to reject old duplicate segments from earlier incarnations of the connection, if the timestamp clock can be guaranteed to have ticked at least once since the old connection was open. This would require that the TIME-WAIT delay plus the RTT together must be at least one tick of the sender’s timestamp clock. Such an extension is not part of the proposal of this RFC. |
大概意思是说:
TCP 有一种行为,能够缓存每个衔接最新的时刻戳,后续恳求中假如时刻戳小于缓存的时刻戳,即视为无效,相应的数据包会被丢掉。
Linux 是否启用这种行为取决于 tcp_timestamps 和 tcp_tw_recycle,由于 tcp_timestamps 缺省敞开,所以当 tcp_tw_recycle 被敞开后,实践上这种行为就被激活了,当客户端或服务端以 NAT 办法构建的时分就或许呈现问题。
当多个客户端经过 NAT 办法联网并与服务端交互时,服务端看到的是同一个 IP。也便是说对服务端而言这些客户端实践上等同于一个,可惜由于这些客户端的时刻戳或许存在差异。于是乎从服务端的视角看,便或许呈现时刻戳紊乱的现象,然后直接导致时刻戳小的数据包被丢掉。
假如产生了此类问题,具体的体现通常是是客户端分明发送的 SYN,但服务端便是不呼应 ACK。
回到问题上——client 地点节点上或许也会有其它 pod 拜访到 server 地点节点,而它们都被 SNAT 成了 client 地点节点的 NODE IP。但时刻戳存在差异,server 就会看到时刻戳紊乱,由于敞开了 tcp_tw_recycle 和 tcp_timestamps 激活了上述行为,就丢掉了比缓存时刻戳小的报文,导致部分 SYN 被丢掉。这也解说了为什么之前抓包发现反常时 server 收到了 SYN,但没有呼应 ACK,然后阐明为什么 client 的恳求部分会卡住直到超时。
由于 tcp_tw_recycle 坑太多,在内核 4.12 之后已移除:
remove tcp_tw_recycle
02、 LB 压测 CPS 低
现象: LoadBalancer 类型的 Service,直接压测 NodePort CPS 比较高,但假如压测 LB CPS 就很低。
环境阐明: 用户运用的黑石TKE,不是公有云TKE,黑石的机器是物理机,LB的完成也跟公有云不相同,但 LoadBalancer 类型的 Service 的完成同样也是 LB 绑定各节点的 NodePort,报文发到 LB 后转到节点的 NodePort, 然后再路由到对应 pod,而测验在公有云 TKE 环境下没有这个问题。
-
client 抓包
很多SYN重传
-
server 抓包
抓 NodePort 的包,发现当 client SYN 重传时 server 能收到 SYN 包但没有呼应。
又是 SYN 收到但没呼应,难道又是敞开 tcp_tw_recycle 导致的?查看节点的内核参数发现并没有敞开,除了这个原因,还会有什么状况能导致被丢掉?
conntrack -S 看到 insert_failed 数量在不断添加,也便是 conntrack 在刺进很多新衔接的时分失利了,为什么会刺进失利?什么状况下会刺进失利?
- 挖内核源码
netfilter conntrack 模块为每个衔接创立 conntrack 表项时,表项的创立和终究刺进之间还有一段逻辑,没有加锁,是一种乐观锁的进程。conntrack 表项并发刚创立时五元组不抵触的话能够创立成功,但中心经过 NAT 转化之后五元组就或许变成相同,榜首个能够刺进成功,后边的就会刺进失利,由于现已有相同的表项存在。比方一个 SYN 现已做了 NAT 可是还没到终究刺进的时分,另一个 SYN 也在做 NAT,由于之前那个 SYN 还没刺进,这个 SYN 做 NAT 的时分就以为这个五元组没有被占用,那么它 NAT 之后的五元组就或许跟那个还没刺进的包相同。
在这个问题里实践便是 netfilter 做 SNAT 时源端口推举抵触了,黑石 LB 会做 SNAT,SNAT 时运用了 16 个不同 IP 做源,可是短时刻内源 Port 却是会集一致的,并发两个 SYN a 和SYN b,被 LB SNAT 后源 IP 不同但源 Port 很或许相同。
这儿就假定两个报文被 LB SNAT 之后它们源 IP 不同源 Port 相同,报文一同到了节点的 NodePort 会再次做 SNAT 再转发到对应的 Pod,当报文到了 NodePort 时,这时它们五元组不抵触,netfilter 为它们别离创立了 conntrack 表项,SYN a 被节点 SNAT 时默许行为是 从 port_range 规模的当时源 Port 作为起始方位开始循环遍历,推举出没有被占用的作为源 Port,由于这两个 SYN 源 Port 相同,所以它们源 Port 推举的起始方位相同。
当 SYN a 选出源 Port 但还没将 conntrack 表项刺进时,netfilter 以为这个 Port 没被占用就很或许给 SYN b 也选了相同的源 Port,这时它们五元组就相同了。当 SYN a 的 conntrack 表项刺进后再刺进 SYN b 的 conntrack 表项时,发现现已有相同的记载就将 SYN b 的 conntrack 表项丢掉了。
- 处理办法探究
不运用源端口推举,在 iptables 的 MASQUERADE 规矩假如加 –random-fully 这个 flag 能够让端口推举彻底随机,基本上能防止绝大多数的抵触,但也无法彻底杜绝。终究决议开发 LB 直接绑 Pod IP,不根据 NodePort,然后防止 netfilter 的 SNAT 源端口抵触问题。
03、 DNS 解析偶然 5S 延时
问题:细心剖析,实践跟之前黑石 TKE 压测 LB CPS 低的根因是同一个,都是由于 netfilter conntrack 模块的规划问题,只不过之前产生在 SNAT,这个产生在 DNAT。
这儿我测验用浅显的言语来总结下原因:
DNS client(glibc 或 musl libc)会并发恳求 A 和 AAAA 记载,跟 DNS Server 通信自然会先 connect(树立fd),后边恳求报文运用这个 fd 来发送。 由于 UDP 是无状况协议, connect 时并不会创立 conntrack 表项, 而并发恳求的 A 和 AAAA 记载默许运用同一个 fd 发包,这时它们源 Port 相同。当并发发包时,两个包都还没有被刺进 conntrack 表项,所以 netfilter 会为它们别离创立 conntrack 表项,而集群内恳求 kube-dns 或 coredns 都是拜访的CLUSTER-IP,报文终究会被 DNAT 成一个 endpoint 的 POD IP。当两个包被 DNAT 成同一个 IP,终究它们的五元组就相同了,在终究刺进的时分后边那个包就会被丢掉。假如 dns 的 pod 副本只需一个实例的状况就很容易产生。现象便是 dns 恳求超时,client 默许策略是等候 5s 主动重试。假如重试成功,咱们看到的现象便是 dns 恳求有 5s 的延时。 |
- 处理计划一: 运用 TCP 发送 DNS 恳求
假如运用 TCP 发 DNS 恳求,connect 时就会刺进 conntrack 表项,而并发的 A 和 AAAA 恳求运用同一个 fd,所以只会有一次 connect,也就只会测验创立一个 conntrack 表项,也就防止刺进时抵触。
resolv.conf 能够加 options use-vc 强制 glibc 运用 TCP 协议发送 DNS query。下面是这个 man resolv.conf 中关于这个选项的阐明:
use-vc (since glibc2.14)
SetsRES_USEVCin_res.options. Thisoptionforces the
useofTCPforDNS resolutions.
-
处理计划二: 防止相同五元组 DNS 恳求的并发
resolv.conf 还有另外两个相关的参数:
single-request-reopen (since glibc 2.9):A 和 AAAA 恳求运用不同的 socket 来发送,这样它们的源 Port 就不同,五元组也就不同,防止了运用同一个 conntrack 表项。 single-request(since glibc 2.10):A 和 AAAA 恳求改成串行,没有并发,然后也防止了抵触。 |
man resolv.conf 中解说如下:
single-request-reopen (since glibc2.9)
Sets RES_SNGLKUPREOP in _res.options. The resolver
uses the same socketforthe AandAAAA requests. Some
hardware mistakenly sends backonlyone reply. When
that happens the clientsystemwill sitandwaitfor
the second reply. Turning this optiononchangesthis
behaviorsothatiftwo requests from the same port are
not handled correctly it willclosethe socketandopen
anewone before sending the second request.
single-request (since glibc2.10)
Sets RES_SNGLKUP in _res.options. By default, glibc
performs IPv4andIPv6 lookups in parallel since
version2.9. Some appliance DNS servers cannot handle
these queries properlyandmakethe requests time out.
This option disables the behaviorandmakes glibc
perform the IPv6andIPv4 requests sequentially (at the
cost of some slowdown of the resolving process).
要给容器的 resolv.conf 加上 options 参数,最便利的是直接在 Pod Spec 里边的 dnsConfig 加。 (k8s v1.9 及以上才支撑)
spec:
dnsConfig:
options:
-name: single-request-reopen
加 options 还有其它一些办法:
榜首,在容器的 ENTRYPOINT 或许 CMD 脚本中,履行 /bin/echo ‘options single-request-reopen’ >> /etc/resolv.conf
第二,在 postStart hook 里加。
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
-"/bin/echo 'options single-request-reopen' >> /etc/resolv.conf"
第三,运用 MutatingAdmissionWebhook。这是 1.9 引进的 Controller,用于对一个指定的资源的操作之前,对这个资源进行变更。istio 的主动 sidecar 注入便是用这个功能来完成的,也能够经过 MutatingAdmissionWebhook 来主动给一切 Pod 注入 resolv.conf 文件,不过需求必定的开发量。
-
处理计划三: 运用本地 DNS 缓存
细心调查能够看到前面两种计划是 glibc 支撑的。而根据 alpine 的镜像底层库是 musl libc 不是 glibc,所以即便加了这些 options 也没用。
这种状况能够考虑运用本地 DNS 缓存来处理。容器的 DNS 恳求都发往本地的 DNS 缓存服务(dnsmasq, nscd等),不需求走 DNAT,也不会产生 conntrack 抵触。另外还有个优点,便是防止 DNS 服务成为功能瓶颈。
运用本地DNS缓存有两种办法:
每个容器自带一个 DNS 缓存服务。 每个节点运转一个 DNS 缓存服务,一切容器都把本节点的 DNS 缓存作为自己的 nameserver。 |
从资源效率的视点来考虑,引荐后一种办法。
04、 Pod 拜访另一个集群的 apiserver 有延时
现象:集群 a 的 Pod 内经过 kubectl 拜访集群 b 的内网地址,偶然呈现延时的状况,但直接在宿主机上用同样的办法却没有这个问题。
提炼环境和现象精华如下:
首先,在 pod 内将另一个集群 apiserver 的 ip 写到了 hosts。由于 TKE apiserver 敞开内网集群外内网拜访创立的内网 LB 暂时没有支撑主动绑内网 DNS 域名解析,所以集群外的内网拜访 apiserver 需求加 hosts。 其次,pod 内履行 kubectl 拜访另一个集群偶然延迟 5s,有时乃至10s。 |
调查到 5s 延时。感觉跟之前 conntrack 的丢包导致 dns 解析 5s 延时有关,可是加了 hosts 呀!怎样还去解析域名?
进入 pod netns 抓包。 履行 kubectl 时的确有 dns 解析,而且产生延时的时分 dns 恳求没有呼应然后做了重试。
看起来延时应该便是之前已知 conntrack 丢包导致 dns 5s 超时重试导致的。可是为什么会去解析域名?分明配了 hosts 啊,正常状况应该是优先查找 hosts,没找到才去恳求 dns 呀,有什么装备能够操控查找次序?
实践上, /etc/nsswitch.conf 能够操控,但看有问题的 pod 里没有这个文件。然后调查到有问题的 pod 用的 alpine 镜像,试试其它镜像后发现只需根据 alpine 的镜像才会有这个问题。
进一步地,musl libc 并不会运用 /etc/nsswitch.conf ,也便是说 alpine 镜像并没有完成用这个文件操控域名查找优先次序,瞥了一眼 musl libc 的 gethostbyname 和 getaddrinfo 的完成,看起来也没有读这个文件来操控查找次序,写死了先查 hosts,没找到再查 dns。
这么说,那仍是该先查 hosts 再查 dns 呀,为什么这儿抓包看到是先查的 dns? (假如是先查 hosts 就能射中查询,不会再主张 dns 恳求)
拜访 apiserver 的 client 是 kubectl,用 go 写的,会不会是 go 程序解析域名时压根没调底层 c 库的 gethostbyname 或 getaddrinfo?
进一步了解后,咱们发现:go runtime 用 go 完成了 glibc 的 getaddrinfo 的行为来解析域名,削减了 c 库调用 (应该是考虑到削减 cgo 调用带来的的功能损耗)
issue地址:
net: replicate DNS resolution behaviour of getaddrinfo(glibc) in the go dns resolver
翻源码验证下:
Unix 系的 OS 下,除了 openbsd, go runtime 会读取 /etc/nsswitch.conf (net/conf.go):
hostLookupOrder 函数决议域名解析次序的策略,Linux 下,假如没有 nsswitch.conf 文件就 dns 比 hosts 文件优先 (net/conf.go):
能够看到 hostLookupDNSFiles 的意思是 dns first (net/dnsclient_unix.go):
所以尽管 alpine 用的 musl libc 不是 glibc,但 go 程序解析域名仍是相同走的 glibc 的逻辑,而 alpine 没有 /etc/nsswitch.conf 文件,也就解说了为什么 kubectl 拜访 apiserver 先做 dns 解析,没解析到再查的 hosts,导致每次拜访都去恳求 dns,恰好又碰到 conntrack 那个丢包问题导致 dns 5s 延时,在用户这儿体现便是 pod 内用 kubectl 拜访 apiserver 偶然呈现 5s 延时,有时呈现 10s 是由于重试的那次 dns 恳求刚好也遇到 conntrack 丢包导致延时又叠加了 5s 。
处理计划:
换根底镜像,不必 alpine 挂载nsswitch.conf文件(能够用 hostPath) |
05、DNS 解析反常
现象: 有个用户反应域名解析有时有问题,看报错是解析超时。
榜首反应,当然是看 coredns 的 log:
[ERROR]2loginspub.gaeamobile-inc.net.
A:unreachablebackend:readudp172.16.0.230:43742->10.225.30.181:53:i/otimeout
这是上游 DNS 解析反常了。由于解析外部域名 coredns 默许会恳求上游 DNS 来查询,这儿的上游 DNS 默许是 coredns pod 地点宿主机的 resolv.conf 里边的 nameserver 。coredns pod 的 dnsPolicy 为 “Default”,也便是会将宿主机里的 resolv.conf 里的 nameserver 加到容器里的 resolv.conf, coredns 默许装备 proxy . /etc/resolv.conf。
意思是非 service 域名会运用 coredns 容器中 resolv.conf 文件里的 nameserver 来解析。
超时的上游 DNS 10.225.30.181 并不是希望的 nameserver。VPC 默许 DNS 应该是 180 开头的。咱们看了 coredns 地点节点的 resolv.conf,发现的确多出了这个非希望的 nameserver。终究,咱们跟用户承认,这个 DNS 不是用户自己加上去的。添加节点时这个 nameserver 本身就在 resolv.conf 中。
依据内部人员反应, 10.225.30.181 是广州一台年久失修将被撤裁的 DNS 物理网络,没有 VIP,撤掉就没有了。所以假如 coredns 用到了这台 DNS 解析时就或许 timeout。后边测验,某些 VPC 的集群的确会有这个 nameserver,奇了怪了,哪里冒出来的?
咱们,又试了下直接创立 CVM,不加进 TKE 节点发现没有这个 nameserver,只需一加进 TKE 节点就有了。
看起来是 TKE 的问题。将 CVM 添加到 TKE 集群会主动重装系统,初始化并加进集群成为 K8s的 node,承认了初始化进程并不会写 resolv.conf,会不会是 TKE 的 OS 镜像问题?
咱们测验搜一下除了 /etc/resolv.conf 之外哪里还有这个 nameserver 的 IP,最终发现 /etc/resolvconf/resolv.conf.d/base 这儿面有。
看下 /etc/resolvconf/resolv.conf.d/base 的效果:
Ubuntu 的 /etc/resolv.conf 是动态生成的,每次重启都会将 /etc/resolvconf/resolv.conf.d/base 里边的内容加到 /etc/resolv.conf 里。
经承认,这个文件的确是 TKE 的 Ubuntu OS 镜像里自带的,或许发布 OS 镜像时不小心加进去的。
那为什么有些 VPC 的集群的节点 /etc/resolv.conf 里边没那个 IP 呢?它们的 OS 镜像里也都有那个文件那个 IP 呀。
咱们请教其他同事发现:
非 dhcp 子机,cvm 的 cloud-init 会掩盖 /etc/resolv.conf 来设置 dns; dhcp 子机,cloud-init 不会设置,而是经过 dhcp 动态下发; 2018 年 4 月 之后创立的 VPC 就都是 dhcp 类型了的,比较新的 VPC 都是 dhcp 类型的。 |
/etc/resolv.conf 一开始内容都包含 /etc/resolvconf/resolv.conf.d/base 的内容,也便是都有那个不希望的 nameserver。但老的 VPC 由于不是 dhcp 类型,所以 cloud-init 会掩盖 /etc/resolv.conf,抹掉了不被希望的 nameserver,而新创立的 VPC 都是 dhcp 类型,cloud-init 不会掩盖 /etc/resolv.conf,导致不被希望的 nameserver 残留在了 /etc/resolv.conf,而 coredns pod 的 dnsPolicy 为 “Default”,也便是会将宿主机的 /etc/resolv.conf 中的 nameserver 加到容器里,coredns 解析集群外的域名默许运用这些 nameserver 来解析,当用到那个将被撤裁的 nameserver 就或许 timeout。
能够从两个层面处理:
-
暂时处理:
删掉 /etc/resolvconf/resolv.conf.d/base 重启
-
长时刻处理:
咱们从头制作 TKE Ubuntu OS 镜像然后发布更新
这下应该没问题了吧,可是用户反应仍是会偶然解析有问题,但现象不相同了,这次并不是 dns timeout。
用脚本跑测验细心剖析现象:
|
进入 dns 解析偶然反常的容器的 netns 抓包:
|
正常状况下id不会抵触,这儿抵触了也就能解说这个 dns 解析反常的现象了:
|
那为什么 dns 恳求 id 会抵触?
咱们持续调查发现,其它节点上的 pod 不会复现这个问题,有问题这个节点上也不是一切 pod 都有这个问题,只需根据 alpine 镜像的容器才有这个问题,在此节点新起一个测验的 alpine:latest 的容器也相同有这个问题。
为什么 alpine 镜像的容器在这个节点上有问题在其它节点上没问题?为什么其他镜像的容器都没问题?它们跟 alpine 的差异是什么?
咱们发现差异,alpine 运用的底层 c 库是 musl libc,其它镜像基本都是 glibc。翻 musl libc 源码,构造 dns 恳求时,恳求 id 的生成没加锁,而且跟当时时刻戳有关:
看注释,作者应该以为这样id基本不会抵触,事实证明,绝大多数状况的确不会抵触,在网上搜了好久没有搜到任何关于 musl libc 的 dns 恳求 id 抵触的状况。这个看起来取决于硬件,或许在某种类型硬件的机器上运转,短时刻内生成的 id 就或许抵触。测验跟用户在相同地域的集群,添加相同装备相同机型的节点,也复现了这个问题,但后来删去再添加时又不能复现了,看起来后边新建的 cvm 又跑在了另一种硬件的母机上了。
OK,能解说通之后,来看下处理计划:
|
终究主张用户根底镜像换成另一个比较小的镜像: debian:stretch-slim。
问题处理。但用户后边觉得 debian:stretch-slim 做出来的镜像太大了,有 6MB 多,而之前根据 alpine 做出来只需 1MB 多,最终运用了一个非官方的修改过 musl libc 的 alpine 镜像作为根底镜像,里边禁止了 AAAA 恳求然后防止这个问题。
06、Pod 偶然存活查看失利
现象: Pod 偶然会存活查看失利,导致 Pod 重启,事务偶然衔接反常。
之前从未遇到这种状况,在测验环境测验复现也没有成功,只需在用户这个环境才能够复现。这个用户环境流量较大,感觉跟衔接数或并发量有关。用户反应说在友商的环境里没这个问题。
比照市面上其他产品的内核参数,咱们发现有些差异。测验将节点内核参数改成跟友商的相同,发现问题没有复现。
再比照剖析下内核参数差异,最终发现是 backlog 太小导致的,节点的 net.ipv4.tcp_max_syn_backlog 默许是 1024,假如短时刻内并发新建 TCP 衔接太多,SYN 行列就或许溢出,导致部分新衔接无法树立。解说一下:
TCP 衔接树立会经过三次握手,server 收到 SYN 后会将衔接参加 SYN 行列。当收到最终一个 ACK 后衔接树立,这时会将衔接从 SYN 行列中移动到 ACCEPT 行列。在 SYN 行列中的衔接都是没有树立彻底的衔接,处于半衔接状况。假如 SYN 行列比较小,而短时刻内并发新建的衔接比较多,一同处于半衔接状况的衔接就多,SYN 行列就或许溢出,tcp_max_syn_backlog 能够操控 SYN 行列巨细,用户节点的 backlog 巨细默许是 1024,改成 8096 后就能够处理问题。
07、拜访 externalTrafficPolicy 为 Local 的 Service 对应 LB 有时超时
现象:用户在 TKE 创立了公网 LoadBalancer 类型的 Service,externalTrafficPolicy 设为了 Local,拜访这个 Service 对应的公网 LB 有时会超时。
externalTrafficPolicy 为 Local 的 Service 用于在四层获取客户端实在源 IP,官方参考文档:Source IP for Services with Type=LoadBalancer。
TKE 的 LoadBalancer 类型 Service 完成是运用 CLB 绑定一切节点对应 Service 的 NodePort。CLB 不做 SNAT,报文转发到 NodePort 时源 IP 仍是实在的客户端 IP。
假如 NodePort 对应 Service 的 externalTrafficPolicy 不是 Local 的就会做 SNAT,到 pod 时就看不到客户端实在源 IP 了。
但假如是 Local 的话就不做 SNAT。假如本机 node 有这个 Service 的 endpoint 就转到对应 pod,假如没有就直接丢掉,由于假如转到其它 node 上的 pod 就有必要要做 SNAT,否则无法回包,而 SNAT 之后就无法获取实在源 IP 了。
LB 会对绑定节点的 NodePort 做健康查看勘探,查看 LB 的健康查看状况: 发现这个 NodePort 的一切节点都不健康。
那么问题来了:
-
为什么会全不健康,这个 Service 有对应的 pod 实例,有些节点上是有 endpoint 的,为什么它们也不健康?
-
LB 健康查看全不健康,可是为什么有时仍是能够拜访后端服务?
跟 LB 的同学承认后咱们发现,假如后端 rs 全不健康会激活 LB 的全死全活逻辑,也便是一切后端 rs 都能够转发。那么有 endpoint 的 node 也是不健康这个怎样解说?
咱们在有 endpoint 的 node 上抓 NodePort 的包,发现很多来自 LB 的 SYN,可是没有呼应 ACK。看起来报文被丢了。咱们持续抓下 cbr0 看下,发现没有来自 LB 的包,阐明报文在 cbr0 之前被丢了。
再调查用户集群环境信息:
k8s 版别1.12启用了 ipvs只需 local 的 service 才有反常 |
测验新建一个 1.12 启用 ipvs 和一个没启用 ipvs 的测验集群。也都创立 Local 的 LoadBalancer Service。咱们发现启用 ipvs 的测验集群复现了那个问题,没启用 ipvs 的集群没这个问题。
再测验创立 1.10 的集群,也启用 ipvs,发现没这个问题。看起来跟集群版别和是否启用 ipvs 有关。
1.12 比照 1.10 启用 ipvs 的集群: 1.12 的会将 LB 的 EXTERNAL-IP 绑到 kube-ipvs0 上,而 1.10 的不会:
$ ip ashowkube-ipvs0 | grep -A2170.106.134.124
inet170.106.134.124/32brd170.106.134.124scopeglobalkube-ipvs0
valid_lft forever preferred_lft forever
170.106.134.124 是 LB 的公网 IP1.12 启用 ipvs 的集群将 LB 的公网 IP 绑到了 kube-ipvs0 网卡上 |
kube-ipvs0 是一个 dummy interface,实践不会接收报文。能够看到它的网卡状况是 DOWN,主要用于绑 ipvs 规矩的 VIP。由于 ipvs 主要作业在 netfilter 的 INPUT 链,报文经过 PREROUTING 链之后需求决议下一步该进入 INPUT 仍是 FORWARD 链。假如是本机 IP 就会进入 INPUT,假如不是就会进入 FORWARD 转发到其它机器。所以 K8s 运用 kube-ipvs0 这个网卡将 service 相关的 VIP 绑在上面,以便让报文进入 INPUT 然后被 ipvs 转发。
当 IP 被绑到 kube-ipvs0 上,内核会主动将上面的 IP 写入 local 路由:
$ ip route showtablelocal| grep 170.106.134.124local170.106.134.124dev kube-ipvs0 proto kernel scopehost src 170.106.134.124
内核以为在 local 路由里的 IP 是本机 IP,而 linux 默许有个行为:
疏忽任何来自非回环网卡而且源 IP 是本机 IP 的报文。而 LB 的勘探报文源 IP 便是 LB IP,也便是 Service 的 EXTERNAL-IP 猜想便是由于这个 IP 被绑到 kube-ipvs0,主动加进 local 路由导致内核直接疏忽了 LB 的勘探报文。
带着猜想做完成, 试一下将 LB IP 从 local 路由中删去:
iproutedeltablelocallocal170.106.134.124devkube-ipvs0protokernelscopehostsrc170.106.134.124
咱们发现这个 node 的在 LB 的健康查看的状况变成健康了。看来便是由于这个 LB IP 被绑到 kube-ipvs0 ,导致内核疏忽了来自 LB 的勘探报文,然后 LB 收不到回包以为不健康。
那为什么市面上其他产品没反应这个问题? 应该是 LB 的完成问题,腾讯云的公网 CLB 的健康勘探报文源 IP 便是 LB 的公网 IP,而大多数厂商的 LB 勘探报文源 IP 是保留 IP 并非 LB 本身的 VIP。
如何处理呢?咱们发现一个内核参数 accept_local 能够让 linux 接收源 IP 是本机 IP 的报文。咱们试了敞开这个参数,的确在 cbr0 收到来自 LB 的勘探报文了,阐明报文能被 pod 收到,但抓 eth0 仍是没有给 LB 回包。
为什么没有回包?咱们剖析下五元组,要给 LB 回包。那么意图 IP:意图 Port 有必要是勘探报文的源 IP:源Port,所以意图 IP 便是 LB IP。由于容器不在主 netns,发包经过 veth pair 到 cbr0 之后需求再经过 netfilter 处理,报文进入 PREROUTING 链然后发现意图 IP 是本机 IP,进入 INPUT 链,所以报文就出不去了。再剖析下进入 INPUT 后会怎样,由于意图 Port 跟 LB 勘探报文源 Port 相同,是一个随机端口,不在 Service 的端口列表,所以没有对应的 IPVS 规矩,IPVS 也就不会转发它,而 kube-ipvs0 上尽管绑了这个 IP,但它是一个 dummy interface,不会收包,所以报文最终又被疏忽了。
再看看为什么 1.12 启用 ipvs 会绑 EXTERNAL-IP 到 kube-ipvs0,翻翻 k8s 的 kube-proxy 支撑 ipvs 的 proposal,发现有个当地说法有点缝隙:
LB 类型 Service 的 status 里有 ingress IP,实践便是 kubectl get service 看到的 EXTERNAL-IP,这儿说不会绑定这个 IP 到 kube-ipvs0,但后边又说会给它创立 ipvs 规矩。已然没有绑到 kube-ipvs0,那么这个 IP 的报文根本不会进入 INPUT 被 ipvs 模块转发,创立的 ipvs 规矩也是没用的。
后来找到作者私聊考虑了下,发现规划上的确有这个问题。
咱们看到1.10 的确也是这么完成的。可是为什么 1.12 又绑了这个 IP 呢?调研后发现是由于 #59976 这个 issue 发现一个问题,后来引进 #63066 这个 PR 修正的,而这个 PR 的行为便是让 LB IP 绑到 kube-ipvs0,这个提交影响 1.11 及其之后的版别。
#59976 的问题是由于没绑 LB IP到 kube-ipvs0 上,在自建集群运用 MetalLB 来完成 LoadBalancer 类型的 Service。而有些网络环境下,pod 是无法直接拜访 LB 的,导致 pod 拜访 LB IP 时拜访不了。而假如将 LB IP 绑到 kube-ipvs0 上就能够经过 ipvs 转发到 LB 类型 Service 对应的 pod 去, 而不需求实在经过 LB,所以引进了 #63066 这个PR。
暂时计划: 将 #63066 这个 PR 的更改回滚下,从头编译 kube-proxy,供给晋级脚本晋级存量 kube-proxy。
假如是让 LB 健康查看勘探支撑用保留 IP 而不是本身的公网 IP ,也是能够处理,但需求跨团队协作。而且假如多个厂商都遇到这个问题,每家都需求为处理这个问题而做开发调整,代价较高。所以长时刻计划需求跟社区沟通一同推进,所以我提了 issue,将问题描绘的很清楚: #79783
小考虑: 为什么 CLB 能够不做 SNAT ?回包意图 IP 便是实在客户端 IP,但客户端是直接跟 LB IP 树立的衔接,假如回包不经过 LB 是不或许发送成功的呀。是由于 CLB 的完成是在母机上经过地道跟 CVM 互联的,多了一层封装,回包一直会经过 LB。
便是由于 CLB 不做 SNAT,正常来自客户端的报文是能够发送到 nodeport,但健康查看勘探报文由于源 IP 是 LB IP 被绑到 kube-ipvs0 导致被疏忽,也就解说了为什么健康查看失利,但经过LB能拜访后端服务,只是有时会超时。那么假如要做 SNAT 的 LB 岂不是更糟糕,一切报文都变成 LB IP,一切报文都会被疏忽?
提的 issue 有回复指出:AWS 的 LB 会做 SNAT,但它们不将 LB 的 IP 写到 Service 的 Status 里,只写了 hostname,所以也不会绑 LB IP 到 kube-ipvs0:
可是只写 hostname 也得 LB 支撑主动绑域名解析,而且个人觉得只写 hostname 很别扭,经过 kubectl get svc 或许其它 K8s 办理系统无法直接获取 LB IP,这不是一个好的处理办法。
提了 #79976 这个 PR 能够处理问题: 给 kube-proxy 加 –exclude-external-ip 这个 flag 操控是否为 LB IP,创立 ipvs 规矩和绑定 kube-ipvs0。
但有人忧虑添加 kube-proxy flag 会添加 kube-proxy 的调试复杂度,看能否在 iptables 层面处理:
咱们细心一想,的确可行,打算有空完成下,从头提个 PR:
08、结语
至此,咱们一同完成了一段美妙的问题排查之旅,信息量很大而且比较复杂,假如你有些没看懂很正常,主张保藏起来重复阅览,一同在技能的道路上打怪晋级。以上是本次分享全部内容,欢迎大家在谈论区分享沟通。假如觉得内容有用,欢迎转发~想学习更多K8s知识与实践办法?在大众号后台回复 「K8s」,免费阅览本文作者所著kubernetes实践指南书⬇️⬇️⬇️。
-End-
原创作者|陈鹏
技能责编|陈鹏
你还遇到过哪些K8s「怪现象」?欢迎在大众号谈论区分享你的阅历和处理计划。咱们将选取1则最有创意的分享,送出腾讯云开发者-限定随行杯1个(见下图)。5月14日正午12点开奖。
在大众号后台回复「K8s」
免费阅览本文作者所著kubernetes实践指南书
阅览原文