容器重启后,Docker Swarm 路由网格不会转发 UDP 流量

容器重启后,Docker Swarm 路由网格不会转发 UDP 流量

我已经设置了一个包含几个服务的 docker 堆栈。其中一项服务正在发布一个 UDP 端口,用于监听流量(它恰好是一个 Wireguard 端点,但我认为在本例中这与此无关)。

到目前为止,一切运行正常。我可以在节点的已发布端口上发送 UDP 流量,并且可以看到它通过 docker 网桥 (veeth*、docker_gwbridge) 转发到虚拟接口,并使用 tcpdump 到达容器内。

但是,当我重新启动服务容器时,新的服务容器不再正确转发 UDP 流量。在某个时刻——目前我仍不清楚触发原因究竟是什么,但似乎只是时间问题——流量突然又开始流动。

我对所有流向主机映射端口的接口上的所有流量进行了 tcpdump,我可以看到流量从主机接口进入,离开了 docker_gwbridge 和 veeth* 接口,但是容器内映射端口的 tcpdump 却保持静默。

我比较了重启前、重启后以及容器再次恢复时离开主机的流量,似乎没有任何变化(即使用相同的 IP 地址和端口)。

我发现了一个非常类似,但是是旧帖并且它链接到的 Docker 问题似乎在一年前就已解决。尝试使用帖子中描述的解决方法(通过在conntrack -F主机上运行来刷新 conntrack 表)也没有帮助。

我怀疑 conntrack 或 docker VIP 覆盖网络在某种程度上与此问题有关,但我对这两个系统的了解太有限,甚至不知道从哪里开始。我的搜索也没有找到任何对我有帮助的东西。

进度更新1

总结:流量在 LVS 网络命名空间中被丢弃。但包括 LVS 在内的所有内容似乎都设置正确。我甚至可以看到通过 iptables 应用了正确的 LVS 标记,但流量似乎无法通过 LVS 路由/伪装

在过去的几天里,我一直在继续解决这个问题,并想提供一个进度更新,因为我认为这可能对遇到类似问题的其他人有价值,并可能帮助其他人把最后剩下的拼图碎片放到位。

我还远不是这些主题的专家,所以如果我误用任何术语或歪曲任何细节,请原谅(并纠正)我。

一些基础知识:Docker (Swarm) 网络大量使用 Linux 网络命名空间、网桥和虚拟 IP 地址。通过此设置跟踪任何特定流或一系列数据包的流动可能具有挑战性且令人困惑。我之前没有使用过 Docker 网络、Linux 网络命名空间或虚拟 IP 地址,也不知道它们如何工作或如何与它们交互,但我发现以下三篇文章非常有用,如果您不熟悉这些主题,我建议您至少浏览一下它们:

一些关键要点:

  • Linux 网络命名空间是一种网络容器化/隔离的形式。每个网络命名空间在 Linux 内核中都有其完整的独立网络堆栈。
  • ip netns是与 Linux 网络命名空间交互的规范方式,ip netns exec可用于在特定网络命名空间的上下文中运行任何命令。
  • Docker 使用网络命名空间,但没有将它们链接到/var/run/netns,这使得它们对于规范的 Linux 工具来说是不可见的。
  • Docker 网络命名空间已链接。您可以根据需要/var/run/docker/netns将整个文件夹绑定到/var/run/netns网络命名空间中,也可以单独链接网络命名空间。/var/run/netns
  • 每个网络命名空间都/var/run/docker/netns属于一个 docker 容器或网络。哪一个属于哪个可能有点困难。
  • 容器似乎通过属性引用其网络命名空间SandboxKey。运行docker inspect <container-id> -f '{{.NetworkSettings.SandboxKey}}'以获取它。您也可以使用它docker exec在容器内运行任意命令,从而有效地在容器的网络命名空间内运行。
  • 但对于网络来说,这有点困难。该docker exec解决方案不适用于网络,并且docker inspect <network-id>不直接引用 下的任何项目/var/run/docker/netns。但是,所有相关网络都相对容易识别。它们两个曾经相关的被链接为ingress_sboxlb_<first-9-chars-of-swarm-network-id>
  • 将这两个网络命名空间链接到一起/var/run/netns允许我使用其中运行命令ip netns exec
  • 在这两个网络命名空间内、容器以及主机上运行 tcpdump 让我能够看到完整的端到端流量流动。
  • 使用ip netns exec ingress_sbox tcpdump -i any udp我还能够看到 ingress_sbox 确实是流量不再被路由到容器中并且在容器重启后被丢弃的地方。
  • 容器在重新启动时会获得一个新的网状网络 IP 地址,并且ip netns exec ingress_sbox conntrack -Ln我确实显示了 UDP 流的过时的 conntrack 条目。
  • ip netns exec ingress_sbox conntrack -F但是使用刷新 conntrack没有帮助。我没有看到任何新的 conntrack 条目被创建。
  • 我看到流量从 10.0.0.2(容器的默认网关和服务 VIP)进入,然后(伪装)流向 10.0.0.100(容器中 eth0 的 IP 地址)。我仍然不清楚路由/伪装发生在哪里。
  • ip netns exec ingress_sbox iptables -L -t nat表明这是一条与10.0.0.0/24目标网络和匹配的 SNAT 规则-m ipvs --ipvs
  • 事实证明,IPVS 是 IP 虚拟服务器——上面提到的虚拟 IP 地址系统。运行ip netns exec ingress_sbox ipvsadm -L显示基于防火墙标记 (FWM) 11962 的循环 (rr) 规则伪装 (Masq) 流量到 10.0.0.100 IP 地址,InActConn 计数为 1(对于我的 UDP“连接”)。
  • 事实上,如果我使用 mangle 表查看,ip netns exec ingress_sbox iptables -n -t mangle -L我可以看到一条规则将标记(十六进制)应用于映射 UDP 端口上传入的所有流量。
  • 当我重新启动容器时,iptables 中的标记值会发生变化,并且 ipvs 规则也会将现在更改的标记映射到容器 IP 地址。
  • 我甚至可以看到使用 进行标记的 iptables 规则的数据包和字节计数器上升ip netns exec ingress_sbox iptables -Lnvt mangle。虽然ip netns exec ipvsadm -L显示了正确的标记规则和伪装 IP 地址,但 InActConn 列在该条目中保持为 0 - 直到容器恢复。

进度更新 2

我发现ip netns exec ingress_sbox ipvsadm -Lc列出了 LVS 的连接表,其中确实包含一个 UDP“连接”条目,即使接收该连接的 LVS 真实服务器从池中移除后,该条目仍然保留。这里有三点需要说明

  1. 这可能是 LVS 的一个错误。这在一定程度上取决于这里的语义以及 LVS 对从池中移除的真实服务器做出的保证(即 LVS 是否保证真实服务器在从池中移除后仍将继续接收活动连接的流量),但有一种说法是,如果真实服务器从池中移除,其连接应被清除或至少标记为“过时”或“可采用”,尤其对于无状态 UDP,甚至在超时之前,也会“移交给”列表中的任何其他真实服务器。如果您希望 LVS 继续为其提供活动连接,则可以将真实服务器权重降低为 0。

  2. 我已经在 Google 上搜索了很多次,但恐怕找不到更新、更改、删​​除或刷新 LVS 连接表中条目的方法。似乎有一些20 年前的帖子在讨论此类功能的 LVS 用户邮件中,似乎没有人实现过。至少据我所知没有。真的希望我错了,因为我只想清除池中已删除的真实服务器的所有 UDP 条目。我已将此部分陈述为这里有单独的问题

  3. 超时。这是可以使用 更改的一件事ip netns exec ingress_sbox ipvsadm --set <tcp> <tcpfin> <udp>。设置该<udp>值会影响 UDP 连接超时,较低的值可以显著减少容器更新/重启时的服务中断(默认值为 300 秒)。起初,我以为我可以将此值设置为 0 或某个任意低值来刷新所有 UDP 连接,但似乎只有当连接仍被视为活动状态(即进出流量)时,该值才会更新,而容器停止后情况并非如此。可能可以将该值设置为更低的值容器停止了。但需要确保这不会导致其他服务中断。

非常感谢任何额外的建议或推荐。

相关内容