kube-proxy的精妙之处:调试间歇性连接重置

作者: 永贵, 谷歌

我最近遇到了一个导致间歇性连接重置的错误。后 一些挖掘,我发现它是由几种不同的细微组合造成的 网络子系统。它帮助我更好地了解了Kubernetes网络,而且我 认为值得与对同一内容感兴趣的更多受众分享 topic.

症状

我们收到了一份用户报告,声称他们在使用 类型为ClusterIP的Kubernetes服务可将大型文件提供给在 同一集群。群集的初始调试未产生任何结果 有趣的是:网络连接很好,下载文件没有成功 任何问题。但是,当我们在许多客户端上并行运行工作负载时, 我们能够重现该问题。事实上, 在不使用VM的情况下运行工作负载时无法重现该问题 Kubernetes。这个问题很容易重现 一个简单的 app,显然有一些要 与Kubernetes联网一起使用,但是又如何呢?

Kubernetes网络基础知识

在深入探讨此问题之前,让我们先讨论一下 Kubernetes网络,因为Kubernetes处理来自Pod的网络流量 根据不同的目的地有很大不同。

点对点

在Kubernetes中,每个Pod都有自己的IP地址。好处是 在Pod中运行的应用程序可以使用其规范端口,而不是 重新映射到其他随机端口。每个Pod之间都有L3连接 其他。他们可以相互ping通,并相互发送TCP或UDP数据包。 CNI 是解决的标准 在不同主机上运行的容器会出现此问题。有很多 支持CNI的不同插件。

从外部到外部

对于从Pod到外部地址的流量,Kubernetes只需使用 SNAT。它能做什么 是用主机的IP:端口替换Pod的内部源IP:端口。什么时候 返回数据包返回到主机,它将Pod的IP:端口重写为 目的地,并将其发送回原始吊舱。整个过程是透明的 到原来完全不知道地址翻译的Pod。

到服务

豆荚是凡人。人们很可能希望获得可靠的服务。不然的话 几乎没有用。所以Kubernetes有一个叫做“服务”的概念 只是在吊舱前面的一个L4负载平衡器。有几种不同类型的 服务。最基本的类型称为ClusterIP。对于此类服务,它 有一个唯一的VIP地址,该地址只能在集群内部路由。

Kubernetes中实现此功能的组件称为kube-proxy。 它位于每个节点上,并对复杂的iptables规则进行编程以执行各种操作 Pod和服务之间的过滤和NAT的过程。如果您转到Kubernetes节点 and type iptables-save, you’ll see the rules that are inserted by Kubernetes or other programs. The most important chains are KUBE-SERVICES, KUBE-SVC-* and KUBE-SEP-*.

  • KUBE-SERVICES 是服务数据包的入口点。它的作用是 匹配目标IP:端口并将数据包分派到相应的 KUBE-SVC-* 链。
  • KUBE-SVC-* 链充当负载平衡器,并将数据包分发到 KUBE-SEP-* chain equally. Every KUBE-SVC-* has the same number of KUBE-SEP-* 链作为其后面的端点数。
  • KUBE-SEP-* 链表示服务端点。它只是做DNAT, 用Pod的端点IP:Port替换服务IP:port。

对于DNAT,conntrack启动并使用状态跟踪连接状态 机。需要状态,因为它需要记住目的地 地址更改为,并在返回数据包返回时将其更改回。 iptables也可以依靠conntrack状态(ctstate)来决定命运 一个数据包。这4个conntrack状态特别重要:

  • :conntrack对此数据包一无所知,这在SYN时发生 packet is received.
  • 已建立:conntrack知道数据包属于已建立的连接, 握手完成后会发生这种情况。
  • 有关:数据包不属于任何连接,但已关联 到另一个连接,这对于FTP等协议特别有用。
  • 无效:数据包出了点问题,并且conntrack不知道如何 处理它。该状态在Kubernetes问题中起着中心作用。

这是Pod和服务之间TCP连接如何工作的示意图。的 事件的顺序是:

  • 客户端Pod从左侧发送数据包到 服务:192.168.0.2:80
  • 数据包正在通过客户端中的iptables规则 节点并将目标更改为Pod IP 10.0.1.2:80
  • 服务器Pod处理该数据包,并发送回目的地为10.0.0.2的数据包
  • 数据包返回到客户端节点,conntrack识别该数据包并重写源 地址回192.169.0.2:80
  • 客户端容器收到响应数据包
良好的数据包流

良好的数据包流

是什么导致连接重置?

有足够的背景信息,所以真正出了问题并导致了意外的情况 connection reset?

如下图所示,问题出在数据包3上。 识别返回的数据包,并将其标记为 无效。最普遍的 原因包括:conntrack无法跟踪连接,因为连接已断开 容量,数据包本身不在TCP窗口等范围内。对于这些数据包 被标记为 无效 由conntrack声明,我们没有 iptables规则将其删除,因此它将与源IP一起转发到客户端pod 地址未重写(如数据包4所示)!客户窗格无法识别 数据包,因为它具有不同的源IP,即Pod IP,而不是服务IP。如 结果,客户吊舱说:“等等,我不记得与 这个IP曾经存在过,为什么这个伙计会不断向我发送这个数据包?” 客户端要做的只是将RST数据包发送到服务器Pod IP, 是数据包5。不幸的是,这是一个完全合法的“点对点”数据包, 交付给服务器吊舱。服务器窗格不知道所有地址转换 发生在客户端。从它的角度看,数据包5是完全合法的 数据包,例如数据包2和3。所有服务器Pod都知道,“嗯,客户端Pod没有 想和我说话,所以我们关闭连接!”繁荣!当然,为了 为了使所有这些事情发生,RST数据包也必须合法,并带有正确的TCP 序列号等。但是一旦发生,双方都同意关闭 connection.

连接重置数据包流

连接重置数据包流

如何解决?

一旦我们了解了根本原因,修复起来就不难了。至少有2个 ways to address it.

  • 使conntrack对数据包更加宽松,并且不要将数据包标记为 无效. In Linux, you can do this by echo 1 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_be_liberal.
  • 专门添加iptables规则以删除标记为的数据包 无效,因此它不会到达客户吊舱并造成伤害。

修正案已草拟(//github.com/kubernetes/kubernetes/pull/74840), 但 不幸的是,它没有赶上v1.14发布窗口。但是,对于用户 受此错误影响的方法,有一种方法可以通过应用 集群中的以下规则。

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: startup-script
  labels:
    应用程式: startup-script
spec:
  template:
    metadata:
      labels:
        应用程式: startup-script
    spec:
      hostPID: true
      containers:
      - name: startup-script
        image: gcr.io/google-containers/startup-script:v1
        imagePullPolicy: IfNotPresent
        securityContext:
          privileged: true
        env:
        - name: STARTUP_SCRIPT
          value: |
            #! /bin/bash
            echo 1 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_be_liberal
            echo done            

概要

显然,该错误几乎一直存在。我很惊讶 直到最近才被发现。我相信原因可能是:(1) 在拥挤的伺服器中发生更多的情况,伺服器会提供较大的有效负载,这可能不是 常见用例; (2)应用层处理重试容忍 这种重置。无论如何,无论Kubernetes的发展速度如何, 这仍然是一个年轻的项目。没有其他秘密,除了认真听 客户的反馈,而不是理所当然,而是要深入研究,我们可以 使其成为运行应用程序的最佳平台。

特别感谢 博威 为双方的咨询 调试过程和博客,以 car 对于 报告问题并提供复制品。