编译:bot(才云)

技术校对:星空下的文仔(才云)

编者按:将应用迁移到 Kubernetes 时,有时候工程师们会发现一些令人费解的连接超时,无论怎么排查都找不到原因。在这篇文章中,软件架构师 Maxime Lagresle分享了自己团队的亲身经验。

Linux 内核有一个众所周知的问题,就是在做 SNAT(修改数据包的源地址)时容易出现 SYN 丢包。

默认情况下,SNAT 一般使用 iptables 伪装规则在 Docker 和 Flannel 的传出连接上执行,当多个容器同时尝试和同一外部地址建立新连接时,丢包情况就会发生。在一些情景下,可能两个连接会被分配到一个端口用做地址转换,导致一个或多个丢包,以及至少 1 秒的连接延迟

Linux 的源代码中提到了这个现象,内核也已经支持一个缓解此问题的 flag,但目前社区还没有太多相关文档。为了让更多开发者,尤其是不熟悉 DNAT 的 Kubernetes 新手提前做好心理准备,在这篇文章中,我们将尝试去调查这个问题,并寻找可靠的解决方案。

注:DNAT 上也存在相同的问题,这意味着在 Kubernetes 上,你访问 ClusterIP 时也可能丢包。当你从 Pod 发送请求到 ClusterIP,默认情况下,kube-proxy(通过 iptables)会把 ClusterIP 改成你想要访问的 Service 的某个 Pod IP,而该问题的存在会导致 DNS 解析域名时出现间歇性延迟,详参 Issue 56903

背景简介

Maxime Lagresle 是某公司后端架构团队的软件架构师。他的团队花了一年时间构建了一个 PaaS,经过谨慎评估,他们决定把基于 Capistrano/Marathon/Bash 的部署都迁移到 Kubernetes 上。

那时候,他们的设置依赖 Ubuntu Xenial 虚拟机(Docker v17.06)、Kubernetes v1.8,以及在主机网关模式下运行的 Flannel v1.9.0 。

在迁移时,Maxime Lagresle 团队注意到,当把这些部署放到 Kubernetes 上运行时,应用程序中的连接超时会明显增加。而在他们把第一个基于 Scala 的应用程序迁移上去后,超时的情况就更明显了——相比平时的几百毫秒,几乎每一秒都会有一个响应非常慢的请求。但这个应用程序非常普通,只负责公开 REST 端点并查询平台上的其他 Service,收集、处理数据并将数据返回给客户端。

经过观察,他们发现请求的响应时间很奇怪,几乎都被延迟了 1-3 秒。于是他们决定找出原因。

缩小问题范围

在他们的待办列表中,有一项任务是监控 KubeDNS 的表现。由于依赖 HTTP 客户端,名称解析时间可能是连接时间的一部分,他们决定先处理该任务,并确保该组件运行良好。

为了判断这一猜想,Maxime Lagresle 团队写了一个小的 DaemonSet,它可以直接查询 KubeDNS 和数据中心名称服务器,并将响应时间发送到 InfluxDB。很快,图表显示响应时间非常快,名称解析并不是延迟的罪魁祸首

之后,他们又将重点转移到探索这些延迟背后的含义上。负责该 Scala 应用的团队先做了一些修改,使响应慢的请求在后台继续发送,并在向客户端抛出超时错误后记录持续时间。Maxime Lagresle 团队则在运行应用程序的 Kubernetes 节点上做网络跟踪,尝试将响应慢的请求与网络转储的内容进行匹配。

10.244.38.20 尝试连接到端口 80 上的 10.16.46.24

结果显示,延迟是由第一个网络数据包的重传引起的,该数据包的作用是启动连接(具有 SYN 标志的数据包)。这解释了请求响应时间的延迟量为什么是 1-3 秒,因为这种数据包的重传延迟是 1 秒(第二次)、3 秒(第三次)、6 秒、12 秒、24 秒……以此类推。

这是一个有趣的发现,因为丢失 SYN 数据包不是由随机网络故障导致的,而可能是网络设备或 SYN 泛洪保护算法主动丢弃了新连接。

在默认的 Docker 安装中,每个容器都有一个虚拟网络接口(veth)上的 IP,它和主接口(如 eth0、docker0)一样连接着 Docker 主机上的 Linux 网桥。容器通过网桥互相通信,如果容器想连接 Docker 主机外部地址,那么数据包会先进入网桥,再通过 eth0 路由到服务器外部。

下图的示例已经对默认 Docker 设置进行了调整,以匹配网络捕获中看到的网络配置:

实际上 veth 端口是成对出现的,但这里不需要关注这点

Maxime Lagresle 团队随机查看了网桥上的数据包,之后继续查看虚拟机的主接口 eth0,并根据结果集中查看网络基础架构和虚拟机。

10.244.38.20 尝试连接到端口 80 上的 10.16.46.24

网络捕获结果显示,第一个 SYN 包在 13:42:23.828339 从容器网络接口(veth)离开,经过网桥(cni0)。在 13:42:24.826211 待了 1 秒后,容器没有从 10.16.46.24 得到响应就重传了这个包。所以再一次,这个包会先出现在容器的网络接口,然后是网桥。

在下一行,我们可以看到这个包在 IP 地址和端口从 10.244.38.20:38050 转换成 10.16.34.2:10011 之后,在 13:42:24.826263 离开了 eth0。下一行显示远程服务是如何响应的。

很明显,问题很可能出在虚拟机上,与其他基础架构无关。为了配置更灵活的解决方案,他们编写了一个非常简单的 Go 程序,可以通过一些可配置的设置对端点发出请求:

  • 两个请求之间的延迟;
  • 并发请求的数量;
  • 超时;
  • 要调用的端点。

要连接的远程端点是具有 Nginx 的虚拟机,测试程序将针对此端点发出请求,并记录任何高于一秒的响应时间。

Go 程序:https://github.com/maxlaverse/snat-race-conn-test

解决问题

已知数据包是在网桥之间丢包的,eth0 正是执行 SNAT 操作的地方。所以,如果因为某些原因,导致 Linux 内核无法分配一个空闲的源端口来做地址转换,他们就看不到这个包出 eth0。

这里可以设一个简单的测试来做验证——尝试 pod-to-pod 通信并记录响应延迟的请求的数量。Maxime Lagresle 团队做了测试,发现没有丢包。接下来,他们准备深入看一下 conntrack。

注:为了更好地理解后续内容,建议读者先了解一些关于源网络地址转换的知识。

用户空间中的 conntrack

在整个过程中,他们提出了一系列猜想:

猜想一:大部分连接都被转成相同的 host:port

这一点已经被否定了。

猜想二:这个现象很可能是一些配置错误的 SYN 泛洪保护引起的。

他们检查了网络内核参数,并没有找到自己不知道的机制;增加了 conntrack 表的大小,内核日志也没有报错。所以这个猜想也不正确。

猜想三:端口复用。如果端口耗尽并且没有可用于 SNAT 操作的端口,那么数据包是可能会被丢弃或拒绝的。

他们查看了 conntrack 表,发现 conntrack 包有一个命令,可以显示一些统计信息(conntrack -S)。在运行该命令时,有一个字段非常有趣:“insert_failed”具有非零值。

他们再次运行测试程序,并密切关注字段的统计信息,发现如果按一个丢包导致 1 秒请求延迟、两个丢包导致 3 秒请求延迟来算,统计信息里的数据和丢包的数量是完全一致的!

man page 上有对那个字段的清楚描述:尝试列表插入但失败的条目数(如果已存在相同的条目,则会发生)。但这个描述没有解答在什么情况下插入会失败?无论如何,在低负载服务器发生丢包怎么看都不像是正常现象。

Netfilter NAT & Conntrack 内核模块

在阅读了 Netfilter 内核代码之后,他们决定重新编译它并添加一些跟踪。

NAT 代码在 POSTROUTING 链上被 hook 了两次。首先,通过修改源 IP 和端口来修改包的结构;其次,如果期间没有丢包,在 conntrack 表中记录转换。这意味着 SNAT 端口分配与表中的插入之间存在延迟,如果存在冲突和数据包丢失,则可能会导致插入失败。

在 tcp 连接上执行 SNAT 时,NAT 模块会做以下尝试:

  • 第一步:如果数据包的源 IP 在目标 NAT 池中,并且元组可用,则返回(数据包保持不变);
  • 第二步:找到 NAT 池中使用最少的 IP,并用它替换数据包中的源 IP;
  • 第三步:检查端口是否在允许的端口范围内(默认为 1024-64512),以及具有该端口的元组是否可用。如果是,请返回(源 IP 已更改,端口保留)。注:SNAT 端口范围不受内核参数值net.ipv4.ip_local_port_rangekernel 的影响;
  • 第四步:端口不可用,通过调用 nf_nat_l4proto_unique_tuple() 请求 tcp 层为 SNAT 找到一个唯一的端口。

按照上述步骤,当主机仅运行一个容器时,NAT 模块很可能在第三步之后返回,容器内部进程使用的本地端口也将被保留并用于传出连接。当 Docker 主机运行多个容器时,连接的源端口更可能已被另一个容器的连接使用。在这种情况下,调用 nf_nat_l4proto_unique_tuple () 以查找 NAT 操作的可用端口。

Netfilter 还支持另外两种算法来查找 SNAT 空闲端口:

  • 部分随机端口搜索初始位置分配。在 SNAT 规则有NF_NAT_RANGE_PROTO_RANDOM flag 的时候被使用;
  • 完全随机端口搜索初始位置分配。在规则有 NF_NAT_RANGE_PROTO_RANDOM_FULLY flag 的时候被使用。

NF_NAT_RANGE_PROTO_RANDOM 虽然降低了两个线程以相同初始端口启动的次数,但仍有很多错误。Maxime Lagresle 团队在使用 NF_NAT_RANGE_PROTO_RANDOM_FULLY 时,才发现 conntrack 表插入错误的次数明显变少:在 Docker 测试虚拟机上,使用默认伪装规则,且有 10-80 个线程连接到同一主机时,conntrack 表的插入失败率大约在 2% 到 4%;如果在内核强制使用完全随机,错误就降到了 0(在真实集群中确实也接近 0)。

激活 K8s 上的完全随机端口分配

NF_NAT_RANGE_PROTO_RANDOM_FULLY flag 需要在伪装规则中设置。在Maxime Lagresle 团队的 Kubernetes 设置中,Flannel 负责添加这些规则。它在 Docker 镜像构建期间从源代码构建 iptables。iptables 工具不支持设置此 flag,但 Maxime Lagresle 团队已经提交了一个合并的小补丁并添加了此功能。

Flannel 补丁:

https://gist.github.com/maxlaverse/1fb3bfdd2509e317194280f530158c98

他们现在使用的是应用了这个补丁的 Flannel 修改版本,并在伪装规则上增加了 --random-fullyflag(4 行更改)。通过 DaemonSet,他们可以在每个节点上获取 conntrack 统计信息,并将指标发送到 InfluxDB 以密切关注插入错误。

通过这个补丁,他们的错误数量已经从每个节点几秒一次下降到整个集群几小时一次,效果显著。

小结

Kubernetes 正值快速发展,但上述问题却到现在还存在着,讨论它的人也不多,这一点是令人惊讶的。随着越来越多应用程序连接到相同的端点,这一问题的严重性将被很快暴露出来

我们需要采取一些额外的解决措施,因为每个人都在使用这种 DNS 循环,或者将 IP 添加到每个主机的 NAT 池。如果你在工作中曾遇到过这个问题,希望这篇文章对你有所帮助!

NETGEAR拒绝连接请求_破案:Kubernetes/Docker 上无法解释的连接超时相关推荐

  1. NETGEAR拒绝连接请求_详解 Tomcat 的连接数与线程池

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达 关注公众号后台回复pay或mall获取实战项目资料视频 点击此链接:多套SpringCloud/SpringBoot实 ...

  2. ansible 建 kubernetes 证书签名请求_基于Kubernetes的云平台存储容器化实践

    本文根据蔡逸煌老师在[Deeplus直播第214期]线上分享演讲内容整理而成.(文末有获取本期PPT&回放的途径,不要错过) 蔡逸煌 OPPO云平台高级后端工程师 主要从事云平台开发工作,擅长 ...

  3. mysql外连接插座_深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接...

    1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接. 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行.例如,检索 students和 ...

  4. mysql与java连接查询_【java】MySQL数据库之连接查询

    连接查询 首先来认识一个叫笛卡尔积 (cartesian product) 的东东,也可以叫直积. 假设我们有一个集合 A = {a, b}, 还有一个集合B = {0, 1, 2} ,那么这两个两集 ...

  5. mybatis 连接池_应用框架之Mybatis数据源和连接池

    本文将从以下几个方面介绍Mybatis的数据源和连接池: MyBatis数据源DataSource分类 数据源DataSource的创建过程 DataSource什么时候创建Connection对象 ...

  6. volumio怎么连接屏幕_如何把拼接屏和电脑连接起来

    目前,拼接屏的应用范围不断扩大,液晶拼接屏终端是需要与电脑连接才能显示播放,那么拼接屏和电脑是怎么连接的呢? 现在有很多单位.企业展厅.会议室等都能见到液晶拼接屏的身影.包括我们在大街上经常会看到的大 ...

  7. mysql 左外连接原理_深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接...

    1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接. 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行.例如,检索 students和 ...

  8. 在桌面上创建一个宽带连接服务器,win7怎么在桌面上创建宽带连接图标 桌面上创建宽带连接教程...

    如果我们想要上网的话一定要进行宽带连接,除非你通过路由器或者其他共享,但是在路由器上的那台电脑也要配置宽带连接,如果系统重装了或由于个人操作引起一些文件丢失而使电脑上的桌面没有"宽带连接&q ...

  9. NETGEAR拒绝连接请求_习惯了独来独往,该怎么与别人建立连接?| KY咨询师信箱Vol.44...

    亲爱的咨询师: 您好.受我这种别扭的性格困扰好久了,不知道为什么,我不知道怎么跟别人建立亲密关系. 在与别人相处的过程中,我总是把自己放在很低的位置,过分在意别人的想法,不敢表达自己的真实想法,说话总 ...

最新文章

  1. linux java的安装目录,linux java 安装目录
  2. C# 存储过程 分页
  3. Kafka如何通过精妙的架构设计优化JVM GC问题
  4. 检查mysql的replication_MySQL Replication需要注意的问题
  5. 多帧点云数据拼接合并_自动驾驶:Lidar 3D传感器点云数据和2D图像数据的融合标注...
  6. java记事本保存_JAVA记事本关于保存
  7. Matlab--colorbar的各项细节操作
  8. c++读取excel_Java 嵌入 SPL 轻松实现 Excel 文件合并
  9. 【报告分享】2019年用户生命周期运营白皮书(京东尼尔森出品).pdf(附下载链接)
  10. [AD19] 使用元器件向导为元件绘制PCB封装
  11. 屏幕分辨率修改工具SwitchResX for Mac
  12. 版本控制工具--CVS
  13. 全国宏观经济指标(图表以及相应的大致注释)
  14. 解决打开EXCEL插件时报错“配置系统未能初始化”的问题
  15. 海龟作图python等边三角形_python 海龟作图
  16. 对话杨宁:巨头搞不成区块链,落地的最大阻碍是“习惯”
  17. linux ipad 视频,是否可以在iPad或iPad上同时播放多个视频文件?
  18. Google hacking(谷歌语法)
  19. DDIM代码详细解读(3):核心采样代码、超分辨率重建
  20. ie兼容模式下跨域访问问题的解决

热门文章

  1. 基础功能2-python修改文件中所有文件名
  2. Pytest之pytest.assume用例中断言1失败会继续执行后续代码断言2
  3. 【数据库】Mysql删除重复记录只保留一条
  4. 如何编写自定义的Web控件
  5. 【PAT】B1055 集体照(25 分)
  6. 对Linux命令od -tc -tx1的C语言程序实现myod-优化版
  7. CentOS 7 安装SVN服务端
  8. iOS_TableView的相关操作
  9. 隐藏文字的另一种方法
  10. 【原】unity3D ios 退出保存数据(2)