docker ps 显示正常却访问不了:端口与防火墙排查

docker ps 里容器显示 Up,甚至 PORTS 栏已经有 0.0.0.0:8080->80/tcp,接口仍然超时或拒绝连接,并不矛盾。Up 只能说明容器主进程尚未退出,不能证明应用已经启动完成、端口确实发布,更不能证明防火墙和云安全组允许外部流量进入。

定位时不必反复重启容器,可以沿请求路径做三次请求:先在容器内请求应用端口,再从宿主机请求发布端口,最后从另一台客户端请求服务器地址。容器内失败先查应用;容器内成功、宿主机失败先查端口发布与监听地址;宿主机成功、远程失败则重点查监听范围、防火墙、安全组和 Docker 转发规则。沿着请求路径由内到外测试,比只盯着 docker ps 的状态更容易定位问题。

# 1. 进入容器,请求应用实际监听端口
docker exec -it <容器名> sh
# 进入容器后执行
curl -v http://127.0.0.1:<容器端口>/<接口路径>

# 2. 在宿主机请求 Docker 发布后的端口
curl -v --connect-timeout 5 http://127.0.0.1:<宿主机端口>/<接口路径>

# 3. 从另一台客户端请求服务器的实际地址
curl -v --connect-timeout 5 http://<服务器IP>:<宿主机端口>/<接口路径>

下面的命令以 Linux 宿主机和常见 Docker Engine CLI 为例。镜像里如果没有 sh 或 curl,就改用镜像已有的 wget、busybox 或其他诊断工具,不要为了测试临时改生产镜像。

docker ps 显示 Up,究竟能证明什么?

docker ps 里的 STATUS 和 PORTS 要分开看。Up 对应容器主进程的生命周期;应用可能还在初始化,也可能应用子进程已经异常,但容器入口进程仍然活着。PORTS 才用于显示端口发布情况,其中方向是“宿主机地址:宿主机端口 → 容器端口/协议”,不能只看到 tcp 就认为外部一定可达。

docker ps
docker logs --tail 200 <容器名>
docker port <容器名>

docker port 适合核对最终生效的映射。如果 Ports 为空,先看容器启动时是否真的传入了 -p 或 -P。-p 会明确发布指定端口,-P 则发布镜像通过 EXPOSE 声明的端口;EXPOSE 本身只提供元数据,不会把端口直接开放到宿主机。

如果启动命令里明明有 -p,docker inspect 中却没有对应绑定,或者 docker ps 仍不显示映射,就不要反复用相同参数重建容器。此时还应检查容器使用的网络模式、Docker 守护进程的启动日志以及 Docker 与宿主内核的兼容性;旧资料中也记录过参数正确但端口发布异常的情况。

docker inspect --format '{{json .HostConfig.PortBindings}}' <容器名>
docker inspect --format '{{.HostConfig.NetworkMode}}' <容器名>

容器内服务是否真的在监听正确端口?

容器内测试可以直接区分“应用没有监听”与“请求已经到达宿主机,但没能进入容器”。先看日志,再看监听地址,最后直接请求应用端口:

docker logs --tail 200 <容器名>

docker exec -it <容器名> sh
ss -lntp
curl -v http://127.0.0.1:<容器端口>/<接口路径>

如果 ss 不可用,可以使用镜像已有的 netstat。应用刚启动时可能还在加载配置或连接数据库,日志会帮助判断是“尚未就绪”还是“已经报错退出”。即使根路径返回 404,只要收到了 HTTP 响应,也说明 TCP 连接和 Web 服务已经可达,不能把非 2xx 一律当成端口不通。

监听地址同样关键。应用监听 0.0.0.0:<端口> 或实际的容器接口地址,才有机会接收 Docker 转发进来的流量;如果只监听 127.0.0.1,请求就只限于容器内部,宿主机和外部客户端即使完成了端口映射也无法直接访问应用。如果应用绑定的是某个具体容器 IP,可以取得地址后再从容器内验证:

docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' <容器名>

一次回环地址请求失败不能直接证明应用完全没启动,它也可能只是绑定到了特定地址。应当结合监听列表、日志和实际容器地址一起判断。

Ports 为空、端口写反或被占用,应该怎么修?

端口映射要明确区分两侧。假设应用在容器内监听 80,希望从宿主机 8080 访问,应该使用:

docker run -d --name <容器名> \
  -p 8080:80 <镜像>

反过来,如果应用在容器内监听 8080,希望占用宿主机 80,才应该使用 -p 80:8080。Docker 不会根据应用配置自动推断两端,写反后通常只是访问了错误端口。

docker port <容器名>

-P 是大写形式,会把镜像声明过的端口发布到随机宿主机端口,最终结果仍应以 docker port 为准。如果镜像没有正确的 EXPOSE 元数据,-P 可能不会发布任何端口;即使镜像有 EXPOSE,也仍要核对映射到了哪个宿主机端口。

运行中的容器不能补上一条端口映射。若最初没有发布端口,通常需要用正确的 -p 重建容器。重建前应确认数据库文件、上传目录等数据是否已经通过卷或其他持久化方式保存。

宿主机端口被占用时,先查清楚占用者,不要直接终止未知进程:

sudo ss -tulpn | grep ':<宿主机端口>'

确认占用来源后,再决定释放端口还是更换宿主机端口,例如把 -p 8080:80 改为 -p 8081:80。

还有一种常见配置是只绑定宿主机回环地址:

docker run -d --name <容器名> \
  -p 127.0.0.1:8080:80 <镜像>

这种映射只允许宿主机本机访问,局域网和公网客户端无法通过服务器地址进入。如果确实需要远程访问,应根据暴露范围绑定服务器的实际地址或所有网卡,并同步收紧安全组和防火墙规则。绑定所有网卡只解决监听范围,不会自动替你提供访问控制。

宿主机能通、外网不通,防火墙还是 Docker 转发?

如果宿主机上的 curl http://127.0.0.1:<宿主机端口>/... 已经成功,而另一台客户端访问服务器 IP 失败,应用和基本端口发布链路通常已经成立。此时先检查 docker port 显示的宿主机绑定地址,再依次核对云安全组、宿主机防火墙和 Docker 的 NAT 规则。

云安全组放行的应当是“宿主机发布端口”,不是容器内部端口。例如映射为 8080:80 时,安全组需要考虑的是 8080,而不是 80。具体操作位置取决于云平台,没有通用的配置命令。

传统 iptables 环境中,Docker 可能先在 PREROUTING 阶段执行 DNAT,把流量改写到容器地址,然后再经过过滤链。因此,只检查普通 INPUT 规则,很容易误判真实流量路径。可以先查看现有规则:

sudo iptables-save | less
sudo iptables -t nat -S

这些命令只适用于仍使用相应 iptables 接口的环境。不同发行版可能使用 nftables 或其他防火墙实现,应先确认主机的实际管理方式,再查看对应的转发与过滤规则;不能因为某个命令没有输出,就直接认定 Docker 没有配置转发。

连接错误也能提供线索,但只能作为辅助判断:立即返回 Connection refused,通常表示请求到达了目标主机,但没有进程监听或被防火墙主动拒绝;长时间超时,更常见于安全组或防火墙静默丢包、上游路由不可达,也可能是请求走错了服务器地址。

不要把永久关闭防火墙当成通用修复。已有案例记录过停止防火墙后,Docker 端口映射反而出现异常的情况。这并不表示防火墙永远不能临时停用,而是说明“关掉就一定好”并不成立;真要做对照测试,也应限定在维护窗口,并恢复原配置、保留前后规则输出。

本机能通、局域网不通,localhost 到底指向哪台机器?

localhost 永远表示发起请求的那台机器。在自己的电脑上访问 localhost,测试的是自己的电脑;在 Linux 服务器上执行 curl localhost,测试的才是那台服务器。排查远程服务器时,最常见的错误之一,就是在客户端继续使用 127.0.0.1,结果根本没有访问服务器。

# 在 Linux 宿主机查看实际地址
ip -brief address

旧版 Docker Machine、Docker QuickStart Terminal 和 Toolbox 的网络边界不同,容器可能运行在 Linux 虚拟机里,宿主机浏览器需要使用虚拟机 IP。这类旧环境可以执行:

docker-machine ip default

该命令不适用于原生 Linux,也不能直接套到现代 Docker Desktop。现代 Docker Desktop 通常会处理宿主机到容器的本地转发,但局域网或公网访问仍取决于实际监听地址、宿主防火墙和路由。判断 localhost 指向哪里时,应以当前执行命令的机器和实际 Docker 运行环境为准。

三层测试都有响应,还要查哪些应用层问题?

如果三层请求针对同一应用接口,并且都得到了响应,端口发布链路基本成立,下一步就不该继续改 -p。此时需要检查请求使用的是 http 还是 https、接口路径是否正确、TLS 证书和 SNI 是否匹配、是否需要鉴权或 Cookie、反向代理是否收到正确的 Host,以及应用依赖的数据库或上游服务是否可用。

curl -v 还能帮助区分传输层和业务层:404 表示服务器可达但路径不对,401 或 403 表示请求已经到达应用但没有通过鉴权,502 则常见于反向代理无法连接上游。浏览器与 curl 结果不同时,还要检查 CORS 等浏览器侧限制。

排查结束后,保留这些诊断结果:

docker ps
docker logs --tail 200 <容器名>
docker port <容器名>
docker inspect --format '{{json .HostConfig.PortBindings}}' <容器名>
docker inspect --format '{{.HostConfig.NetworkMode}}' <容器名>

同时记录三组 curl 的结果、宿主机系统、Docker 安装方式、容器网络模式,以及实际使用的访问地址。日志和配置中若含有令牌、密码或 Cookie,应先脱敏。

这套分层方法可以确定请求失败在哪一层,但无法判断应用协议、TLS、路径、鉴权、反向代理和业务依赖问题,也不会自动解决问题。缺少具体系统版本与防火墙实现时,也不存在一条可以安全套用到所有服务器的放行命令。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容