我们对一个持续数月的问题进行了调查,从中我们学到了很多东西。
系统管理员常说,网络出现问题时是 总是 DNS。
大多数时候,实际上是其他问题,例如,人为错误(第8层问题)。但在这篇文章里,我想告诉你的是,我们花了好几个星期才走出一个不同的“兔子洞”,直到我们发现这次问题果然还是由 DNS 引起的。
老套的系统管理员梗
背景在进入故事之前,我想先简单说一下我们做的事情。
在Adevinta,我们的团队运营一个内部的平台即服务(PaaS):The Common Platform,来自我们不同市场的软件工程师可以在The Common Platform 上构建、部署和管理他们的微服务。
这些微服务运行在SCHIP之上,SCHIP 是基于 Kubernetes 构建的运行时环境。
SCHIP 运行一个支持多租户的 Kubernetes 环境,其中包含了多个集群,每个集群中有超过 20 个租户。在任意一个集群中,多个租户的应用部署可以同时运行。
途程开始了在一个平静而晴朗的周五下午,我们接到了一位客户的电话,他们告诉我们注意到在SCHIP上运行的某些服务的HTTP错误(5xx)有所增加。
我们注意到,某些客户的5xx HTTP错误率突然飙高了。
我们立即宣布事件发生了,并开始查看问题。
一开始,我们团队就觉得可能有点问题,我们集群的 ingress 控制器可能有问题。这个控制器负责整个集群的流量接入。我们注意到这段时间内,控制器实例数量在2到5之间波动,在我们调查期间,实例数量一直保持在两个。
最初,我们原先认为 ingress controller 的降级的事件是由于每秒请求量的波动,导致了部分请求被丢弃。
我们也注意到我们的 ingress 控制器中存在少量的 CPU 节流情况。基于这些观察和假设,我们决定将实例的最小数量增加到与峰值相匹配,减少 ingress 被缩减的风险,并给控制器分配更多的 CPU 资源。
可以看到少量的限流。
情况好像好转了,但实际上并没有,所以我们决定等到下周一看看情况是否真的好转。
第一次浪潮一到星期一,就又收到了一份报告,确认问题仍然没有解决,我们客户的服务质量依然大受影响。
客户的体验仍然下降了
我们确认了 ingress controller 实例的数量不再变化,并且 CPU 指标没有显示节流,所以问题可能在其他地方。
不再出现CPR throttling事件
在调查期间,这些问题仍然一天中时不时出现,但每次性能下降都很短暂,不足以造成明显的影响。
这肯定是网络问题在其中一次事件中,我们发现了一个相关的问题,这使我们得出了下一个结论:肯定是网络出了问题。
我们为什么会这么认为?这是因为我们可以将一次性能下降与我们内部警报中的一个“A fluent-bit agent stopped processing logs.”关联起来。fluent-bit代理负责监控容器日志并将它们发送到日志转发器。当我们收到该警报时,意味着位于某个节点的fluent-bit代理遇到了网络问题,无法正常转发日志。这导致其缓冲区溢出,阻止代理处理更多日志。
报告 5xx 错误的服务实例恰好与异常行为的 fluent-bit 实例位于同一节点中。
为了让你知道,我们使用 kubenurse,一个小工具来监控Kubernetes集群中的所有网络连接,并通过Prometheus暴露有用的指标。例如,我们常使用的“kubenurse_errors_total”指标,用来找出那些没有更新网络配置的节点。
我们已经被这个 fluent-bit 警告困扰了一段时间,我们觉得问题出在某个节点的行为有问题(对吧,挺简单的)。所以,我们总是采取的措施是先排除有问题的节点,再重启网络层。因为我们看到一些节点在同步整个集群中所有节点的最新状态时确实存在延迟(我们使用的是 Calico 作为网络层),这听起来挺合理的。
情况似乎好转了一次(但实际上并没有)。
我们在缓解措施之后继续收到更多的报告,但并非所有的报告都与我们监控的网络问题指标相关。
这很严重,我们需要找出问题的根源随着这个问题变得越来越“喧闹”,它开始影响团队的交付速度,我们的紧急应对健康状况也受到了严重的影响。作为团队,我们决定开启一个专门的调查事件,并指派了2-3名工程师来研究这个问题。
我们首先设计了一个仪表板,结合了我们认为可能与问题相关且可能有助于我们找到根本原因的相关信息。
比如说:
- 网络错误(节点中的 pod 无法与其它节点通信)
- Pod/节点迁移(我们注意到,错误每小时发生一次,这可能与大量 CronJobs Pod 的创建有关)
- 集群中的所有 5xx 请求
- API 服务器的使用指标
- DNS 命中/未命中和 DNS 延迟指标 SLIs
尝试在控制面板上找关联
仪表盘变得更让人困惑,每个指示器组合一开始似乎合理,但之后却不再这样。
最后,我们决定亲自查看受影响的应用程序,以便更好地理解问题的根本原因。这有些改变了局面。之前我们认为所有应用程序的行为都差不多,因此我们只关注了一些特定组件,而没有关注应用程序本身的行为。
我们首先发现 QoS 下降主要是由几个应用程序引起的,而且大部分是由单个应用程序引起,并不是整个集群或所有应用。这个应用基本上就是一个代理服务器,位于多个进行内部调用的微服务之前,这些微服务使用 Kubernetes 服务名解析。
Kubernetes 中的服务和容器 (Pod) 的 DNS
Kubernetes 中服务和容器实例的 DNS
比如说,它直接使用 http://foo/api 来调用 foo 服务。
这一发现,以及每次收到服务性能下降的通知时,我们通常也会因上述提到的fluent-bit问题而接到通知。这表明我们之前未曾考虑过的某些方面。
我们这边的 fluent-bit 代理也通过此 URL log-router-ingress-forward 调用集群内的日志转发器。
使用 Kubernetes 的 DNS 解析功能的 Fluent Bit 主机配置
是的,到这个时候,我们已经相当确定那必定是DNS。
当我们检查受影响集群的DNS延迟SLI时,我们发现它与其他集群相比一直很低。
DNS 延迟 SLO 显示有问题
我们已经有一段时间忽视了这个问题的恶化,因为我们没有看到任何问题,并且在此之前,其他集群也没有受到更广泛的影响。尽管fluent-bit的报警错误一直困扰着我们,但它不是每次都会发生。通常,重启节点可以减轻噪音,所以我们认为这是节点的随机问题。
这个问题让我们深刻认识到,拥有这些坚实的SLI(服务等级指标)的重要性,以及当你真正信赖它们时的实用性。
让我们确认一下DNS我们先来看看我们的DNS架构吧。
带本地解析的Kubernetes集群中的DNS
在我们的设置中,我们部署了 [CoreDNS](https://coredns.io/)
作为高级 DNS 解析服务。每个节点上运行的 DNSMasq 代理作为本地 DNS 缓存,来减少 CoreDNS 的请求量。
</TRANSLATION>
我们的关键绩效指标用于衡量集群中DNS的健康程度是DNS延迟服务等级指标。它通过计算内部请求的在16毫秒内得到解决的百分比和外部请求的在64毫秒内得到解决的百分比来实现。
内部请求是指顶级域名是 **_cluster.local**
的请求,因此被集群级 DNS(CoreDNS)解析。外部请求则由上游 DNS 提供商(例如 Amazon)解析。
我们发现该问题主要来自内部DNS请求,这些请求的阈值是16ms。我们还注意到服务降级的时间点与DNS缺失错误指标增加的时间相吻合。
DNS 未命中率增加的同时,QoS 也在下降(服务质量)。
问题有几个问题导致了这种恶化,下面我来解释一下造成这些问题的原因。
并发查询限制当我们意识到这与DNS相关时,我们从日志开始——第一个问题非常明显地显现出来:
I0920 12:39:44.923951 6 nanny.go:146] dnsmasq[25]: 并发DNS查询已达最大限制(最大值:150)
I0920 12:39:55.097844 6 nanny.go:146] dnsmasq[25]: 已达最大限制(最大值:150)
DNS Masq 默认仅允许 每个节点150个并发DNS查询。由于受影响集群中的请求数量较多,我们超过了该限制,因此我们将这个数值从150增加到了1500,如 Google 推荐 的那样。
我们在推出改动后,DNS延迟的SLI马上有了改善。
DNS 延迟 SLI 得到提升(SLI 为服务等级指标)
然而,我们还没来得及高兴太久,因为一周后,我们看到了下降的趋势。这就像我们只是在更深层次的问题上敷衍了事,打了补丁。
在一周内,SLI 回落到令人担忧的水平。
找不到的缓存在深入调试 DNS Masq 之后,我们发现只有外部 DNS 查询被缓存下来。这意味着每一个这样的 cluster.local 查询仍然被转发到集群 DNS(CoreDNS)。在进一步调查后我们发现,最近 CoreDNS 版本的一些更改阻止了 DNS Masq 缓存 CoreDNS 的响应。
(感谢原作者:https://qasim-sarfraz.medium.com/dns-caching-gone-wrong-a329dc00452e)
在这里我会略去一些细节,感兴趣的话,可以点击这里查看这篇博客:链接的博客。
总之;
这两个标志,分别是可用递归(RA)标志(由服务器设置的)和所需递归(RD)标志(由客户端设置),在DNS查询及其相应响应中由服务器设置的比特标志。
DNS Masq在RA被关闭时,明确不缓存RA。
通过强制RA位始终存在,我们可以解决这个问题,这样DNSMasq可以缓存这些本地查询。
. {
头部定义 {
设置 ra 的值为 aa
清除 rd
}
}
/* 这是代码块的头部定义,设置 ra 的值为 aa 并清除 rd。*/
SLI再次得到改进(如截图中10/11所示)
DNSMasq缓存的命中率和未命中率指标有了显著的提升。
DNS 查询命中率/未命中率突然变化
嗯,经过这么多尝试后,问题应该已经被解决了,对吧? 并不是。
经过几周的调试这个问题后,每天检查SLI渐渐变成了一种习惯,甚至有点上瘾。再过了一周,我们开始觉得有点不对劲。
SLI再次变差了。
主要反派,N点帮为了收集真实数据,我们选择性地在一些有问题的节点上运行了名为DNStop的工具,这些节点显示出了一些可疑之处。
集群中的DNS请求中,无法解析的域名太多。
有大量“垃圾”域名涌入节点的DNS请求。这是著名的ndots 问题所导致的结果。
The ndots 问题及其对 Kubernetes DNS 性能的影响是众所周知的。这样的域名,如果名称中少于 5 个点,将会触发在配置中定义的多次本地搜索,从而不会立即进行外部查询。
少于五个点的域名会先通过本地的DNS服务器来解析。
因此,在这个问题上,我们发现了每秒数百次的请求,尝试解析以下域名中的主机:
schip.io.svc.cluster.local
cluster.local.cluster.local
cluster.local.svc.cluster.local
cluster.local.system-namespace.svc.cluster.local
grafana.net.platform-services.cluster.local
grafana.net.platform-services.svc.cluster.local
kube-system.platform-services.svc.cluster.local
svc.cluster.local.system-namespace.svc.cluster.local
即使有了适当的缓存措施,我们的一些集群规模大到足以每秒生成数百个这样的请求。特别是我们一直在研究的那个集群。
既然我们找到了真正的问题,我们就考虑采取几种措施来改善现状。
扔掉垃圾域名最好的做法是拦截那些无效的搜索并返回一个 NXDOMAIN 响应。最好是在靠近POD的地方这样做——在节点本地缓存(DNSMasq)里。
我们引入了一个配置,拦截这些无用域名并返回一个 NXDOMAIN 给客户端。这些请求将不会转发到我们的 CoreDNS 实例,从而防止它们被淹没。
我们向本地DNS配置添加了无效的域名。
我们在本地DNS中添加了一个配置来阻止无效的域名。
将完全限定域名用作主机名为了避免ndots配置问题,最直接的解决办法是让我们的主机名至少包含五个点。Fluent-bit是DNS请求的大户之一。我们把接收日志路由器的主机名从服务名改成完全合格的域名(FQDN),这样每当它需要发送日志时,就能每次都用正确的域名建立连接了。
因为它现在有五个点,所以不再进行本地搜索了。
域名包含五个层级
我们也联系了这些受影响的客户,建议他们把内部解析更改为FQDN,因为他们受影响的应用程序也生成了很多本地搜索。
行得通吗?在我们发现问题并应用了所有优化措施之后,让我们来看看这些措施的效果。
从我们开始修复缓存问题到我们放弃垃圾域名这段时间,性能改进情况如图表所示。你看,我们一实施缓存修复,性能就显著提升了,特别是在图表最右边。在图表最右边可以看到,我们的服务等级指标 (SLI) 有了明显的改进。
打补丁后的显著改进
另外,到达集群DNS(CoreDNS)的请求量从每秒超过4000个请求降至每秒不足1000个请求。
我们的SLO又回到了绿色状态,没有变得更糟。
DNS 延时 SLI 指标已恢复到 > 99.9%
为什么这件事只在一个集群里发生?在经历这段旅程的过程中,你们当中有些人可能感到困惑,为什么这个问题只影响到我们的一位客户,既然配置不是特定于某个集群,而是以相同的方式应用于所有集群。
事实上,这个问题确实影响了每一个人。我们发现了2至3个更大的几个集群,其DNS延迟SLI受到影响。然而,这里描述的集群由于其Pod密度(即容器密度)以及运行在上面的应用程序的行为,拥有舰队中最多的DNS查询量。特别是,我们发现有许多CronJobs被安排每X小时运行一次,这些任务安排时会恶化DNS健康状况。因此,问题主要在午夜时被报告。
蓝色线代表受影响最严重的集群,该集群每秒有超过130次DNS请求。
时间轴从这一个月的调试来看,我们很清楚地知道发生了什么事情。
时间轴
我们杀掉了带有代理应用程序的节点后,性能得到了提升。然而当它再次变差时,我们修复了DNS缓存。接着性能再次下降,直到我们丢弃了这些无用的DNS请求。
教训让我们谈谈它教会了我们团队的。
SLO的重要性及其影响这让我对SLO(服务级别目标)有了新的看法。我过去认为它是一个值得关注的指标,但从未将其作为保障措施。当SLO之前下降时,我从未优先去弄清楚性能下降的原因。
然而,你需要确保为你的SLO定义了明确、可靠且实用的SLI。否则,你可能会直接陷入无底洞中,甚至都不知道问题出在哪。
开展调查工作这个问题拖了一个月没解决,其中一个原因是我们的应对方法比较分散。我们的值班工程师们多次尝试解决,但这些尝试并没有很好地协调。
我们没有一个汇总所有观察信息的地方。因此,每个人研究这个问题都得重新开始。
我们也没有将所有的假设整理并优先列出在一个地方。有时我们在相同的假设上工作,甚至在已经被淘汰的假设上工作。
我亲爱的团队技术主管给了我一个超级重要的教训——在开始新的假设之前,先摒弃一个假设。专注于你的主要假设,并尽你所能去证明它是错的,直到你找到另一个办法来确定根本原因。
收集数据,好好研究基于上述原则,当你研究某个假设时,你需要尽可能深入地挖掘信息,找到你能找到的所有支持该假设的数据。有时候你可能从现有的度量系统中找不到所有你需要的数据,所以别害怕,发挥创意,去搜集更多的信息。
例如,运行DNStop工具给我们提供了非常有价值的信息,这使我们得出了正确的结论,而我们无法在我们的指标系统中获取这些信息。
规模经济的优势
这一事件影响了所有人,我们对DNS架构的改进也惠及了在我们平台上运行的所有其他客户,尽管他们并不知情,他们都从中受益。我们在多租户环境中进行的一个调整提升了每个人的体验,增加了我们努力的总体效果。
另外,我们还可以将这个问题中学到的经验应用到其他使用不同技术的平台中。
希望大家喜欢这篇博客,记住,有时候这可能真的和DNS有关哦。
共同学习,写下你的评论
评论加载中...
作者其他优质文章