Docker 端口映射做了,局域网却连不上,问题出在监听地址

举报
phantomxjc 发表于 2026/10/13 08:45:38 2026/10/13
【摘要】 一次容器跑起来了、-p 也映射了、本机 curl 能通,但局域网别的机器始终 Connection refused 的排查记录。

起因

周末在 NAS 上起了个新的 Web 服务,用容器跑的:

docker run -d --name dashy -p 8080:8000 my-dash-app
docker ps
docker logs dashy --tail 20

容器状态 Up,日志里干干净净没报错,宿主机上 curl http://127.0.0.1:8080 也能正常返回页面。但我转到书房的笔记本,访问 http://192.168.1.20:8080 就是 Connection refused,telnet 端口秒拒。

这种症状挺典型:本机通、别机拒,说明包根本没走到应用层,先别急着怪网络。

看监听地址

第一步永远是确认这个端口到底监听在哪个地址上:

ss -lntp | grep 8080

输出是这样的:

LISTEN 0  4096  0.0.0.0:8080  0.0.0.0:*  users:(("docker-proxy",pid=1234,fd=4))

宿主机这一层看着没问题,0.0.0.0:8080 已经在收了。但要注意,这里 LISTEN 的是 docker-proxy,不是真正的服务进程。也就是说,-p 8080:8000 只是把宿主机 8080 转发到容器里的 8000,宿主机这侧的监听没问题,不代表容器里面也没问题。

进容器再看一次:

docker exec -it dashy sh -c 'netstat -lntp || ss -lntp'

结果:

tcp  0  0  127.0.0.1:8000  0.0.0.0:*  LISTEN  1/python

根因找到了。应用在容器里只绑了 127.0.0.1,而 docker-proxy 转发过来的目标地址是容器 eth0 的那张网卡 IP(一般是 172.17.x.x),不是 loopback。目标地址不匹配,内核直接回 RST,客户端看到的就是 refused。

改成绑 0.0.0.0 就好了。不同服务改的地方不一样:

# gunicorn / uvicorn
gunicorn -b 0.0.0.0:8000 app:app
uvicorn --host 0.0.0.0 --port 8000 app:app

# Flask 开发服务器
app.run(host="0.0.0.0", port=8000)

# nginx
server { listen 0.0.0.0:8000; ... }

顺带一提,Redis 的 bind 127.0.0.1、MySQL 的 bind-address = 127.0.0.1 都是同一类东西。默认值往往是安全的那一档,方便本地调试,代价就是跨机访问一律连不上。

监听没问题的话,看包到没到

如果 ss 显示已经是 0.0.0.0:8000 了还不通,就该确认数据包到底有没有落到网卡上。在服务端抓:

sudo tcpdump -ni any port 8080

然后从另一台机器 curl 一下,看终端:

  • 一条 SYN 都没有 —— 包压根没到,去查路由、交换、防火墙、以及云厂商的安全组;
  • 有 SYN 但没有 SYN,ACK —— 到了网卡但没人应答,回头再查一遍应用配置和 backlog;
  • 握手正常但立刻 RST —— 应用层 accept 之后主动断了,看应用日志。

我第一次用云服务器部署的时候,iptables 全都放通了还是不通,折腾了半小时才想起控制台里还有一层安全组。现在这个习惯固定下来了:只要换机器部署,先去看控制台的安全组规则。

防火墙别漏掉

本机端口放通是第二道关卡,两台发行版两套命令:

# Debian / Ubuntu
sudo ufw status verbose
sudo ufw allow 8080/tcp

# CentOS / RHEL
sudo firewall-cmd --list-all
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

这里有个容易忽略的点:Docker 启动容器时会自己往 iptables 写规则(走的是 FORWARD 链),而 ufw 主要管 INPUT。-p 映射出去的端口有可能绕过 ufw 的规则直接对外可达 —— 你以为被防火墙挡着,其实早就在公网裸奔了。想让两者一致,要么别混用,要么补一条 ufw route allow proto tcp from any to any port 8080,具体得看机器上的实际规则(sudo iptables -S 看全貌最靠谱)。

关于 IPv6 的一个误会

ss 里有时候会看到 *:8080 或者 [::]:8080,不少人以为只监听了 IPv6。实际上 Linux 上绑定 :: 默认是双栈的,IPv4 请求会以 v4-mapped 的形式照样进来,nginx 里写 listen [::]:80; 也是能接 IPv4 的。真正要留意的是反过来:应用显式设了 IPV6_V6ONLY,那才是只认 IPv6。

最后留个清单

这次之后我把排查顺序写在便签上了,下次遇到直接顺着走:

  1. 目标机 ss -lntp | grep 端口,看是 0.0.0.0 还是 127.0.0.1;
  2. 如果是容器,一定要 docker exec 进去再看一遍容器内那个端口;
  3. 从别的机器测,本机 curl 通过不能说明任何问题;
  4. tcpdump 确认 SYN 到底有没有到网卡;
  5. 查 iptables/ufw/firewalld,云主机额外查控制台安全组;
  6. 握手能建立但服务不响应,再看 backlog 和 fd:ss -tan state syn-recv sport = :8080、ulimit -n。

六个步骤走完,这类"端口在却连不上"基本都能定位。我这次卡了二十分钟,全浪费在第 2 步 —— 一直在宿主机上看那个 docker-proxy,忘了真正的应用在另一层。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。