继去年 HomeLab PRD 节点全面使用 lxc 容器运行应用后,最近兴趣使然,又开始折腾主机的网络。主要折腾的内容是 vlan、SR-IOV 和万兆,等整理完再把折腾笔记发出来,本文主要解决折腾过程中遇到的一个问题。
为方便管理容器,我为每个 lxc 里的容器(docker in lxc)创建一个 Network,分配独立的网段。在主路由中将该子网的请求路由到该 lxc 上,就可以实现通过子网 IP 直接访问容器。
配置好后发现,在 lxc 之外根本 ping 不通容器,请求始终到不了容器。
现象
相同的配置方式在群晖的 Docker 是可以如期使用的,而且在 lxc 节点又能正常 Ping 通容器。
使用 tcpdump
抓包 icmp,观察网络情况。 下面的 10.0.3.11
是容器 IP,10.0.3.1
是 lxc 的 IP(Docker Network 的网关 IP), 10.0.1.26
是节点外电脑的 IP:
tcpdump 抓包1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $ tcpdump -i eth0 host 10.0.3.11 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 23:25:25.432364 IP 10.0.1.26 > 10.0.3.11: ICMP echo request, id 3197, seq 38, length 64 23:25:26.434915 IP 10.0.1.26 > 10.0.3.11: ICMP echo request, id 3197, seq 39, length 64 23:25:27.440254 IP 10.0.1.26 > 10.0.3.11: ICMP echo request, id 3197, seq 40, length 64 23:25:28.441107 IP 10.0.1.26 > 10.0.3.11: ICMP echo request, id 3197, seq 41, length 64
$ tcpdump -i vethef07fa1 host 10.0.3.11 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on vethef07fa1, link-type EN10MB (Ethernet), snapshot length 262144 bytes 23:40:26.510201 IP 10.0.3.1 > 10.0.3.11: ICMP echo request, id 2245, seq 58, length 64 23:40:26.510216 IP 10.0.3.11 > 10.0.3.1: ICMP echo reply, id 2245, seq 58, length 64 23:40:27.534200 IP 10.0.3.1 > 10.0.3.11: ICMP echo request, id 2245, seq 59, length 64 23:40:27.534211 IP 10.0.3.11 > 10.0.3.1: ICMP echo reply, id 2245, seq 59, length 64
|
可以发现:
- lxc 的接口能接受到 icmp 的 request 包,无响应回包;
- lxc 内 docker 容器的网络接口无数据包;
- 只有从 lxc 容器外才不能访问 lxc 里的 docker 容器。
原因
从上面的测试可以基本确定,问题出在 lxc 转发数据上。我使用 lxc 的容器是基于 Ubuntu 23.04 Template 创建而来,怀疑是不是默认没打开 ipv4 的转发功能:
ipv4 转发1 2 3 4
| $ sysctl net.ipv4.ip_forward 1 $ cat /proc/sys/net/ipv4/ip_forward 1
|
参数都表示打开的,在对群晖和 lxc 的路由和防火墙规则的详细对比,发现问题在防火墙规则上。
lxc iptables 规则1 2 3 4 5 6
| $ iptables --list-rules -P INPUT ACCEPT -P FORWARD DROP -P OUTPUT ACCEPT -N DOCKER
|
可以发现,这里有一个 -P FORWARD DROP
的默认规则,把转发流量全部丢弃掉了。
解决
找到了问题的根源,解决也就很简单了,只需要用 iptables
命令设置开启就可以。
修改规则1
| $ /sbin/iptables -P FORWARD ACCEPT
|
这样的修改是临时性的,要想将这个规则持久化,可以使用 iptables-persistent
或者自己写启动脚本。
iptables-persistent
的启动顺序比 docker.service
早,会导致 iptables
规则被 docker 默认的覆盖,需要手动调整顺序。
我这里使用的启动脚本方式解决。首先创建一个 shell 脚本(编辑完记得用 chmod
授权可执行权限):
/etc/iptables/accept-forward-iptables.sh1 2
| #!/bin/sh /sbin/iptables -P FORWARD ACCEPT
|
/etc/iptables/
目录可能不存在,报错的话先创建好。
然后创建一个 service
启动配置:
/etc/systemd/system/iptables-persistent.service1 2 3 4 5 6 7 8 9 10 11 12
| [Unit] Description=runs iptables restore on boot ConditionFileIsExecutable=/etc/iptables/accept-forward-iptables.sh After=network.target docker.service [Service] Type=forking ExecStart=/etc/iptables/accept-forward-iptables.sh start TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no [Install] WantedBy=multi-user.target
|
这里注意,After
配置里一定要有 docker.service
,不然规则会被覆盖。推测默认 DROP
的应该是 Docker 创建的规则,这个没有深究。
修改完成后,执行以下命令开机启动并生效:
开机启动并生效1 2 3
| systemctl daemon-reload systemctl enable iptables-persistent.service systemctl start iptables-persistent.service
|
配置完后,应该就可以在内网的其它机器成功 Ping 通 10.0.3.11
主机了。