Skip to content

Docker 网络底层原理:Network Namespace、veth、Bridge 与端口发布

“容器间同网络可以用容器名访问”只是使用结论。真正排查连接失败,需要知道数据包从容器 socket 经过什么网卡、路由、网桥、NAT 和宿主机防火墙,以及 Docker DNS 在哪里参与。

学习目标

学完后应能:

  1. 解释 Network Namespace 为什么让每个容器拥有独立 localhost、网卡、路由和端口空间。
  2. 解释 veth pair、Linux bridge、docker0 和用户自定义 bridge 的数据路径。
  3. 解释容器访问外网时的路由、转发与 SNAT/MASQUERADE。
  4. 解释 -p 18080:8080 的宿主机监听地址、DNAT和应用监听地址。
  5. 解释 Docker 内置 DNS、容器名/别名解析和为什么默认 bridge 行为不同。
  6. 比较 bridge、host、none、macvlan、ipvlan、overlay 的边界。
  7. 用分层证据排查 DNS、TCP拒绝、超时、端口映射、MTU和防火墙问题。

一、容器为什么有自己的localhost

Network Namespace 隔离:

  • 网络设备,如 eth0、lo。
  • IP 地址。
  • 路由表。
  • ARP/邻居表。
  • 防火墙规则视图。
  • Socket 和端口空间。
text
宿主机Network Namespace
├── 127.0.0.1:8080 -> 宿主机自己的进程
├── eth0 -> 宿主机物理/虚拟网卡
└── docker0/app-net bridge

容器A Network Namespace
├── 127.0.0.1:8080 -> 容器A自己的进程
└── eth0 -> veth一端

容器B Network Namespace
├── 127.0.0.1:3306 -> 容器B自己的进程
└── eth0 -> 另一组veth

所以 order-api 容器配置:

text
jdbc:mysql://localhost:3306/order

表示连接 order-api 容器自身的 3306,不是 mysql 容器,也不是宿主机 MySQL。

二、veth pair像一根虚拟网线

veth 成对创建,从一端发送的数据包会出现在另一端。Docker 将一端移动到容器 Network Namespace 并命名为 eth0,另一端留在宿主机并接到 Linux bridge。

mermaid
flowchart TD
    A["容器A应用Socket"] --> B["容器A eth0"]
    B --> C["veth pair宿主机端"]
    C --> D["Linux bridge app-net"]
    D --> E["容器B veth宿主机端"]
    E --> F["容器B eth0"]
    F --> G["容器B应用Socket"]

创建实验:

bash
docker network create --driver bridge app-net
docker run -d --name web --network app-net nginx:1.25
docker run -d --name client --network app-net alpine:3.20 sleep 1d

查看容器网络:

bash
docker network inspect app-net
docker inspect web --format '{{json .NetworkSettings.Networks}}'

宿主机查看 veth/bridge:

bash
ip link
bridge link
bridge fdb show

命令需要宿主机 Linux 权限;Docker Desktop 的 Linux 网络运行在其虚拟机内部,Windows 主机上未必直接看到同样接口。

三、Linux bridge怎么转发

Bridge 工作在二层,根据 MAC 地址转发表决定从哪个端口转发。容器在同一 bridge 子网中互访,通常先通过 ARP/邻居发现目标 MAC,再通过 bridge 转发,不需要先绕到外部路由器。

text
容器A判断目标IP与自身同子网
→ ARP询问目标IP对应MAC
→ 数据帧从eth0进入veth
→ bridge根据MAC表转发到容器B veth
→ 容器B eth0收到
→ 内核交给目标端口socket

如果 Docker 配置了网络隔离/防火墙规则,二层路径仍可能被过滤。不能看到同一子网就断言一定可达。

四、默认bridge和自定义bridge区别

Docker 安装后通常有默认 bridge,常关联 docker0。用户自定义 bridge:

bash
docker network create app-net

通常具有更好的容器名/别名 DNS、网络隔离和动态连接能力。生产/Compose 更推荐按应用创建用户网络,而不是依赖旧式默认 bridge 和手工 IP。

bash
docker run --name a --network app-net ...
docker run --name b --network app-net ...

容器 A 可以使用 b 作为目标名。IP 可能在重建后变化,因此不要把 inspect 得到的 172.x IP 硬编码进配置。

五、Docker内置DNS

在用户自定义网络中,容器 /etc/resolv.conf 常将 DNS 指向 Docker 内置 resolver,例如 127.0.0.11。它将容器名和网络别名解析为同一网络中的容器地址,并把其他查询转发到宿主机配置的上游 DNS。

bash
docker exec client cat /etc/resolv.conf
docker exec client getent hosts web
docker exec client nslookup web

镜像可能没有 getent/nslookup。不要因工具不存在就判断 DNS 失败,可用批准的诊断容器共享目标网络 namespace:

bash
docker run --rm --network app-net nicolaka/netshoot getent hosts web

生产环境拉取诊断镜像需遵守镜像来源和审批规则。

网络别名

bash
docker network connect --alias order-service app-net order-api

Compose 的服务名通常成为网络 DNS 名,可增加 aliases。多个副本解析可能返回多个地址或由平台规则决定;普通 Docker Compose 不等于 Kubernetes Service 负载均衡语义。

六、容器访问外网全过程

假设容器地址 172.20.0.2 访问 8.8.8.8:

mermaid
flowchart TD
    A["容器应用访问外部IP"] --> B["容器路由表选择默认网关"]
    B --> C["eth0与veth进入bridge"]
    C --> D["宿主机开启IP转发"]
    D --> E["宿主机路由到外网网卡"]
    E --> F["SNAT/MASQUERADE改写源地址"]
    F --> G["外部服务器"]
    G --> H["返回包按连接跟踪还原"]
    H --> A

容器私网 IP 通常无法在外部网络直接路由,Docker 通过 iptables/nftables 兼容层配置 NAT。MASQUERADE 将源地址改成宿主机出口地址,连接跟踪负责返回流量映射。

排查外网失败:

bash
docker exec client ip addr
docker exec client ip route
docker exec client cat /etc/resolv.conf
docker exec client wget -S -O- http://example.com

分开测试:

text
域名失败、IP成功 -> DNS
IP也失败 -> 路由、转发、防火墙、代理
TCP成功、HTTP失败 -> TLS/HTTP/应用层

七、-p 18080:8080背后发生什么

bash
docker run -d --name web -p 18080:8080 nginx:1.25

含义:将宿主机发布地址/端口流量转发到容器 IP 的 8080。

mermaid
flowchart TD
    A["客户端访问宿主机IP:18080"] --> B["宿主机路由与防火墙"]
    B --> C["Docker端口发布DNAT/代理路径"]
    C --> D["目标改写为容器IP:8080"]
    D --> E["bridge和veth"]
    E --> F["容器eth0"]
    F --> G["容器应用监听8080"]

具体由 iptables/nftables、docker-proxy 和平台版本共同实现,不能把某一版本实现当永久唯一方式。核心语义是宿主机发布端口到容器地址端口。

发布地址

bash
# 所有宿主机接口
-p 18080:8080

# 只绑定宿主机回环,外部不能直接访问
-p 127.0.0.1:18080:8080

# 指定宿主机地址
-p 192.168.1.10:18080:8080

默认绑定 0.0.0.0 可能向外暴露服务,必须结合防火墙、安全组和反向代理设计。

应用监听地址

容器应用如果只监听:

text
127.0.0.1:8080

从容器 eth0 IP 到 8080 的流量无法被该 socket 接收。通常服务应监听:

text
0.0.0.0:8080

或者明确容器接口地址。

EXPOSE不是发布

Dockerfile:

dockerfile
EXPOSE 8080

只是声明镜像预期端口,不自动生成宿主机映射。验证必须看:

bash
docker port web
docker inspect web --format '{{json .NetworkSettings.Ports}}'

八、连接从TCP角度怎么判读

Connection refused

通常表示路径到达目标主机/namespace,但目标端口没有监听,或防火墙主动 REJECT。检查:

text
容器是否运行
应用是否启动
监听端口是否正确
目标IP/服务名是否正确
应用是否只监听localhost

Timeout

可能是数据包被 DROP、路由错误、安全组、NetworkPolicy、宿主机转发、目标无响应。超时比 refused 更难定位,需要分别抓连接两端和中间路径证据。

DNS错误

Name or service not known,检查网络是否相同、服务名、aliases、resolv.conf、上游 DNS。不要把 DNS 名拼错当成“Docker网络不通”。

TLS/HTTP错误

TCP 建立后出现证书错误、HTTP 401/500,说明网络层大体已通,应转向 TLS、代理、应用认证和业务日志,不能继续只 ping。

九、网络模式比较

bridge

默认常用,Network Namespace 隔离,通过 veth/bridge 通信,支持端口发布。适合单机多容器。

host

bash
docker run --network host ...

容器共享宿主机 Network Namespace:无独立容器 IP、端口直接占用宿主机端口,网络隔离降低。Linux 原生行为与 Docker Desktop 支持需按版本验证。

适合对网络性能/大量端口有特殊要求的受控服务,不应因为不会配置 -p 就使用 host。

none

只有 loopback 或极少网络配置,用于完全不需网络或自行配置网络的场景。

macvlan

为容器分配独立 MAC,使其像局域网物理设备直接出现。需要交换机/网络允许多个 MAC;宿主机与 macvlan 子接口容器默认可能不能直接互通,需要额外宿主机 macvlan 接口。云环境常限制。

ipvlan

共享/减少 MAC 需求,通过不同 IP 组织,L2/L3 模式行为不同。适合有明确网络团队规划的场景。

overlay

跨主机逻辑网络,通常通过封装在底层网络上传输,需要控制面维护端点和隧道。Docker Swarm 使用;Kubernetes 网络通常由 CNI 插件实现,不应把 Docker overlay 规则直接套到所有 K8s。

十、MTU为什么会导致“有的请求通、有的不通”

隧道、VPN、overlay 增加封装头,实际可用 MTU 下降。如果容器网卡 MTU 大于路径支持且 PMTU/ICMP 被阻断,小包可通,大 TLS 包或上传卡住。

检查:

bash
docker exec client ip link show eth0
ip link show docker0
ping -M do -s 1472 <target>

参数因系统不同。不要直接全局把 MTU 调小;先确认宿主机、Docker daemon、overlay/VPN和云网络的完整路径,再统一配置并重建网络/容器验证。

十一、iptables/nftables与防火墙

Docker 会配置转发、NAT 和隔离规则。现代发行版可能使用 nftables 后端但提供 iptables 兼容命令。

bash
iptables -t nat -S
iptables -S FORWARD
nft list ruleset

不要在不了解规则所有者时执行 iptables -F,会破坏 Docker、Kubernetes、VPN和宿主机安全规则。Docker 重启也可能重建部分规则。

发布端口可能绕过某些只考虑 INPUT 的旧防火墙预期,生产应结合 DOCKER-USER 链/平台推荐规则、安全组和入口代理治理,具体以 Docker 和发行版版本为准。

十二、抓包与namespace诊断

获取容器宿主机PID:

bash
PID=$(docker inspect web --format '{{.State.Pid}}')

进入网络 namespace:

bash
nsenter -t "$PID" -n ip addr
nsenter -t "$PID" -n ip route

抓包:

bash
nsenter -t "$PID" -n tcpdump -i any -nn port 8080
tcpdump -i any -nn port 18080

通过两端抓包判断:请求是否到宿主机、是否完成 DNAT、是否进入容器、容器是否响应。抓包可能包含敏感业务数据,需授权、限时、限过滤条件和安全保存。

十三、商业场景:Order API连不上MySQL

配置:

text
jdbc:mysql://localhost:3306/order

现象:容器日志 Connection refused

排查:

bash
docker inspect order-api --format '{{json .NetworkSettings.Networks}}'
docker inspect mysql --format '{{json .NetworkSettings.Networks}}'
docker exec order-api getent hosts mysql
docker exec order-api sh -c 'nc -vz mysql 3306'

根因:localhost 指 order-api 自己。两容器应加入同一用户网络,URL 改为:

text
jdbc:mysql://mysql:3306/order

注意数据库账号授权、TLS和初始化完成仍可能导致后续连接错误;网络连通只证明 TCP 层。

十四、完整排查决策树

mermaid
flowchart TD
    A["容器访问目标失败"] --> B{"使用域名还是IP"}
    B -- "域名" --> C["getent/nslookup检查解析"]
    B -- "IP" --> D["检查ip route和Network"]
    C --> E{"是否解析成功"}
    E -- "否" --> F["检查同网络、服务名、alias、上游DNS"]
    E -- "是" --> D
    D --> G["nc/curl测试目标端口"]
    G --> H{"结果是什么"}
    H -- "refused" --> I["检查目标进程和监听地址"]
    H -- "timeout" --> J["检查路由、转发、防火墙、安全组、MTU"]
    H -- "TCP成功" --> K["检查TLS、HTTP、认证和业务"]
    I --> L["修复后两端复测"]
    J --> L
    K --> L

十五、面试标准回答

Docker bridge网络原理

Docker 为容器创建独立 Network Namespace和eth0,通过veth pair连接宿主机Linux bridge。同一bridge内,数据帧经veth和bridge按MAC转发;访问外网时宿主机开启IP转发并通过SNAT/MASQUERADE转换源地址;发布端口时通过DNAT/代理把宿主机端口转到容器IP端口。用户自定义bridge还提供容器名和网络别名DNS。

容器内localhost为什么不能访问另一个容器

每个容器有独立Network Namespace,localhost只指向当前容器的loopback。容器间应加入同一用户自定义网络,通过服务名/网络别名和目标容器端口访问,不能硬编码易变化的容器IP。

EXPOSE和-p区别

EXPOSE只是镜像元数据,声明应用预期监听端口,不会自动发布到宿主机。-p 宿主机端口:容器端口 才创建端口绑定和转发规则;同时容器应用还必须监听容器eth0可达的地址,通常是0.0.0.0。

host网络有什么代价

host模式共享宿主机Network Namespace,减少虚拟网络转换并直接使用宿主机端口,但失去独立IP和端口隔离,端口更容易冲突,安全边界变弱。它不是解决端口配置问题的默认方案,Docker Desktop支持也需按版本验证。

关联知识点