容器访问宿主机 NoRouteToHost 排查

本地调用第三方接口正常,线上同一套代码报 No route to host。查下来不是配置没读到,而是 Docker 容器访问「宿主机自己的局域网 IP + 端口映射」走了 hairpin,重启后这条绕路断了

现象

业务异常被包成一句「调用失败」,真正原因在 Caused byRestTemplate 发 GET 时出现 ResourceAccessException,根因是 java.net.NoRouteToHostException

目标地址正是配置里的第三方接口。说明程序读到了 URL,请求在 TCP 阶段就失败了,对方服务根本没收到。

容易踩的配置误区

线上挂载的外部配置文件里没有这项,jar 内的 application.yml 里有。

Spring Boot 是按属性合并,不是整份文件互相覆盖。外部没有的 key,会继续用 jar 里的值。堆栈里已经出现了 jar 中配置的那个地址,配置是生效的。

配置生效不等于网络可达。

排查过程

确认应用跑在容器里

docker ps 能看到业务容器,日志里进程号为 1,确定请求发生在容器网络命名空间内,不能拿开发机或宿主机的连通性当结论。

宿主机上看目标 IP

在宿主机执行地址、路由、ping、ip route get

结果:配置里的「第三方 IP」就是这台宿主机自己的网卡地址。ip route get 显示走本机回环,ping 通且延迟极低,只是在 ping 自己。

同时能看到 Docker 网桥,容器跑在私有网段上。

宿主机访问映射端口

ss 看端口在监听。从宿主机 curl 本机回环地址、以及 Docker 网桥网关,都能建立 TCP 连接。接口因缺少业务参数返回 500,只能说明服务进程活着,不能说明容器也能连上。

进容器复现

在业务容器里 curl 宿主机局域网 IP、以及网桥网关,全部 No route to host,和线上堆栈一致。

端口到底是谁在听

查端口和 Docker 需要管理员权限,否则连不上 Docker 套接字,ss 也看不到进程名。

结论:

  • 监听该端口的是 docker-proxy,说明是容器做了端口映射,不是宿主机上的裸进程
  • 业务容器和对端服务容器在同一张用户自定义网里
  • 对端就在旁边,有自己的容器名和容器 IP

同网直连

从业务容器 curl 对端容器名容器 IP,都能 Connected。返回 500 仍是参数不全,网络已经通了。

根因

错误路径:业务容器 → 宿主机局域网 IP → docker-proxy → 再转到旁边那个容器。

正确路径:业务容器 → 同一张 Docker 网 → 对端容器名。

前一种是 hairpin(数据包绕出容器,打到宿主机映射端口,再转回另一容器)。依赖 iptables / firewalld。容器访问宿主机映射端口常被 REJECT,表现就是 No route to host

开发机、宿主机本机访问「宿主机 IP + 映射端口」是从外面进来,不走 hairpin,所以本地一直正常。

为什么以前能用、重启后突然不行

外部配置文件里一直没有这项,长期用的是 jar 内地址。代码仓库里较早时就把容器网段 IP 改成了宿主机局域网 IP,之后线上用了几个月。近期有重启,配置本身没再改。

说明当时防火墙 / Docker 转发规则碰巧允许这条绕路。重启 Docker、firewalld,或重载防火墙之后,规则被冲掉或顺序错乱,hairpin 断掉,配置一行不变也会突然失败。

能用几个月,只说明当时状态允许绕路,不是正确的容器互访方式。容器 IP 重建会变,更不该写死。

修复

在线上外部配置里覆盖第三方地址,改为 Docker 内置 DNS 的对端容器名(不要写容器 IP,也不要写宿主机 IP),然后重启业务容器。

开发机不在那张容器网里,仍可通过宿主机映射端口访问,和容器内互访不是同一条路。

经验

  1. 先看完整 Caused by,不要只看业务包装异常。
  2. 日志里的目标 URL 能证明配置有没有读到;本次读到了,败在网络路径。
  3. 容器访问「宿主机局域网 IP + 映射端口」和「同网另一个容器」是两条路。同网互访用容器名。
  4. 宿主机 ping 自己的 IP 通,不能当连通性结论。
  5. 在 Docker 宿主机上查容器和端口,命令往往需要管理员权限。
  6. 容器 IP 会变,跨容器调用写服务名,不要写死私网地址。

排查命令备忘

把尖括号里的占位符换成自己环境中的值即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 确认容器与端口映射
sudo docker ps

# 看业务容器所在网络、IP、网关
sudo docker inspect <业务容器> --format '{{range $k,$v := .NetworkSettings.Networks}}net={{$k}} ip={{$v.IPAddress}} gw={{$v.Gateway}}{{"\n"}}{{end}}'

# 列出同一张网里的容器
sudo docker network inspect <网络名> --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'

# 看宿主机上谁在监听目标端口(需要权限才能看到进程名)
sudo ss -lntp | grep <端口>

# 宿主机访问本机映射(通只说明服务在这台机上)
curl -v --connect-timeout 5 "http://127.0.0.1:<端口>/<接口路径>"

# 容器内:错误路径(宿主机 IP / 网桥网关,常 No route to host)
sudo docker exec <业务容器> curl -v --connect-timeout 5 "http://<宿主机IP>:<端口>/<接口路径>"

# 容器内:正确路径(同网容器名)
sudo docker exec <业务容器> curl -v --connect-timeout 5 "http://<对端容器名>:<端口>/<接口路径>"