Home
avatar

whohh

Docker 容器无法访问宿主机端口排查:1Panel 防火墙导致 Docker Bridge 网络异常

最近在折腾服务器的时候遇到了一个比较诡异的问题。

我的 Docker 容器一直运行正常:

  • 可以访问公网
  • 可以正常解析 DNS
  • 可以 ping 通宿主机
  • Docker bridge 网络也没有异常

但是唯独一个问题:

容器无法访问宿主机上的任何端口。

比如宿主机:

192.168.1.14

上面运行了一个服务:

192.168.1.14:8096

宿主机自己访问:

curl -v http://192.168.1.14:8096/

正常返回:

HTTP/1.1 302 Found
Location: web/index.html

说明服务本身没有问题。

但是进入 Docker 容器:

wget -S --spider http://192.168.1.14:8096/

直接卡住:

Connecting to 192.168.1.14:8096

最后 timeout。


第一反应:是不是 Docker 网络坏了?

因为 Docker 容器访问宿主机失败,很多时候第一反应都会怀疑:

  • docker0 网桥
  • iptables NAT
  • 路由
  • FORWARD 链

于是先检查 Docker NAT。

iptables -t nat -L POSTROUTING -n -v

可以看到:

MASQUERADE  172.17.0.0/16
MASQUERADE  172.19.0.0/16

Docker 默认 NAT 规则正常。

继续看 FORWARD:

iptables -L FORWARD -n -v

发现:

DOCKER-USER
DOCKER-FORWARD

并且 docker0 有 ACCEPT:

ACCEPT all -- docker0 *

所以 Docker 本身应该没有问题。


tcpdump 给出了关键线索

网络问题最直接的方法还是抓包。

执行:

tcpdump -ni any host 192.168.1.14 and port 8096

然后在容器里面访问:

wget -S --spider http://192.168.1.14:8096/

抓到了:

172.17.0.4.59974 > 192.168.1.14.8096: Flags [S]

说明:

Docker 容器发出的 SYN 已经到达宿主机。

但是后面不断重复:

Flags [S]
Flags [S]
Flags [S]

却没有看到:

192.168.1.14.8096 > 172.17.0.4: Flags [S.]

也就是:

宿主机收到了请求,但是没有回复。

问题基本锁定到了宿主机防火墙。


检查 iptables INPUT 链

查看:

iptables -L INPUT -n -v

发现:

Chain INPUT (policy DROP)

1PANEL_BASIC_BEFORE
1PANEL_BASIC
1PANEL_BASIC_AFTER
ufw-before-input
...

这里出现了一个关键点。

我的机器同时安装了:

  • 1Panel 防火墙
  • UFW

原本以为 UFW 会处理规则。

但是实际上:

INPUT
 |
 +-- 1Panel 防火墙
 |
 +-- UFW

也就是说:

流量先经过 1Panel。

如果 1Panel 丢掉了,根本轮不到 UFW。


真正的罪魁祸首:1Panel BASIC_AFTER

继续查看:

iptables -L 1PANEL_BASIC_AFTER -n -v

结果:

Chain 1PANEL_BASIC_AFTER

DROP tcp
DROP udp

看到这里基本确定了。

1Panel 在最后阶段做了兜底:

没有提前允许的 TCP/UDP
        |
        v
       DROP

而 Docker 容器:

172.17.0.4

访问:

192.168.1.14:8096

属于进入宿主机 INPUT。

所以流程实际上是:

Docker Container

172.17.0.4

       |
       | TCP SYN
       v

Host INPUT

       |
       v

1PANEL_BASIC_AFTER

       |
       v

DROP

于是表现出来就是:

  • ping 没问题
  • TCP 全部 timeout

临时验证

为了确认判断,我直接插入两条规则:

iptables -I INPUT 1 -s 172.17.0.0/16 -j ACCEPT

iptables -I INPUT 1 -s 172.19.0.0/16 -j ACCEPT

然后重新测试:

wget -S --spider http://192.168.1.14:8096/

马上恢复:

HTTP/1.1 302 Found
Location: web/index.html

问题解决。


后续处理

临时规则肯定不是最终方案。

因为:

iptables -I INPUT

重启以后可能消失。

正确做法应该是在 1Panel 防火墙里面允许 Docker 网段。

例如:

172.17.0.0/16
172.18.0.0/16
172.19.0.0/16
172.20.0.0/16
172.21.0.0/16

或者把规则放到 1Panel 的前置链:

iptables -I 1PANEL_BASIC_BEFORE 1 \
-s 172.17.0.0/16 \
-j ACCEPT

原因也很简单:

后面有:

1PANEL_BASIC_AFTER
DROP

必须提前放行。


总结

这次问题最后定位下来,其实不是 Docker 的问题。

也不是 UFW。

真正原因是:

1Panel 防火墙规则优先级高于 UFW,并且默认 DROP 了 Docker bridge 网段访问宿主机的 TCP 流量。

以后遇到:

Docker 容器能联网,但是访问不了宿主机端口

不要第一时间怀疑 Docker。

排查顺序:

  1. 看服务是否正常监听
curl host:port
  1. 看 Docker 网络
iptables -t nat -L POSTROUTING -n -v
  1. 看 FORWARD
iptables -L FORWARD -n -v
  1. 抓包确认 SYN 有没有回来
tcpdump -ni any port xxxx
  1. 最后检查 INPUT 链

尤其注意:

  • 1Panel
  • 宝塔
  • UFW
  • firewalld
  • nftables

多个防火墙同时存在的时候,规则顺序比规则内容更加重要。

这次也算踩了一个坑:

Docker 容器访问宿主机,很多人会关注 FORWARD,但实际上访问宿主机服务最终走的是 INPUT 链。

记录一下,希望以后遇到类似问题的人少走点弯路。

Docker Docker Bridge 1Panel iptables UFW Linux 网络排障 防火墙