跳转到内容
123xiao | 无名键客

《Docker 容器网络实战:用 bridge、host 与自定义网络排查中级项目中的连通性问题》

字数: 0 阅读时长: 1 分钟

背景与问题

在中级项目里,Docker 网络问题很少表现为“完全不通”,更多时候是这种让人皱眉的情况:

  • 容器之间有时能通,有时不能通
  • 宿主机能访问容器,容器却访问不了宿主机服务
  • localhost 在容器里明明能通,换成另一个容器就不通
  • 切到 host 网络后问题“突然好了”,但又带来端口冲突和安全疑虑
  • docker-compose 里服务名解析正常,手工 docker run 却不行

我自己排这类问题时,最容易浪费时间的不是命令不会,而是没有先搞清楚容器当前到底在哪个网络模型里bridgehost、自定义 bridge 网络,看起来都像“能联网”,但连通性边界完全不一样。

这篇文章不打算泛讲 Docker 网络大全,而是围绕一个典型故障排查思路展开:
先复现,再定位网络层级,再用 bridge、host、自定义网络逐个验证,最后给出止血与长期方案。


背景示意:三种常见网络模式的差异

flowchart LR
    subgraph Host[宿主机]
        APP[宿主机服务: 127.0.0.1:8080]
        subgraph BR[Docker 默认 bridge]
            C1[容器 A]
            C2[容器 B]
        end
        subgraph CNET[自定义 bridge 网络]
            C3[web]
            C4[api]
        end
        H1[host 网络容器]
    end

    C1 -. 不能直接用服务名 .-> C2
    C3 -->|服务名解析| C4
    H1 -->|共享宿主机网络栈| APP

从排障视角,可以先记住一句话:

  • 默认 bridge:能出网,容器间不天然靠名字互通,偏“单机临时运行”
  • host:容器直接用宿主机网络栈,简单粗暴,但隔离弱
  • 自定义 bridge:容器间支持内置 DNS 解析,最适合一组业务服务互联排障

现象复现

下面我们故意造一个常见问题:

  • 启动一个 HTTP 服务容器 web1
  • 再启动一个诊断容器 client1
  • 在默认 bridge 网络中,尝试通过容器名访问 web1
  • 观察失败,再切换到自定义网络验证成功

1)启动一个测试服务

docker run -d --name web1 nginx:alpine

确认服务在运行:

docker ps

2)启动一个诊断容器并测试访问

docker run --rm -it --name client1 alpine:3.19 sh

进入容器后安装基础工具:

apk add --no-cache curl bind-tools iproute2

先用容器名访问:

curl http://web1

大概率你会得到类似错误:

curl: (6) Could not resolve host: web1

这就是默认 bridge 下非常典型的“我以为容器名能通,其实不能”。


核心原理

Docker 网络排障如果只记命令,很快就乱。中级项目建议按这三个问题理解:

  1. 当前容器在哪个网络里?
  2. 目标地址是 IP、宿主机地址,还是容器服务名?
  3. 报错属于 DNS 解析失败、路由失败,还是端口未监听?

1)默认 bridge 与自定义 bridge 的关键差别

默认 bridge 网络是 Docker 装好就有的。很多人第一次接触时会误以为:

只要都在 bridge,容器之间就能互相用名字访问。

其实不是。
容器名自动 DNS 解析,主要发生在“用户自定义 bridge 网络”中。

查看网络:

docker network ls

你通常会看到:

  • bridge
  • host
  • none

如果执行:

docker network inspect bridge

你会看到加入默认 bridge 的容器信息,但这不等于有稳定的服务发现能力。

2)host 网络为什么“看起来很灵”

host 模式下,容器不再拥有独立网络命名空间的虚拟网卡视角(更准确说,是直接共享宿主机网络栈)。所以:

  • 容器访问 127.0.0.1,实际就是宿主机的 127.0.0.1
  • 不需要 Docker 做端口映射
  • 网络性能路径更短一些

但代价也很明显:

  • 端口冲突直接暴露
  • 容器网络隔离基本没了
  • 排障时容易把“容器问题”误判成“宿主机问题”

3)排障时要区分三类失败

DNS 解析失败

典型报错:

Could not resolve host

说明你连“目标是谁”都没找到,先查:

  • 容器是否在同一自定义网络
  • 容器名/服务名是否正确
  • /etc/resolv.conf 和 Docker 内置 DNS 是否正常

TCP 连不上

典型报错:

Connection refused
No route to host
Operation timed out

它们意义不同:

  • Connection refused:对方地址可达,但端口没监听或被拒绝
  • No route to host:路由/网络路径有问题
  • timed out:常见于防火墙、丢包、目标未响应

HTTP 层失败

比如 502、404、握手失败。
这时网络可能已经通了,问题在应用协议层,不要继续死磕 Docker 网络。


一次完整定位路径

我习惯按下面顺序走,基本不会绕太远。

flowchart TD
    A[现象: 访问失败] --> B{能解析目标吗?}
    B -- 否 --> C[查网络类型/服务名/DNS]
    B -- 是 --> D{能建立TCP连接吗?}
    D -- 否 --> E[查监听端口/端口映射/防火墙/路由]
    D -- 是 --> F{应用响应正常吗?}
    F -- 否 --> G[查HTTP协议/应用配置/反向代理]
    F -- 是 --> H[网络正常, 问题已定位]

可以把它理解为:

名字 -> 路 -> 门 -> 服务

  • 名字:DNS / 服务名
  • 路:IP 路由 / 网络模式
  • 门:端口监听 / 防火墙
  • 服务:应用本身

实战代码(可运行)

下面用一组最小可运行命令,把三种网络模式都跑一遍。


场景 A:默认 bridge 下的连通性问题

启动服务容器

docker run -d --name web1 nginx:alpine

查看容器网络信息

docker inspect web1 --format '{{json .NetworkSettings.Networks}}'

启动诊断容器

docker run --rm -it alpine:3.19 sh

容器内执行:

apk add --no-cache curl bind-tools iproute2
ip addr
ip route
nslookup web1 || true
curl -I http://web1 || true

你会看到:

  • 可能 DNS 无法解析 web1
  • 即使知道 IP,也不推荐依赖动态 IP 做业务调用

用容器 IP 临时验证

在宿主机查 IP:

docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web1

假设得到 172.17.0.2,再到诊断容器里:

curl -I http://172.17.0.2

如果能通,说明:

  • 网络路径基本通
  • 问题更可能在服务发现/名称解析而不是 TCP

场景 B:自定义 bridge 网络解决服务互访

创建自定义网络

docker network create app-net

在该网络中启动两个容器

docker run -d --name web2 --network app-net nginx:alpine
docker run --rm -it --name client2 --network app-net alpine:3.19 sh

进入 client2 后执行:

apk add --no-cache curl bind-tools iproute2
nslookup web2
curl -I http://web2

这时通常就能成功。

查看自定义网络详情

docker network inspect app-net

这里能看到容器都加入了同一个用户自定义网络,Docker 内置 DNS 会对容器名做解析。


场景 C:host 网络快速止血验证

有些时候你怀疑问题不是应用本身,而是 NAT、端口映射或者容器网络隔离。
这时可以临时用 host 模式做对照实验。

用 host 网络启动 nginx

docker run -d --name web-host --network host nginx:alpine

然后在宿主机上查看监听:

ss -lntp | grep :80 || true

如果容器启动成功,你会发现宿主机直接监听了 80 端口。

在容器内/宿主机验证

宿主机执行:

curl -I http://127.0.0.1:80

如果通,而 bridge 模式不通,就说明排查重点要回到:

  • 端口映射是否正确
  • 容器是否监听在 0.0.0.0 而不是 127.0.0.1
  • 网络策略/iptables 是否拦截

注意:host 更适合作为“对照组”或临时止血,不一定适合作为长期方案。


场景 D:宿主机服务无法被容器访问

这也是高频坑。

假设宿主机跑了一个只监听本地回环的服务:

python3 -m http.server 8080 --bind 127.0.0.1

宿主机本地访问:

curl http://127.0.0.1:8080

没问题。

但容器里访问宿主机:

docker run --rm -it alpine:3.19 sh

容器内:

apk add --no-cache curl
curl http://host.docker.internal:8080

在 Linux 环境里,host.docker.internal 不一定天然可用。可以这样启动:

docker run --rm -it --add-host=host.docker.internal:host-gateway alpine:3.19 sh

然后再测试:

apk add --no-cache curl
curl http://host.docker.internal:8080

如果仍然失败,很可能不是名字问题,而是宿主机服务只绑定了 127.0.0.1
改为监听所有地址:

python3 -m http.server 8080 --bind 0.0.0.0

这时容器再访问,通常就通了。


一张更贴近排障过程的时序图

sequenceDiagram
    participant U as 排障者
    participant C as client容器
    participant D as Docker网络
    participant W as web容器/宿主机服务

    U->>C: curl http://web
    C->>D: 解析 web
    alt DNS 失败
        D-->>C: Could not resolve host
        U->>D: 检查 network / inspect / 服务名
    else DNS 成功
        C->>W: 发起 TCP 连接
        alt 连接被拒绝
            W-->>C: Connection refused
            U->>W: 检查监听地址和端口
        else 连接超时
            U->>D: 检查路由/防火墙/NAT
        else HTTP 成功
            W-->>C: 200 OK
        end
    end

常见坑与排查

下面这些坑,我基本都见过,很多还踩过不止一次。

1)把 localhost 当成“另一个容器”

这是最经典的误区。

在容器里:

  • localhost
  • 127.0.0.1

指向的是当前容器自己,不是宿主机,也不是别的容器。

错误示例

api 容器里配置数据库地址:

DB_HOST=127.0.0.1

如果数据库并不在同一个容器里,那几乎必挂。

正确思路

  • 同网络容器之间:用服务名,如 mysql
  • 访问宿主机:用 host.docker.internal 或宿主机网关地址
  • host 网络模式下才可把容器与宿主机的回环视角合并考虑

2)服务只监听在 127.0.0.1

这个现象非常像“网络不通”,但本质不是网络。

比如应用在容器里只监听:

127.0.0.1:3000

即使做了:

docker run -p 3000:3000 ...

宿主机访问仍可能失败,因为容器外部流量无法进入容器内部回环接口上的服务。

如何验证

进容器看监听地址:

docker exec -it <container_name> sh

容器内:

ss -lntp

如果看到的是:

127.0.0.1:3000

就要改应用配置,让它监听:

0.0.0.0:3000

3)误以为 -p 影响容器间通信

-p 8080:80宿主机到容器的端口映射。
它不决定容器之间能否互通。

容器之间通信主要看:

  • 是否在同一网络
  • 是否能解析服务名
  • 容器内服务是否监听正确端口

所以一个服务即便没做 -p,在同一自定义网络里的其他容器也依然可以访问它。


4)默认 bridge 下硬编码 IP

有些项目图省事,直接把一个容器 IP 写进配置:

REDIS_HOST=172.17.0.3

短期可能有效,但重建容器后 IP 常常变化。
中级项目一旦开始扩缩容、重启、CI/CD 重建,这种配置会变成隐患。

建议

  • 单机多容器:用自定义网络 + 服务名
  • 复杂环境:交给 Compose、Swarm 或 Kubernetes 的服务发现机制

5)宿主机防火墙/iptables 干扰

有时你命令都对,配置也对,就是超时。
这时候要怀疑宿主机安全策略。

常用检查

iptables -t nat -L -n
iptables -L -n

或者在新系统上:

nft list ruleset

如果宿主机本身对 FORWARD、DOCKER 链做了额外限制,Docker 自动生成的网络规则可能被覆盖或冲突。


6)容器加入了错误网络

一个容器明明跑着,但访问不到另一个服务,最后发现压根不在同一个网络里。

快速检查

docker inspect <container_name> --format '{{json .NetworkSettings.Networks}}'

如果两个容器不在同一自定义网络,服务名解析和连通性就不能按你预期工作。

动态补救

docker network connect app-net <container_name>

必要时断开错误网络:

docker network disconnect bridge <container_name>

止血方案

当线上故障已经影响业务时,排障不只讲“优雅”,还得讲“先恢复”。

止血方案 1:切到 host 做对照验证

适用场景:

  • 怀疑是端口映射或容器网络层问题
  • 允许临时暴露到宿主机网络
  • 服务简单、单实例、端口冲突可控

不适合:

  • 多实例并存
  • 安全隔离要求高
  • 宿主机端口紧张

止血方案 2:改用自定义 bridge 并统一服务名

适用场景:

  • 单机或单节点多容器项目
  • 需要稳定的容器间互访
  • 当前问题集中在容器名解析和通信混乱

这是我更推荐的中长期方案。


止血方案 3:先用 IP 验证路径,再回收为服务名

适用场景:

  • 必须尽快判断“是 DNS 问题还是 TCP 问题”
  • 需要快速缩小故障范围

但只建议用于验证,不建议长期硬编码。


安全/性能最佳实践

网络能通只是底线,项目要稳定跑,还得考虑安全和性能边界。

1)优先使用自定义 bridge,而不是默认 bridge

原因很实际:

  • 服务发现更清晰
  • 网络边界更可控
  • 便于按项目划分网络域

例如:

docker network create project-a-net

然后服务都加入这个网络,而不是全堆在默认 bridge 里。


2)谨慎使用 host 网络

host 的优点是少一层 NAT 和映射,排障时很直接。
但长期使用要满足这些前提:

  • 明确知道容器会占用哪些宿主机端口
  • 业务可接受较弱的网络隔离
  • 宿主机上的其他服务不会发生端口冲突

如果是多租户、混部、或者安全要求高的环境,host 往往不是优选。


3)服务监听地址统一为 0.0.0.0

这是容器化应用的常见要求。
尤其 Web 服务、API 服务、消息网关,最好明确配置监听:

0.0.0.0

而不是开发环境习惯性的 127.0.0.1


4)保留最小化诊断工具镜像

生产镜像可以很瘦,但排障时别临时抓瞎。
我通常会准备一个轻量诊断容器,带上:

  • curl
  • dig/nslookup
  • iproute2
  • netcat
  • tcpdump(按需)

例如临时排障:

docker run --rm -it --network app-net nicolaka/netshoot bash

它对中级项目定位网络问题非常省时间。


5)把“网络验证”写进交付流程

很多连通性问题不是线上才出现,而是发布前没人验证。

建议至少固化这几个检查项:

  • 容器是否加入预期网络
  • 服务是否监听在 0.0.0.0
  • 容器间是否能通过服务名访问
  • 宿主机暴露端口是否符合设计
  • 是否存在与宿主机服务端口冲突

如果用 Compose,可以在部署脚本后追加健康检查。


一份实用排查清单

当你下次再遇到“容器不通”,可以直接按这个顺序走:

# 1. 看容器和端口
docker ps

# 2. 看容器在哪个网络
docker inspect <container_name> --format '{{json .NetworkSettings.Networks}}'

# 3. 看有哪些网络
docker network ls

# 4. 看目标网络详情
docker network inspect <network_name>

# 5. 进容器看IP、路由、DNS
docker exec -it <container_name> sh
ip addr
ip route
cat /etc/resolv.conf

# 6. 测服务名解析
nslookup <service_name> || true

# 7. 测TCP/HTTP
curl -v http://<service_name>:<port> || true
nc -zv <service_name> <port> || true

# 8. 看服务监听地址
ss -lntp

# 9. 看宿主机端口与规则
ss -lntp
iptables -t nat -L -n

如果你把每一步都能回答清楚,Docker 网络问题通常不会失控。


总结

排查 Docker 容器网络连通性,最怕的是一上来就“猜”。
更稳的方式是先搞清楚容器处在哪种网络模型里:

  • 默认 bridge:适合简单场景,但别指望天然服务发现
  • host:适合快速对照和临时止血,长期使用要谨慎
  • 自定义 bridge:单机多容器项目里最实用,服务名解析也最省心

如果你想要一句可执行建议,我会这么说:

  1. 业务容器默认用自定义 bridge 网络
  2. 服务监听地址统一用 0.0.0.0
  3. 排障时先分清是 DNS、TCP 还是应用层问题
  4. host 只做对照实验或明确评估后的特例方案
  5. 不要长期依赖容器 IP,尽量使用服务名

边界条件也要说明白:

  • 如果是单机开发环境,host 有时确实方便
  • 如果是多容器协作,自定义 bridge 几乎总比默认 bridge 更合适
  • 如果已经进入编排阶段,网络问题还要结合 Compose/Swarm/Kubernetes 的服务发现一起看

很多所谓“Docker 网络玄学”,其实拆开后就是:
名字有没有解析到、路能不能到、端口有没有开、服务是不是监听对了。

按这个顺序查,问题通常会快很多。


分享到:

上一篇
《面向中级开发者的 AI 应用实战:基于 RAG 构建企业知识库问答系统的架构设计与性能优化》
下一篇
《分布式架构中配置中心的高可用设计与灰度发布实践》