# Linux Conntrack 的短板

原文：[When Linux conntrack is no longer your friend](https://www.tigera.io/blog/when-linux-conntrack-is-no-longer-your-friend/)

作者：[Alex Pollitt](https://www.linkedin.com/in/alexpollitt)

Linux 内核中，`conntrack` 的是网络栈的核心功能之一。它使得内核能够跟踪所有逻辑网络连接或者流量，因此能够鉴别构成流量的数据包，从而对每个数据流进行一致的处理。

Conntrack 是一个重要的内核功能，是一些关键用例的基础：

* NAT 能够根据 Conntrack 的信息，对构成数据流的所有数据包进行翻译。例如当 Pod 访问 Kubernetes 服务的时候，kube-proxy 的负载均衡功能会使用 NAT 把连接重定向给指定的后端 Pod。Conntrack 负责对指定连接进行记录，发送到 Service IP 的数据包会被发送给同一个后端 Pod，从后端 Pod 返回的数据包会反向送回源 Pod。
    
* Calico 这样的有状态防火墙，依赖 Conntrack 的信息来精确的将响应流量纳入白名单。用户如果编写了一个“允许 Pod 连接所有远端 IP”的策略，无需进行其它工作就能够允许所有的返回流量（如果没有这种功能，就必须加入一个不那么安全的规则：允许所有目标是该 Pod 的流量）。
    

另外 Conntrack 还能够提高性能（降低 CPU 和延迟），这是因为只有第一个数据包需要完成整个网络栈的处理，参见 [Comparing kube-proxy modes](https://www.tigera.io/blog/comparing-kube-proxy-modes-iptables-or-ipvs/) 一文，其中包含了这方面的例子。

然而 Conntrack 也有其限制。

## 那么它哪里不行了？

Conntrack 的存储表有一个可配置的最大容量，如果满了，连接通常会被拒绝和丢弃。在多数负载情况下，这个表的容量是足够的，不会出现这种问题。但是有些场景下，Conntrack 也会不够用：

* 最常见的情况是，如果你的服务器需要同时处理非常大数量的活动连接。例如你的 Conntrack 表设置为 128k 的容量，但是你的并发连接超过了 128k，这肯定会出问题。
    
* 另外一种情况没那么常见——每秒处理极大数量的连接。这些连接就算再短，Linux 还会在超时（通常是 120 秒）期限内对其进行跟踪。例如如果你的 Conntrack 容量设置为 128k，尝试每秒钟处理 1100 个连接，这就会超出 Conntrack 表的限制（128k/120秒 = 1092 连接/秒）。
    

有个别的负载类型就符合这种条件。另外如果在一个恶劣环境中，用大量的半开连接冲击服务器，就能造成拒绝服务攻击的效果。两种情况下，Conntrack 都会成为系统中的瓶颈。有些情况下，通过提高 Conntrack 数据表容量或者降低 Conntrack 的超时时间（如果调节失误，可能会造成更多痛苦）就能解决问题。别的场景中，可能需要跨过 Conntrack 来处理这种威胁。

## 一个真实的案例

我们合作的某大型 SaaS 供应商，他们有一组运行于物理机（不是虚拟化，也不是容器化）上的 Memcached 服务器，每台服务器每秒都要处理 50k 以上的短连接。这可不是标准的 Linux 配置能够承受的。

他们曾经使用提高 Conntrack 数据表容量和降低超时时间的方式进行调整，但是这种调整非常脆弱，内存占用增长极大（GB 级），超时时间过短让 Conntrack 的益处（降低 CPU 和数据包延迟）也大为减少。

因此他们转向了 Calico，Calico 的网络策略允许指定部分流量绕过 Conntrack。这一措施让他们得到了期待的性能，并且还得到了 Calico 带来的安全优势。

## 绕开 Conntrack 的妥协之处

* `Do-not-track` 策略通常是对称的。上面谈到的 SaaS 供应商案例，他们的工作负载是内部的，因此他们可以非常严格的在工作负载和 Memcached 之间进行白名单设置。
    
* `Do-not-track` 策略是不知道连接的方向的。所以只要 Memcached 服务器知道 Memcached 客户端的源端口，他都可以尝试连接。但是如果为 Memcached 客户端定义了正确的策略，那么还可以在客户端拒绝这些连接。
    
* `Do-not-track` 对每个数据包生效，而通常的网络策略只对数据流中的第一个数据包生效。这样会提高 CPU 的消耗。但是在短连接环境下，网络策略造成的消耗还是低于 `Conntrack` 过程的消耗的。例如 SaaS 供应商的例子中，每连接中的数据包都很少，所以使用策略处理每个数据包的过程中造成的多余开销也就可以接受了。
    

## 测试一下

我们测试了单一 Memcached 服务器 Pod 以及运行在远程节点上的多个客户端的场景，这种场景能方便的产生大量链接。Memcached Pod 所在的节点有 8 个 CPU 核心，Conntrack 表容量为 512k（主机的标准设置）。我们在几种方案中进行了比较：没有网络策略；Calico 通用网络策略；Calico `Do-not-track` 策略。

第一个测试中，我们限制每秒 4000 个连接，以此来方便的观察 CPU 方面的差异。无策略和正常策略的测试用例，其结果没有差异，而 `Do-not-track` 策略降低了 20%。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1746353993886/2f91953a-2ddc-4fb2-8ea0-b9ff56635cb0.png align="center")

第二个测试中，我们尽量的提高连接数量，这样就可以观察到 Memcached 能够处理的每秒最大连接数量了。如前所述，无策略和普通策略的情况里，受到 Conntrack 数据表容量的影响，只能达到每秒 4000 多连接的能力（512k / 120s = 4,369 connections/s）。而 `Do-not-track` 策略下，连接数达到了 60k 每秒，没有出现问题。我们相信，可以通过更多的客户端来产生更高的负载数量，但是这一数字已经证实了我们提到的观点了。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1746353988619/0936e43f-885b-4999-835e-eef83151d0dc.png align="center")

## 结论

Conntrack 是一个重要的内核功能。大多数场景下，它都能发挥很好的作用。然而有些小众场景中，Conntrack 的开销会大于其收益。在这种情况下，Calico 的网络策略可以通过选择性的绕过 Conntrack，并提高网络安全性。而对其他流量，Conntrack 还是你的好朋友。
