一个存在三年的内核 bug 引发大量的容器系统出现网络故障

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介:

一个存在三年的内核 bug 引发大量的容器系统出现网络故障

最近发现的一个 Linux 内核 bug,会造成使用 veth 设备进行路由的容器(例如 Docker on IPv6、Kubernetes、Google Container Engine 和 Mesos)不检查 TCP 校验码checksum,这会造成应用在某些场合下,例如坏的网络设备,接收错误数据。这个 bug 可以在我们测试过的三年内的任何一个内核版本中发现。

这个问题的补丁已经被整合进核心代码,正在回迁入3.14之前的多个发行版中(例如SuSE,Canonical)。如果在自己环境中使用容器,强烈建议打上此补丁,或者等发布后,部署已经打上补丁的核心版本。

注:Docker 默认的 NAT 网络并不受影响,而实际上,Google Container Engine 也通过自己的虚拟网络防止了硬件错误。

编者:Jake Bower 指出这个 bug 跟一段时间前发现的另外一个 bug 很相似。有趣!

起因

​十一月的某个周末,一组 Twitter 负责服务的工程师收到值班通知,每个受影响的应用都报出 “impossible” 错误,看起来像奇怪的字符出现在字符串中,或者丢失了必须的字段。这些错误之间的联系并不很明确指向 Twitter 分布式架构。问题加剧表现为:任何分布式系统,数据,一旦出问题,将会引起更长的字段出问题(他们都存在缓存中,写入日志磁盘中,等等...)。经过一整天应用层的排错,团队可以将问题定位到某几个机柜内的设备。团队继续深入调查,发现在第一次影响开始前,进入的 TCP纠错码错误大幅度升高;这个调查结果将应用从问题中摘了出来,因为应用只可能引起网络拥塞而不会引起底层包问题。

编者:用“团队”这个词可能费解,是不是很多人来解决这个问题。公司内部很多工程师参与排错,很难列出每个人名字,但是主要贡献者包括:Brian Martin、David Robinson、Ken Kawamoto、Mahak Patidar、Manuel Cabalquinto、Sandy Strong、Zach Kiehl、Will Campbell、Ramin Khatibi、Yao Yue、Berk Demir、David Barr、Gopal Rajpurohit、Joseph Smith、Rohith Menon、Alex Lambert and Ian Downes、Cong Wang。

一旦机柜被移走,应用失效问题就解决了。当然,很多因素可以造成网络层失效,例如各种奇怪的硬件故障,线缆问题,电源问题,等....;TCP/IP 纠错码就是为保护这些错误而设计的,而且实际上,从这些设备的统计证据表明错误都可以检测到---那么为什么这些应用也开始失效呢?

隔离特定交换机后,尝试减少这些错误(大部分复杂的工作是由 SRE Brain Martin 完成的)。通过发送大量数据到这些机柜可以很容易复现失效数据被接收。在某些交换机,大约~10%的包失效。然而,失效总是由于核心的 TCP 纠错码造成的(通过netstat -a 返回的 TcpInCsumError 参数报告),而并不发送给应用。(在 Linux 中,IPv4 UDP 包可以通过设置隐含参数 SO_NO_CHECK,以禁止纠错码方式发送;用这种方式,我们可以发现数据失效)。

Evan Jones(@epcjones)有一个理论,说的是假如两个 bit 刚好各自翻转(例如0->1和1->0)失效数据有可能有有效的纠错码,对于16位序字节,TCP 纠错码会刚好抵消各自的错误(TCP 纠错码是逐位求和)。当失效数据一直在消息体固定的位置(对32位求模),事实是附着码(0->1)消除了这种可能性。因为纠错码在存储之前就无效了,一个纠错码 bit 翻转外加一个数据 bit 翻转,也会抵消各自的错误。然而,我们发现出问题的 bit 并不在 TCP 纠错码内,因此这种解释不可能发生。

很快,团队意识到测试是在正常的 linux 系统上进行的,许多 Twitter 服务是运行在 Mesos 上,使用 Linux 容器隔离不同应用。特别的,Twitter 的配置创建了 veth(虚拟以太网virtual ethernet)设备,然后将应用的包转发到设备中。可以很确定,当把测试应用跑在 Mesos 容器内后,立即发现不管 TCP 纠错码是否有效(通过 TcpInCsumErrors 增多来确认),TCP 链接都会有很多失效数据。有人建议激活 veth 以太设备上的 “checksum offloading” 配置,通过这种方法解决了问题,失效数据被正确的丢弃了。

到这儿,有了一个临时解决办法,Twitter Mesos 团队很快就将解决办法作为 fix 推给了 Mesos 项目组,将新配置部署到所有 Twiter 的生产容器中。

排错

当 Evan 和我讨论这个 bug 时,我们觉得由于 TCP/IP 是在 OS 层出现问题,不可能是 Mesos 不正确配置造成的,一定应该是核心内部网络栈未被发现 bug 的问题。

为了继续查找 bug,我们设计了最简单的测试流程:

  1. 单客户端打开一个 socket,每秒发送一个简单且长的报文​
  2. 单服务端(使用处于侦听模式的 nc)在 socket 端侦听,打印输出收到的消息。
  3. 网络工具,tc,可以用于发送前,任意修改包内容。
  4. 一旦客户端和服务端对接上,用网络工具失效所有发出包,发送10秒钟。

可以在一个台式机上运行客户端,服务器在另外一个台式机上。通过以太交换机连接两台设备,如果不用容器运行,结果和我们预想一致,并没有失效数据被接收到,也就是10秒内没有失效包被接收到。客户端停止修改包后,所有10条报文会立刻发出;这确认Linux端TCP栈,如果没有容器,工作是正常的,失效包会被丢弃并重新发送直到被正确接收为止。


这样是工作的:错误的数据不会接收,TCP转发数据

Linux 和容器

现在让我们快速回顾一下 Linux 网络栈如何在容器环境下工作会很有帮助。容器技术使得用户空间user-space应用可以在机器内共存,因此带来了虚拟环境下的很多益处(减少或者消除应用之间的干扰,允许多应用运行在不同环境,或者不同库)而不需要虚拟化环境下的消耗。理想地,任何资源之间竞争都应该被隔离,场景包括磁盘请求队列,缓存和网络。

Linux 下,veth 设备用于隔离同一设备中运行的容器。Linux 网络栈很复杂,但是一个 veth 设备本质上应该是用户角度看来的一个标准以太设备。

为了构建一个拥有虚拟以太设备的容器,必须:

  1. 创建一个虚机,
  2. 创建一个 veth,
  3. 将 veth 绑定与容器端,
  4. 给 veth 指定一个 IP 地址,
  5. 设置路由,用于 Linux 流量控制,这样包就可以进出容器了。

为什么是虚拟造成了问题

我们重建了如上测试场景,除了服务端运行于容器中。然后,当开始运行时,我们发现了很多不同:失效数据并未被丢弃,而是被转递给应用!通过一个简单测试(两个台式机,和非常简单的程序)就重现了错误。

video2.gif
失效数据被转递给应用,参见左侧窗口。

我们可以在云平台重建此测试环境。k8s 的默认配置触发了此问题(也就是说,跟 Google Container Engine 中使用的一样),Docker 的默认配置(NAT)是安全的,但是 Docker 的 IPv6 配置不是。​

修复问题

重新检查 Linux 核心网络代码,很明显 bug 是在 veth 核心模块中。在核心中,从硬件设备中接收的包有 ip_summed 字段,如果硬件检测纠错码,就会被设置为 CHECKSUM_UNNECESSARY,如果包失效或者不能验证,者会被设置为 CHECKSUM_NONE。

veth.c 中的代码用 CHECKSUM_UNNECESSARY 代替了 CHECKSUM_NONE,这造成了应该由软件验证或者拒绝的纠错码被默认忽略了。移除此代码后,包从一个栈转发到另外一个(如预期,tcpdump 在两端都显示无效纠错码),然后被正确传递(或者丢弃)给应用层。我们不想测试每个不同的网络配置,但是可以尝试不少通用项,例如桥接容器,在容器和主机之间使用 NAT,从硬件设备到容器见路由。我们在 Twitter 生产系统中部署了这些配置(通过在每个 veth 设备上禁止 RX checksum offloading)。

还不确定为什么代码会这样设计,但是我们相信这是优化设计的一个尝试。很多时候,veth 设备用于连接统一物理机器中的不同容器。

逻辑上,包在同一物理主机不同容器间传递(或者在虚机之间)不需要计算或者验证纠错码:唯一可能失效的是主机的 RAM,因为包并未经过线缆传递。不幸的是,这项优化并不像预想的那样工作:本地产生的包,ip_summed 字段会被预设为CHECKSUM_PARTIAL,而不是 CHECKSUM_NONE。

这段代码可以回溯到该驱动程序第一次提交(commit e314dbdc1c0dc6a548ecf [NET]: Virtual ethernet device driver)。 Commit 0b7967503dc97864f283a net/veth: Fix packet checksumming (in December 2010)修复了本地产生,然后发往硬件设备的包,默认不改变CHECKSUM_PARTIAL的问题。然而,此问题仍然对进入硬件设备的包生效。

核心修复补丁如下:

 
 
  1. diff - git a/drivers/net/veth.c b/drivers/net/veth.c
  2. index 0ef4a5a..ba21d07 100644
  3. - - a/drivers/net/veth.c
  4. +++ b/drivers/net/veth.c
  5. @@ -117,12 +117,6 @@ static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)
  6. kfree_skb(skb);
  7. goto drop;
  8. }
  9. - /* don’t change ip_summed == CHECKSUM_PARTIAL, as that
  10. - * will cause bad checksum on forwarded packets
  11. - */
  12. - if (skb->ip_summed == CHECKSUM_NONE &&
  13. - rcv->features & NETIF_F_RXCSUM)
  14. - skb->ip_summed = CHECKSUM_UNNECESSARY;
  15. if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) {
  16. struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats);

结论

我对 Linux netdev 和核心维护团队的工作很钦佩;代码确认非常迅速,不到几个星期补丁就被整合,不到一个月就被回溯到以前的(3.14+)稳定发行版本中(Canonical,SuSE)。在容器化环境占优势的今天,很惊讶这样的bug居然存在了很久而没被发现。很难想象因为这个 bug 引发多少应用崩溃和不可知的行为!如果确信由此引发问题请及时跟我联系。




本文来自云栖社区合作伙伴“Linux中国”

原文发布时间为:2013-04-02.


相关文章
|
6天前
|
NoSQL 应用服务中间件 Redis
Docker跨宿主机容器通信-通过网络跨宿主机互联
这篇文章介绍了Docker容器跨宿主机通信的实现方法,包括Docker的四种网络模式(host、none、container、bridge)以及如何通过修改网络配置和添加路由规则来实现不同宿主机上的容器之间的互联。
16 0
Docker跨宿主机容器通信-通过网络跨宿主机互联
|
10天前
|
传感器 SQL 运维
常见网络安全设备:IPS(入侵防御系统)零基础入门到精通,收藏这一篇就够了
常见网络安全设备:IPS(入侵防御系统)零基础入门到精通,收藏这一篇就够了
32 3
|
12天前
|
安全 网络安全 数据安全/隐私保护
云原生技术探索:容器化与微服务架构的实践之路网络安全与信息安全:保护数据的关键策略
【8月更文挑战第28天】本文将深入探讨云原生技术的核心概念,包括容器化和微服务架构。我们将通过实际案例和代码示例,展示如何在云平台上实现高效的应用部署和管理。文章不仅提供理论知识,还包含实操指南,帮助开发者理解并应用这些前沿技术。 【8月更文挑战第28天】在数字化时代,网络安全和信息安全是保护个人和企业数据的前线防御。本文将探讨网络安全漏洞的成因、加密技术的应用以及提升安全意识的重要性。文章旨在通过分析网络安全的薄弱环节,介绍如何利用加密技术和提高用户警觉性来构建更为坚固的数据保护屏障。
|
17天前
|
监控 网络协议 Linux
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?
在Linux中,如何实时抓取并显示当前系统中tcp 80 端口的网络数据信息?
|
20天前
|
缓存 Kubernetes 数据中心
在Docker中,如何控制容器占用系统资源(CPU,内存)的份额?
在Docker中,如何控制容器占用系统资源(CPU,内存)的份额?
|
22天前
|
网络协议 Linux 网络安全
遇到Docker容器网络隔断?揭秘六种超级实用解决方案,轻松让Docker容器畅游互联网!
【8月更文挑战第18天】Docker容器内网络不通是开发者常遇问题,可能因网络配置错、Docker服务异常或防火墙阻碍等原因引起。本文提供六种解决策略:确认Docker服务运行状态、重启Docker服务、检查与自定义Docker网络设置、验证宿主机网络连接、临时禁用宿主机IPv6及检查防火墙规则。通过这些步骤,多数网络问题可得以解决,确保容器正常联网。
35 1
|
27天前
|
机器学习/深度学习 数据采集 人工智能
未来的守护神:AI驱动的网络安全之盾,如何用智慧的光芒驱散网络黑暗势力?揭秘高科技防御系统背后的惊天秘密!
【8月更文挑战第13天】随着网络技术的发展,网络安全问题愈发严峻,传统防御手段已显乏力。本文探讨构建AI驱动的自适应网络安全防御系统,该系统能自动调整策略应对未知威胁。通过数据采集、行为分析、威胁识别及响应决策等环节,利用Python工具如Scapy、scikit-learn和TensorFlow实现网络流量监控、异常检测及自动化响应,从而提升网络安全防护的效率和准确性。随着AI技术的进步,未来的网络安全防御将更加智能和自动化。
32 6
|
26天前
|
弹性计算 Prometheus 监控
如何基于容器网络流量指标进行弹性伸缩
【8月更文挑战第13天】基于容器网络流量指标进行弹性伸缩可动态调整资源,提升系统性能与利用率。首先选监控工具如Prometheus,收集并分析网络流量数据。接着定义监控指标及阈值,如入站与出站流量。最后配置如Kubernetes的HPA实现自动化伸缩,并通过测试不断优化策略,确保系统稳定高效运行。
|
25天前
|
Linux
虚拟机安装Linux系统的网络配置
该博客文章提供了解决虚拟机中Linux系统网络问题的多种方法,包括重置网络服务、修改网络配置文件、使用不同网络模式等,以确保虚拟机能够成功连接到网络。
虚拟机安装Linux系统的网络配置
|
30天前
|
监控 Linux 测试技术
什么是Linux系统的网络参数?
【8月更文挑战第10天】什么是Linux系统的网络参数?
40 5