现代数据中心安装了数百个有源设备,覆盖不同类型的监控。 但即使是拥有完美监控能力的理想工程师也能够在短短几分钟内正确响应网络故障。 在Next Hop 2020会议的报告中,我提出了一种DC网络设计方法,它有一个独特的功能——数据中心在毫秒内自我修复。 更准确地说,工程师冷静地解决问题,而服务人员根本没有注意到它。
对于许多网络工程师来说,数据中心网络当然是从 ToR 开始的,即机架中的交换机。 ToR 通常有两种类型的链接。 小的进入服务器,其他的——有N倍多——进入第一层的主干,即它的上行链路。 上行链路通常被认为是相等的,上行链路之间的流量基于 5 元组的哈希进行平衡,其中包括 proto、src_ip、dst_ip、src_port、dst_port。 这里没有什么惊喜。
接下来,规划架构是什么样的? 第一层的脊椎彼此不连接,而是通过超级脊椎连接。 字母 X 将负责超级脊柱;它几乎就像一个交叉连接。
另一方面,很明显,托里与第一层的所有脊柱相连。 这张图片中重要的是什么? 如果我们在机架内部进行交互,那么交互当然会通过 ToR。 如果交互发生在模块内部,则交互通过第一级脊椎发生。 如果交互是模块间的(如此处的 ToR 1 和 ToR 2),则交互将通过第一层和第二层的脊柱。
理论上,这样的架构很容易扩展。 如果我们有端口容量、数据中心的空闲空间和预敷光纤,那么通道数量总是可以增加,从而增加系统的整体容量。 这在纸上很容易做到。 生活中也会如此。 但今天的故事不是这个。
我希望得出正确的结论。 我们在数据中心内部有很多路径。 他们是有条件独立的。 数据中心内部的一条路径只能在 ToR 内部进行。 在模块内部,路径的数量等于车道的数量。 模块之间的路径数量等于平面数量与每个平面中超级脊柱数量的乘积。 为了更清楚地了解规模,我将给出对 Yandex 数据中心之一有效的数字。
有八个平面,每个平面有 32 个超级脊柱。 结果,模块内部有 256 条路径,通过模块间交互,路径已经有 XNUMX 条。
也就是说,如果我们正在开发Cookbook,试图学习如何构建自我修复的容错数据中心,那么平面架构是正确的选择。 它解决了扩展问题,理论上很容易。 有许多独立的路径。 问题仍然是:这样的架构如何在失败中幸存下来? 有各种各样的失败。 我们现在将讨论这个问题。
让我们的超级脊柱之一“生病”。 这里我又回到了两平面架构。 我们将继续以这些作为示例,因为使用更少的移动部件可以更容易地了解发生了什么。 让X11生病吧。 这将如何影响数据中心内的服务? 很大程度上取决于失败的实际情况。
如果故障是好的,它被捕获在同一 BFD 的自动化级别,自动化会愉快地放置有问题的接头并隔离问题,然后一切都很好。 我们有很多路径,流量会立即重新路由到替代路线,并且服务不会注意到任何事情。 这是一个好剧本。
一个糟糕的情况是,如果我们不断遭受损失,而自动化却没有注意到这个问题。 为了了解这对应用程序有何影响,我们必须花一些时间讨论 TCP 的工作原理。
我希望我不会让任何人感到震惊:TCP 是一种传输确认协议。 也就是说,在最简单的情况下,发送方发送两个数据包并收到对它们的累积确认:“我收到了两个数据包。”
之后,他会再发送两个数据包,情况又会重复。 对于一些简化,我提前表示歉意。 如果窗口(传输中的数据包数量)为 XNUMX,则这种情况是正确的。 当然,一般情况下不一定如此。 但窗口大小不影响数据包转发上下文。
如果我们丢失数据包 3 会发生什么? 在这种情况下,接收者将收到数据包 1、2 和 4。并且他将使用 SACK 选项明确地告诉发送者:“你知道,三个到达了,但中间丢失了。” 他说:“Ack 2,SACK 4。”
此时,发送方可以毫无问题地重复丢失的数据包。
但如果窗口中的最后一个数据包丢失,情况就会看起来完全不同。
接收方收到前三个数据包,首先开始等待。 由于 Linux 内核的 TCP 堆栈中的一些优化,它将等待配对的数据包,除非标志明确指示它是最后一个数据包或类似的东西。 它将等到延迟 ACK 超时到期,然后发送前三个数据包的确认。 但现在发件人将等待。 他不知道第四个包裹是丢失了还是即将到达。 为了不使网络过载,它将尝试等待数据包丢失的明确指示,或者等待 RTO 超时到期。
什么是RTO超时? 这是由 TCP 堆栈计算的 RTT 的最大值和一些常数。 这是一个什么样的常数,我们现在来讨论。
但重要的是,如果我们再次不走运,第四个数据包再次丢失,那么 RTO 就会翻倍。 也就是说,每次不成功的尝试都意味着超时时间加倍。
现在让我们看看这个基数等于什么。 缺省情况下,最小RTO为200毫秒。 这是数据包的最小 RTO。 对于 SYN 数据包,则不同,为 1 秒。 正如您所看到的,即使是第一次尝试重新发送数据包,也会花费比数据中心内的 RTT 长 100 倍的时间。
现在让我们回到我们的场景。 服务怎么了? 服务开始丢失数据包。 首先让服务有条件地幸运,并在窗口中间丢失一些东西,然后它收到 SACK 并重新发送丢失的数据包。
但如果坏运气重演,那么我们就会面临 RTO。 这里重要的是什么? 是的,我们的网络中有很多路径。 但某个特定 TCP 连接的 TCP 流量将继续通过同一个损坏的堆栈。 丢包,只要我们这个神奇的X11不会自行出去,就不会导致流量流入没有问题的区域。 我们正在尝试通过同一个损坏的堆栈传递数据包。 这会导致级联故障:数据中心是一组交互的应用程序,所有这些应用程序的一些 TCP 连接开始降级 - 因为超级主干会影响数据中心内存在的所有应用程序。 俗话说:不掌蹄,马跛; 马跛了——报告没有送达; 报告没有交付——我们输掉了战争。 只是这里的计数是从问题出现到服务开始下降的阶段的秒数。 这意味着用户可能会错过某些地方的东西。
有两种相辅相成的经典解决方案。 第一个是试图投入救命稻草并解决问题的服务,如下所示:“让我们在 TCP 堆栈中调整一些东西。 让我们通过内部健康检查在应用程序级别或长期 TCP 会话上设置超时。” 问题是这样的解决方案:a)根本无法扩展; b) 检查非常差。 也就是说,即使服务意外地以一种使其变得更好的方式配置了 TCP 堆栈,首先,它不太可能适用于所有应用程序和所有数据中心,其次,很可能,它不会理解它已完成正确与否。 也就是说,它可以工作,但效果很差并且无法扩展。 如果出现网络问题,谁该负责? 当然,国家奥委会。 国家奥委会是做什么的?
许多军种认为,在 NOC 工作中会发生类似的事情。 但说实话,不仅如此。
经典方案中的NOC从事许多监控系统的开发。 这些都是黑盒和白盒监控。 关于黑匣子脊椎监控的示例
你真正想收到什么? 我们有很多方法。 问题的出现正是因为不幸的 TCP 流继续使用相同的路由。 我们需要允许我们在单个 TCP 连接中使用多个路由的东西。 看来我们有一个解决方案。 还有TCP,称为多路径TCP,即多路径的TCP。 确实,它是为完全不同的任务而开发的 - 用于具有多个网络设备的智能手机。 为了最大化传输或建立主/备份模式,开发了一种机制,该机制可以对应用程序透明地创建多个线程(会话),并允许您在发生故障时在它们之间进行切换。 或者,正如我所说,最大化连胜。
但这里有一个细微差别。 为了理解它是什么,我们必须看看线程是如何建立的。
线程是按顺序安装的。 首先安装第一个线程。 然后使用该线程内已商定的 cookie 来设置后续线程。 问题就在这里。
问题是,如果第一个线程没有自行建立,那么第二个和第三个线程将永远不会出现。 也就是说,多路径TCP并不能解决第一流中SYN报文丢失的问题。 如果 SYN 丢失,多路径 TCP 就会变成常规 TCP。 这意味着在数据中心环境中它不会帮助我们解决工厂中的损失问题并学会在发生故障时使用多条路径。
有什么可以帮助我们呢? 你们中的一些人已经从标题中猜到,我们接下来的故事中的一个重要字段将是 IPv6 流标签标头字段。 确实,这是v6中出现的字段,v4中没有,它占用20位,而且长期以来关于它的使用一直存在争议。 这非常有趣 - 存在争议,RFC 中修复了某些内容,同时 Linux 内核中出现了一个实现,但没有任何地方记录。
我邀请你和我一起去进行一个小小的调查。 让我们看一下过去几年Linux内核发生了什么。
2014 年。 来自一家受人尊敬的大公司的工程师将流标签值对套接字哈希的依赖性添加到 Linux 内核的功能中。 他们想在这里解决什么问题? 这与 RFC 6438 相关,RFC 4 讨论了以下问题。 在数据中心内部,IPv6常常封装在IPv6数据包中,因为工厂本身就是IPv4,但外部必须以某种方式给出IPv5。 很长一段时间以来,交换机都存在无法查看两个 IP 标头以到达 TCP 或 UDP 并找到 src_ports、dst_ports 的问题。 事实证明,如果你查看前两个 IP 标头,哈希值几乎是固定的。 为了避免这种情况,以便该封装流量的平衡正常工作,建议将 XNUMX 元组封装数据包的散列添加到流标签字段的值中。 对于其他封装方案,UDP、GRE 也做了大致相同的事情,后者使用了 GRE 密钥字段。 无论如何,这里的目标是明确的。 至少在那个时候它们是有用的。
2015 年,同一位受人尊敬的工程师发布了新补丁。 他很有趣。 它说明了以下内容 - 我们将在出现负面路由事件的情况下随机化哈希值。 什么是负面路由事件? 这就是我们前面讨论的RTO,即窗口尾部丢失是一个真正负面的事件。 确实,要猜到就是这样是相对困难的。
2016年,又一家有信誉的公司,也很大。 它分解了最后的拐杖,并使得我们之前随机生成的哈希值现在在每次 SYN 重传和每次 RTO 超时后都会发生变化。 在这封信中,第一次也是最后一次阐述了最终目标——确保流量在丢失或信道拥塞的情况下能够软性重新路由并使用多条路径。 当然,这之后还有很多出版物,你很容易就能找到。
虽然不可以,但你不能,因为还没有关于这个主题的出版物。 但我们知道!
如果你不完全明白做了什么,我现在就告诉你。
Linux 内核做了什么,添加了哪些功能? 每次 RTO 事件后,txhash 都会更改为随机值。 这是路由带来的非常负面的结果。 哈希取决于这个 txhash,流标签取决于 skb 哈希。 这里有一些函数的计算,所有的细节不可能放在一张幻灯片上。 如果有人好奇,你可以通过内核代码检查一下。
这里重要的是什么? 流标签字段的值在每个 RTO 之后更改为随机数。 这对我们不幸的 TCP 流有何影响?
如果发生 SACK,则不会发生任何变化,因为我们正在尝试重新发送已知丢失的数据包。 到目前为止,一切都很好。
但在 RTO 的情况下,如果我们在 ToR 上的哈希函数中添加了流标签,流量可能会采取不同的路线。 通道越多,找到不受特定设备故障影响的路径的机会就越大。
仍然存在一个问题——RTO。 当然,还有另外一条路线,但是在这上面浪费了很多时间。 200 毫秒已经很多了。 一秒钟绝对是疯狂的。 之前,我谈到了服务配置的超时。 所以,一秒就是一个超时,这通常是由服务在应用层面配置的,这样服务就会比较正确。 此外,我再说一遍,现代数据中心内的真实 RTT 约为 1 毫秒。
您可以利用 RTO 超时做什么? 超时负责在数据包丢失时的 RTO,可以从用户空间相对轻松地配置:有一个 IP 实用程序,其参数之一包含相同的 rto_min。 当然,考虑到RTO不需要全局调整,而是针对给定的前缀,这样的机制看起来相当可行。
确实,使用 SYN_RTO 一切都会变得更糟。 自然就被钉牢了。 内核有一个固定值 1 秒,仅此而已。 您无法从用户空间到达那里。 只有一种方法。
eBPF 来救援。 简单来说,这些都是小C程序,它们可以在内核堆栈和TCP堆栈的执行过程中的不同地方插入钩子,用它们可以改变非常多的设置。 总的来说,eBPF是一个长期趋势。 该运动不是削减数十个新的 sysctl 参数并扩展 IP 实用程序,而是转向 eBPF 并扩展其功能。 使用 eBPF,您可以动态更改拥塞控制和各种其他 TCP 设置。
但对我们来说很重要的是它可以用来更改 SYN_RTO 值。 另外,还有一个公开的例子:
我们已经知道什么? 事实上,平面架构允许扩展,当我们在 ToR 上启用流标签并获得绕过问题区域的能力时,它对我们非常有用。 降低RTO和SYN-RTO值的最佳方法是使用eBPF程序。 问题仍然是:使用流标签进行平衡是否安全? 这里有一个细微差别。
假设您的网络上有一项以选播方式存在的服务。 不幸的是,我没有时间详细介绍什么是选播,但它是一种分布式服务,可以通过相同的 IP 地址访问不同的物理服务器。 这里有一个可能的问题:RTO 事件不仅会在流量通过结构时发生。 它也可能发生在 ToR 缓冲区级别:当发生 incast 事件时,甚至可能在主机溢出某些内容时发生在主机上。 当 RTO 事件发生并且它更改流标签时。 在这种情况下,流量可以流向另一个任播实例。 我们假设这是一个有状态的任播,它包含一个连接状态 - 它可能是一个 L3 Balancer 或一些其他服务。 那么问题就出现了,因为在RTO之后,TCP连接到达服务器,而服务器对这个TCP连接一无所知。 如果我们在选播服务器之间没有状态共享,那么此类流量将被丢弃,TCP 连接将被中断。
你在这里能做什么? 在启用流标签平衡的受控环境中,您需要在访问任播服务器时记录流标签的值。 最简单的方法是通过相同的 eBPF 程序来完成此操作。 但这里有一个非常重要的点——如果你不是运营数据中心网络,而是电信运营商怎么办? 这也是您的问题:从 Juniper 和 Arista 的某些版本开始,它们默认在哈希函数中包含流标签 - 坦率地说,出于我不清楚的原因。 这可能会导致您断开通过您网络的用户的 TCP 连接。 因此,我强烈建议您在此处检查路由器设置。
无论如何,在我看来,我们已经准备好继续进行实验了。
当我们在 ToR 上启用流标签,准备好 eBPF 代理(现在它位于主机上)时,我们决定不等待下一次重大故障,而是进行受控爆炸。 我们采用了具有四个上行链路的 ToR,并在其中之一上设置了分站。 他们制定了一条规则并说 - 现在你将丢失所有数据包。 正如您在左侧看到的,我们有逐包监控,已下降到 75%,即有 25% 的数据包丢失。 右侧是该 ToR 背后的服务图表。 本质上,这些是机架内服务器接口的流量图。 正如你所看到的,它们跌得更低。 为什么它们会下降——不是下降 25%,而是在某些情况下下降了 3-4 倍? 如果 TCP 连接不走运,它会继续尝试通过损坏的连接点进行连接。 DC 内部服务的典型行为加剧了这种情况 - 对于一个用户请求,会生成 N 个对内部服务的请求,并且当所有数据源响应时,或者当应用程序发生超时时,响应将发送给用户level,仍然需要配置。 也就是说,一切都非常非常糟糕。
现在进行相同的实验,但启用了流标签值。 正如您所看到的,在左侧,我们的批量监控也下降了 25%。 这是绝对正确的,因为它不知道有关重传的任何信息,它发送数据包并简单地计算已发送和丢失的数据包数量的比率。
右边是服务时间表。 您不会在这里发现有问题的接头的影响。 在同一毫秒内,流量从问题区域流向其余三个未受问题影响的上行链路。 我们有一个可以自我修复的网络。
这是我的最后一张幻灯片,是时候总结一下了。 现在,我希望您知道如何构建一个自我修复的数据中心网络。 您不需要浏览 Linux 内核存档并在那里查找特殊补丁;您知道本例中的 Flow 标签可以解决问题,但您需要仔细处理此机制。 我再次强调,如果你是电信运营商,你不应该使用流标签作为哈希函数,否则你会扰乱用户的会话。
网络工程师必须经历概念上的转变:网络不是从ToR、不是从网络设备开始,而是从主机开始。 一个相当引人注目的例子是我们如何使用 eBPF 来更改 RTO 并修复选播服务的流标签。
流标签机制当然适用于受控管理段内的其他应用。 这可以是数据中心之间的流量,或者您可以以特殊方式使用此类机制来管理传出流量。 但我希望下次我会告诉你这一点。 非常感谢您的关注。
来源: habr.com