Linux内核bug引起Mesos、Kubernetes、Docker的TCP/IP数据包失效

简介: 本文讲的是Linux内核bug引起Mesos、Kubernetes、Docker的TCP/IP数据包失效,【编者的话】最近发现Linux内核bug,会造成使用veth设备进行路由的容器
本文讲的是Linux内核bug引起Mesos、Kubernetes、Docker的TCP/IP数据包失效 【编者的话】最近发现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栈,如果没有容器,工作是正常的,失效包会被丢弃并重新发送直到被正确接收为止。
video1.gif

The way it should work: corrupt data are not delivered; TCP retransmits data

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的问题。然而,此问题仍然对进入硬件设备的包生效。

核心修复补丁 如下:
diff — git a/drivers/net/veth.c b/drivers/net/veth.c
index 0ef4a5a..ba21d07 100644
— — a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -117,12 +117,6 @@ static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)
kfree_skb(skb);
goto drop;
}
- /* don’t change ip_summed == CHECKSUM_PARTIAL, as that
- * will cause bad checksum on forwarded packets
- */
- if (skb->ip_summed == CHECKSUM_NONE &&
- rcv->features & NETIF_F_RXCSUM)
- skb->ip_summed = CHECKSUM_UNNECESSARY;

if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) {
struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats);

结论

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

原文链接:Linux kernel bug delivers corrupt TCP/IP data to Mesos, Kubernetes, Docker containers(翻译:杨峰)

原文发布时间为: 2016-02-21
本文作者:hokingyang
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:Linux内核bug引起Mesos、Kubernetes、Docker的TCP/IP数据包失效
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
3月前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
167 2
|
3月前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
3月前
|
运维 Kubernetes Docker
利用Docker和Kubernetes构建微服务架构
利用Docker和Kubernetes构建微服务架构
|
2月前
|
存储 Kubernetes 开发者
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
Docker 是一种开源的应用容器引擎,允许开发者将应用程序及其依赖打包成可移植的镜像,并在任何支持 Docker 的平台上运行。其核心概念包括镜像、容器和仓库。镜像是只读的文件系统,容器是镜像的运行实例,仓库用于存储和分发镜像。Kubernetes(k8s)则是容器集群管理系统,提供自动化部署、扩展和维护等功能,支持服务发现、负载均衡、自动伸缩等特性。两者结合使用,可以实现高效的容器化应用管理和运维。Docker 主要用于单主机上的容器管理,而 Kubernetes 则专注于跨多主机的容器编排与调度。尽管 k8s 逐渐减少了对 Docker 作为容器运行时的支持,但 Doc
172 5
容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档
|
2月前
|
存储 Kubernetes Docker
Kubernetes(k8s)和Docker Compose本质区别
理解它们的区别和各自的优势,有助于选择合适的工具来满足特定的项目需求。
228 19
|
3月前
|
运维 Kubernetes Docker
深入理解容器化技术:Docker与Kubernetes的协同工作
深入理解容器化技术:Docker与Kubernetes的协同工作
102 14
|
3月前
|
Kubernetes 监控 云计算
Docker与Kubernetes的协同工作
Docker与Kubernetes的协同工作
|
3月前
|
运维 Kubernetes Cloud Native
云原生技术入门:Kubernetes和Docker的协同工作
【10月更文挑战第43天】在云计算时代,云原生技术成为推动现代软件部署和运行的关键力量。本篇文章将带你了解云原生的基本概念,重点探讨Kubernetes和Docker如何协同工作以支持容器化应用的生命周期管理。通过实际代码示例,我们将展示如何在Kubernetes集群中部署和管理Docker容器,从而为初学者提供一条清晰的学习路径。
|
3月前
|
Kubernetes 开发者 Docker
Docker与Kubernetes的协同工作
Docker与Kubernetes的协同工作
|
3月前
|
Kubernetes 监控 安全
容器化技术:Docker与Kubernetes的实战应用
容器化技术:Docker与Kubernetes的实战应用