Kubernetes DNS 超时

最近有很多Kubernetes的用户报告说从Pod中查找DNS有时需要5秒甚至更多的时间:
weave#3287
kubernetes#56903
在这篇文章中,我将解释这种延迟的根本原因,讨论一些缓解措施并给出内核修复。

背景

在Kubernetes中,Pod访问DNS服务器(kube-dns)的最常见方式是通过服务抽象。因此,在尝试解释这个问题之前,了解Kubernetes Service是如何工作的以及目标网络地址转换(DNAT)是如何在Linux内核中实现的非常重要。

注意:本文中的所有示例都基于Kubernetes v1.11.0和Linux内核v4.17。

Service 如何工作

iptables模式中,节点上的kube-proxy在主机网络名称空间的nat表中为每个Service创建一些iptables规则。

让我们考虑一个集群中有两个DNS服务器实例的kube-dns服务。有关规定如下:

(1) -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
<...>
(2) -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
<...>
(3) -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-LLLB6FGXBLX6PZF7
(4) -A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-LRVEW52VMYCOUSMZ
<...>
(5) -A KUBE-SEP-LLLB6FGXBLX6PZF7 -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.32.0.6:53
<...>
(6) -A KUBE-SEP-LRVEW52VMYCOUSMZ -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.32.0.7:53

在我们的示例中,每个Pod的 /etc/resolv.conf 中都填充了名称服务器10.96.0.10条目。因此,一个来自Pod的DNS查询请求将被发送到10.96.0.10,这是kube-dns对应的Service的一个ClusterIP(一个虚拟IP)。

  • 基于规则1 请求进入KUBE-SERVICE链
  • 匹配到规则2, 进入规则3
  • 进入规则3以后有个随机分配,即要么转到规则5要么转到规则6 (两个rs实例,每个命中的概率为50%)
  • 规则5或者6 修改请求的目的IPv4地址UDP数据包的“真实”IPv4地址DNS服务器

这种修改是由DNAT完成的

10.32.0.6和10.32.0.7是Weave网络中Kubernetes DNS服务器容器的IPv4地址。

DNAT in Linux Kernel

如上所述,服务的基础(在iptables模式下)是DNAT,由内核执行。
DNAT的主要职责是更改发送包的目的地、应答包的来源,同时确保对所有后续包进行相同的修改。
后者严重依赖于连接跟踪机制,也称为conntrack,它是作为内核模块实现的。顾名思义,conntrack跟踪系统中正在进行的网络连接。
以一种简化的方式,conntrack中的每个连接都由两个元组表示

  • 一个用于原始请求(IP_CT_DIR_ORIGINAL)
  • 另一个用于应答(IP_CT_DIR_REPLY)

对于UDP,每个元组:

  • 源IP地址
  • 源端口
  • 目标IP地址
  • 目标端口

应答元组包含存储在src字段中的目标的实际地址。

例如,如果一个IP地址为10.40.0.17的Pod向kube-dns的ClusterIP发送一个请求,该请求被翻译为10.32.0.6,那么将创建以下元组:

Original: src=10.40.0.17 dst=10.96.0.10 sport=53378 dport=53
Reply: src=10.32.0.6 dst=10.40.0.17 sport=53 dport=53378

通过这些条目,内核可以相应地修改任何相关信息包的目标地址和源地址,而不需要再次遍历DNAT规则。此外,它还知道如何修改回复以及应该发送给谁。
当一个conntrack条目被创建时,它首先未经确认。稍后,如果没有使用相同的原始元组或应答元组确认的conntrack条目,内核将尝试确认条目。

conntrack的创建和DNAT的简化流程如下所示:

conntrack
  1. 如果给定数据包不存在,则为其创建一个conntrack
    IP_CT_DIR_REPLY是IP_CT_DIR_ORIGINAL元组的反转,所以应答元组的src还没有改变。
  2. 查找匹配的规则
  3. 根据DNAT规则更新应答元组src部分,使其不被任何已确认的连接使用。
  4. Mangle根据应答元组对包的目的端口和地址进行操作。
  5. 如果没有与相同的原始或应答元组确认的连接,则确认连接;递增insert_failed计数器,如果包存在,则删除它。

问题

当来自不同线程的两个UDP数据包同时通过同一个socket发送时,就会出现问题

UDP是一种无连接的协议:

  • 不会因为connect(2) syscall(与TCP相反)而发送包,因此在调用后不会创建conntrack条目
  • 只有在发送包时才创建条目
    这导致了以下可能的竞争:
  1. 两个包都没有在上图步骤1 nf_conntrack_in中找到已确认的连接, , 对于这两个包,将创建具有相同元组的两个conntrack条目。
  2. 与上面的情况相同,但是其中一个包的conntrack条目在另一个调用3之前被确认get_unique_tuple另一个包得到一个不同的应答元组,通常会改变源端口
  3. 与第一种情况相同,但是在步骤2中选择了两个具有不同端点的不同规则。ipt_do_table。
竞争的结果是一样的:

其中一个包在上图步骤5 __nf_conntrack_confirm中被丢弃`

这正是DNS的情况。GNU C库和musl libc都并行执行A和AAAA DNS查找。其中一个UDP数据包可能会由于竞争被内核丢弃,所以客户端会在超时(通常为5秒)后尝试重新发送它。

值得一提的是,这个问题不仅针对Kubernetes—任何并行发送UDP数据包的Linux多线程进程都容易出现这种竞争情况。

而且,即使您没有任何DNAT规则,也可能发生第2次争用——这足以加载nf_nat内核模块来支持调用get_unique_tuple。

可以使用conntrack -S获得的insert_failed计数器是一个很好的指示器,可以用来判断您是否遇到了这个问题

解决办法

有很多的解决办法:

  • 禁用并行查找
  • 禁用IPv6以避免AAAA查找
  • 使用TCP进行查找
  • 在Pod的解析器配置文件中设置DNS服务器的实际IP地址
    有关详细信息,请参阅文章开头的链接问题。不幸的是,由于通常使用的容器基础映像Alpine Linux所使用的musl libc中的限制,它们中的许多不能工作。

对Weave用户来说,最可靠的一种方法是使用tc延迟DNS数据包, 看Quentin Machu's write-up

另外,您可能想知道ipvs模式下的kube-proxy是否可以绕过这个问题。答案是否定的,因为conntrack也在此模式下启用。此外,当使用rr调度程序时,第3个竞争可以很容易地在低DNS流量的集群中复制。

内核修复

不管有什么变通方法,我决定修复内核中的根本原因。

结果如下:
"netfilter: nf_conntrack: resolve clash for matching conntracks"

fixes the 1st race (accepted)

"netfilter: nf_nat: return the same reply tuple for matching CTs"

fixes the 2nd race (waiting for a review)

这两个补丁修复了仅运行一个DNS服务器实例的集群的问题,同时降低了其他服务器的超时命中率。

为了完全消除所有情况下的问题,第三轮比赛需要解决

  • 一种可能的解决方案是在第5步中合并来自同一套接字的不同目的地的冲突conntrack条目__nf_conntrack_confirm 但是,这将使之前针对包的iptables规则遍历的结果无效,在该步骤中更改了包的目标。

  • 另一种可能的解决方案是在每个节点上运行一个DNS服务器实例,并创建一个Pod来查询在本地节点上运行的DNS服务器,这是我的同事的建议链接

原文

https://www.weave.works/blog/racy-conntrack-and-dns-lookup-timeouts

https://blog.quentin-machu.fr/2018/06/24/5-15s-dns-lookups-on-kubernetes/

https://tech.xing.com/a-reason-for-unexplained-connection-timeouts-on-kubernetes-docker-abd041cf7e02

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容