LXC、Docker 和 iptables 在同一主机上,从容器到容器的端口转发超时

LXC、Docker 和 iptables 在同一主机上,从容器到容器的端口转发超时

谢谢阅读!我有一个公开的服务器(debian 10),用作主机,运行LXC(不是 LXD)和Docker.这两个容器服务以大部分原始配置运行(LXC 使用 lxc-net 及其lxcbr0网桥,Docker 使用其docker0网桥),所有容器都经过 NAT。

几乎一切都OK(容器中的互联网访问、端口转发等),除外我无法使用主机公共IP(以及端口转发)从内部自行访问容器当docker启动时(和iptables: true)。

示例:我想访问 中托管的网站LXC container web,其 IP 指向物理主机。
从外部(例如我的计算机):有用
来自主机本身:有用
来自另一个 LXC 容器:有用
从 Docker 容器:有用
来自web container itself暂停

LXC 容器使用简单的自制 .NET 进行联网iptables rules

iptables -t nat -A PREROUTING -d ${MY_PUBLIC_IP}/32 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.0.0.10:443
iptables -t nat -A OUTPUT -o lo -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.0.0.10:443
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE # added by LXC

几个星期以来,我拼命地试图解决这个问题,但没有成功。我尝试调整这些规则,添加一些伪装规则,记录规则,但我自己可能有一个我无法看到的误解。
使用 docker 运行iptables: false或停止 docker 可以修复该问题。
一种解决方法可能是调整我的 /etc/hosts 以使用容器中的环回接口,但我宁愿避免,因为我有太多不同的域。

我的猜测是,也许 DOCKER-USER 规则阻止了我的请求,但我无法找到解决方法。

有比我更有经验的人告诉我我做错了什么吗?

答案1

你需要有一个发夹配置使其正常工作。

下面还解释了它可以为同一 LAN 中的其他 LXC 容器工作,而它可能不应该工作,以及所有精彩的内容的奇怪世界桥接网络过滤器


你会受到两种不同的影响,一种非常不明显,并且改变了理解这一切的方式。

  • Docker导致内核模块br_netfilter被加载。正如我在回答中解释的那样iptable 如何与 Linux 桥接器一起工作?, 这导致iptables规则影响桥接的帧。

    通常应该从路由路径调用此规则:

    iptables -t nat -A PREROUTING -d ${MY_PUBLIC_IP}/32 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.0.0.10:443
    

    并将目的地变成 10.0.0.10。这也意味着:

    iptables -t nat -A POSTROUTING -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE # added by LXC
    

    现在不会触发。

    因此,如果客户端容器是 10.0.0.11,这将导致网络集装箱应答直接地通过桥接路径到达 10.0.0.11。 10.0.0.11 会收到来自未知 IP 地址 10.0.0.10(而不是 MY_PUBLIC_IP)的连接,因此会发回 TCP RST。这意味着如果没有运行 Docker(即使根本不参与连接),LXC -> LXC 情况也不会起作用。

    br_netfilter加载的桥接的框架是连线和网络地址转换。因此,10.0.0.10 在回复 10.0.0.11 时会在桥接路径中被拦截,并被脱 DNA 回到源 MY_PUBLIC_IP。 10.0.0.11 看到回复仍然来自 MY_PUBLIC_IP:发生了透明发夹。

    虽然这很好,但这是一个奇怪的行为你必须牢记在心,不要想当然。正常行为是不更改桥接数据包。同样,正常的行为是iptables' FORWARD 链不会过滤桥接容器,但它们现在已被过滤。

    有关这方面的更多信息,请检查此示意图。底部蓝色(网桥级别)背景中的绿色(网络级别)框由 启用br_netfilter。您还可以检查图 3c基于 Linux 的桥上的 ebtables/iptables 交互

  • 对于来自以下的连接网络br_netfilter有时(即使没有加载模块)容器与自身之间会存在通过线路从 10.0.0.10 到 10.0.0.10 的连接,该连接无法工作:默认情况下,网络堆栈会丢弃使用其自己的源地址接收的远程数据包。但还有一个看起来像错误的问题。见下文。


因此,要对当前配置应用创可贴,必须将源 IP 地址更改为任何路由 IP 地址。它可以是任何东西,甚至是 8.8.8.8,但最好由自己控制:要么 MY_PUBLIC_IP,要么如果不实用,分配的地址lxcbr0(我假设这里是 10.0.0.1)。这意味着 Web 服务不会在其日志中看到自己的 IP 地址。这是完全不可避免的。

但是,由于我无法解释的原因,经过反复试验(在 netfilter 中跟踪数据包),一个必须在网桥的自身接口上启用混杂模式lxcbr0,否则数据包在 ip nat/PREROUTING 和网桥过滤器/INPUT 之间消失(按此顺序,再次检查图3c),因此永远不会到达 ip nat/POSTROUTING。似乎也第 8 章中描述的行为没有按预期发生。

ip link set lxcbr0 promisc on

然后添加:

iptables -t nat -A POSTROUTING -s 10.0.0.10 -p tcp --dport 443 -j SNAT --to-source $MY_PUBLIC_IP

或者:

iptables -t nat -A POSTROUTING -s 10.0.0.10 -p tcp --dport 443 -j MASQUERADE

或者,只需匹配 DNATed 流(它也可以使用 SNAT 目标):

iptables -t nat -A POSTROUTING -s 10.0.0.10 -m conntrack --ctstate DNAT -j MASQUERADE

较新的内核(我相信 >= 5.3)允许有选择地启用或禁用每个网络命名空间甚至每个桥(例如:)ip link set lxcbr0 type bridge nf_call_iptables 1的激活net.bridge.bridge-nf-call-iptables。但不知道 Docker 如何触发这个(通常是因为-m physdev iptables匹配,但可能不是唯一的情况),很难知道在哪里禁用此功能,同时保持 Docker 照常工作。

相关内容