Docker 网络底层原理:Network Namespace、veth、Bridge 与端口发布
“容器间同网络可以用容器名访问”只是使用结论。真正排查连接失败,需要知道数据包从容器 socket 经过什么网卡、路由、网桥、NAT 和宿主机防火墙,以及 Docker DNS 在哪里参与。
学习目标
学完后应能:
- 解释 Network Namespace 为什么让每个容器拥有独立 localhost、网卡、路由和端口空间。
- 解释 veth pair、Linux bridge、docker0 和用户自定义 bridge 的数据路径。
- 解释容器访问外网时的路由、转发与 SNAT/MASQUERADE。
- 解释
-p 18080:8080的宿主机监听地址、DNAT和应用监听地址。 - 解释 Docker 内置 DNS、容器名/别名解析和为什么默认 bridge 行为不同。
- 比较 bridge、host、none、macvlan、ipvlan、overlay 的边界。
- 用分层证据排查 DNS、TCP拒绝、超时、端口映射、MTU和防火墙问题。
一、容器为什么有自己的localhost
Network Namespace 隔离:
- 网络设备,如 eth0、lo。
- IP 地址。
- 路由表。
- ARP/邻居表。
- 防火墙规则视图。
- Socket 和端口空间。
宿主机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 容器配置:
jdbc:mysql://localhost:3306/order表示连接 order-api 容器自身的 3306,不是 mysql 容器,也不是宿主机 MySQL。
二、veth pair像一根虚拟网线
veth 成对创建,从一端发送的数据包会出现在另一端。Docker 将一端移动到容器 Network Namespace 并命名为 eth0,另一端留在宿主机并接到 Linux bridge。
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"]创建实验:
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查看容器网络:
docker network inspect app-net
docker inspect web --format '{{json .NetworkSettings.Networks}}'宿主机查看 veth/bridge:
ip link
bridge link
bridge fdb show命令需要宿主机 Linux 权限;Docker Desktop 的 Linux 网络运行在其虚拟机内部,Windows 主机上未必直接看到同样接口。
三、Linux bridge怎么转发
Bridge 工作在二层,根据 MAC 地址转发表决定从哪个端口转发。容器在同一 bridge 子网中互访,通常先通过 ARP/邻居发现目标 MAC,再通过 bridge 转发,不需要先绕到外部路由器。
容器A判断目标IP与自身同子网
→ ARP询问目标IP对应MAC
→ 数据帧从eth0进入veth
→ bridge根据MAC表转发到容器B veth
→ 容器B eth0收到
→ 内核交给目标端口socket如果 Docker 配置了网络隔离/防火墙规则,二层路径仍可能被过滤。不能看到同一子网就断言一定可达。
四、默认bridge和自定义bridge区别
Docker 安装后通常有默认 bridge,常关联 docker0。用户自定义 bridge:
docker network create app-net通常具有更好的容器名/别名 DNS、网络隔离和动态连接能力。生产/Compose 更推荐按应用创建用户网络,而不是依赖旧式默认 bridge 和手工 IP。
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。
docker exec client cat /etc/resolv.conf
docker exec client getent hosts web
docker exec client nslookup web镜像可能没有 getent/nslookup。不要因工具不存在就判断 DNS 失败,可用批准的诊断容器共享目标网络 namespace:
docker run --rm --network app-net nicolaka/netshoot getent hosts web生产环境拉取诊断镜像需遵守镜像来源和审批规则。
网络别名
docker network connect --alias order-service app-net order-apiCompose 的服务名通常成为网络 DNS 名,可增加 aliases。多个副本解析可能返回多个地址或由平台规则决定;普通 Docker Compose 不等于 Kubernetes Service 负载均衡语义。
六、容器访问外网全过程
假设容器地址 172.20.0.2 访问 8.8.8.8:
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 将源地址改成宿主机出口地址,连接跟踪负责返回流量映射。
排查外网失败:
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分开测试:
域名失败、IP成功 -> DNS
IP也失败 -> 路由、转发、防火墙、代理
TCP成功、HTTP失败 -> TLS/HTTP/应用层七、-p 18080:8080背后发生什么
docker run -d --name web -p 18080:8080 nginx:1.25含义:将宿主机发布地址/端口流量转发到容器 IP 的 8080。
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 和平台版本共同实现,不能把某一版本实现当永久唯一方式。核心语义是宿主机发布端口到容器地址端口。
发布地址
# 所有宿主机接口
-p 18080:8080
# 只绑定宿主机回环,外部不能直接访问
-p 127.0.0.1:18080:8080
# 指定宿主机地址
-p 192.168.1.10:18080:8080默认绑定 0.0.0.0 可能向外暴露服务,必须结合防火墙、安全组和反向代理设计。
应用监听地址
容器应用如果只监听:
127.0.0.1:8080从容器 eth0 IP 到 8080 的流量无法被该 socket 接收。通常服务应监听:
0.0.0.0:8080或者明确容器接口地址。
EXPOSE不是发布
Dockerfile:
EXPOSE 8080只是声明镜像预期端口,不自动生成宿主机映射。验证必须看:
docker port web
docker inspect web --format '{{json .NetworkSettings.Ports}}'八、连接从TCP角度怎么判读
Connection refused
通常表示路径到达目标主机/namespace,但目标端口没有监听,或防火墙主动 REJECT。检查:
容器是否运行
应用是否启动
监听端口是否正确
目标IP/服务名是否正确
应用是否只监听localhostTimeout
可能是数据包被 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
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 包或上传卡住。
检查:
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 兼容命令。
iptables -t nat -S
iptables -S FORWARD
nft list ruleset不要在不了解规则所有者时执行 iptables -F,会破坏 Docker、Kubernetes、VPN和宿主机安全规则。Docker 重启也可能重建部分规则。
发布端口可能绕过某些只考虑 INPUT 的旧防火墙预期,生产应结合 DOCKER-USER 链/平台推荐规则、安全组和入口代理治理,具体以 Docker 和发行版版本为准。
十二、抓包与namespace诊断
获取容器宿主机PID:
PID=$(docker inspect web --format '{{.State.Pid}}')进入网络 namespace:
nsenter -t "$PID" -n ip addr
nsenter -t "$PID" -n ip route抓包:
nsenter -t "$PID" -n tcpdump -i any -nn port 8080
tcpdump -i any -nn port 18080通过两端抓包判断:请求是否到宿主机、是否完成 DNAT、是否进入容器、容器是否响应。抓包可能包含敏感业务数据,需授权、限时、限过滤条件和安全保存。
十三、商业场景:Order API连不上MySQL
配置:
jdbc:mysql://localhost:3306/order现象:容器日志 Connection refused。
排查:
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 改为:
jdbc:mysql://mysql:3306/order注意数据库账号授权、TLS和初始化完成仍可能导致后续连接错误;网络连通只证明 TCP 层。
十四、完整排查决策树
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支持也需按版本验证。
