Docker 网络深度实战——bridge、host、overlay 网络模式与实际应用场景
Docker 网络深度实战——bridge、host、overlay 网络模式与实际应用场景
适读人群:使用 Docker 部署服务的后端/运维工程师 | 阅读时长:约 15 分钟 | 核心价值:彻底搞清三种网络模式的原理和选型依据,不再靠猜
我第一次真正搞懂 Docker 网络,是因为一次生产事故。
那是个周四下午,我们把一个新服务上线,用的是默认的 bridge 网络。服务本身跑起来了,但性能测试的结果让我们很困惑——同样的请求,延迟比预期高了 0.7ms,而且 CPU 使用率比预期高了 12%。
"才 0.7ms,有什么关系?"同事这么说。但这是一个每天处理 2000 万次请求的核心服务,0.7ms 的延迟叠加起来,影响很显著。
最后定位下来,问题就出在网络模式的选择上。
Docker 网络的基本原理
在讲三种模式之前,先把基础理清楚。
Docker 容器本质上是一个带有独立网络命名空间的进程。网络命名空间把容器的网络栈与宿主机的网络栈隔离开——容器有自己的 IP 地址、路由表、iptables 规则。
这个隔离带来了安全性,但也带来了性能代价:容器和宿主机之间通信、容器和容器之间通信,都需要经过内核的网络协议栈,涉及网络包的转发和 NAT。
理解了这一点,三种网络模式的区别就很好理解了。
bridge 网络:默认模式,最通用
工作原理
当你运行 docker run nginx 不指定网络时,默认使用 bridge 网络。Docker 在宿主机上创建一个虚拟网桥 docker0,给每个容器分配一个 veth pair(虚拟网卡对):一端接入容器网络命名空间,另一端接入 docker0 网桥。
容器A (172.17.0.2) <--veth--> docker0 (172.17.0.1) <--iptables NAT--> 宿主机 (192.168.1.100) <--> 外部网络
容器B (172.17.0.3) <--veth--> docker0容器访问外网:数据包从容器 veth → docker0 → 宿主机 eth0,经过 iptables MASQUERADE 规则做 SNAT。
外网访问容器:通过端口映射(-p 8080:80),宿主机收到 8080 端口的包,iptables DNAT 规则把目的地址改成容器 IP:80。
踩坑实录一:容器间通信用 IP 地址,重启后 IP 变了
现象:容器 A 硬编码了容器 B 的 IP 172.17.0.3,某次容器 B 重启后,IP 变成了 172.17.0.4(因为有其他容器先启动占用了 .3),导致 A 无法访问 B。
原因:default bridge 网络里,容器 IP 是动态分配的,重启后可能变化。而且 default bridge 不支持 DNS 解析,容器之间无法用服务名互相访问。
解法:创建自定义 bridge 网络,自定义网络自动提供 DNS 解析:
# 创建自定义网络
docker network create --driver bridge \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
myapp-network
# 启动容器时指定网络
docker run -d --name db --network myapp-network postgres:15
docker run -d --name app --network myapp-network \
-e DB_HOST=db \ # 直接用容器名作为 hostname!
myapp:latest自定义 bridge 网络提供了内置 DNS,容器可以直接用名字互相访问。这是我最推荐的开发/单机部署方案。
bridge 网络实际配置示例
# docker-compose.yml
version: '3.8'
networks:
backend:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
services:
postgres:
image: postgres:15-alpine
networks:
- backend
environment:
POSTGRES_PASSWORD: secret
redis:
image: redis:7-alpine
networks:
- backend
app:
image: myapp:latest
networks:
- backend
environment:
# 直接用服务名
DB_HOST: postgres
REDIS_HOST: redis
ports:
- "8080:8080"host 网络:性能最高,隔离最弱
工作原理
--network host 让容器直接使用宿主机的网络命名空间。容器不再有独立 IP,直接共享宿主机的 IP 和所有端口。
从性能角度:没有 veth、没有网桥、没有 NAT,网络包直接由宿主机的网卡收发。这就是我一开始那个例子里问题的答案——那个服务切换到 host 网络后,延迟从 0.7ms 降到了接近 0。
适用场景
host 网络适合:
- 高性能网络服务:如 nginx、HAProxy,需要极低延迟
- 监控采集 agent:如 node-exporter,需要访问宿主机网络信息
- 网络测试工具
# node-exporter 标准用法
docker run -d \
--name node-exporter \
--network host \
--pid host \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
prom/node-exporter:latest \
--path.procfs=/host/proc \
--path.sysfs=/host/sys踩坑实录二:host 网络下端口冲突导致容器启动失败
现象:同一台宿主机上运行了两个 nginx 容器,都用 host 网络,都监听 80 端口,第二个容器启动失败,报 bind: address already in use。
原因:host 网络下所有容器共享同一个网络命名空间,端口空间也是共享的,不能重复绑定同一端口。
解法:
- 用 bridge 网络 + 不同的宿主机端口映射
- 或者在容器内使用不同端口,通过前置代理路由
host 网络是一把双刃剑:性能极佳,但端口冲突问题和安全隔离问题都比较麻烦。生产环境谨慎使用,除非明确知道自己在做什么。
overlay 网络:跨主机通信的利器
工作原理
bridge 和 host 网络只能在单台宿主机上工作。当你有多台宿主机,容器需要跨主机通信时,就需要 overlay 网络。
overlay 网络通过 VXLAN 协议在多台宿主机之间构建虚拟的二层网络:
宿主机 A (10.0.1.1) 宿主机 B (10.0.1.2)
├── 容器 A1 (10.10.0.2) ├── 容器 B1 (10.10.0.3)
│ │
└── VXLAN tunnel (UDP 4789) ─────────────┘
封装:容器 MAC/IP → UDP payload → 宿主机 IP容器 A1 发包给容器 B1,数据包被 VXLAN 封装成 UDP 包,从宿主机 A 发送到宿主机 B,再解封装交给容器 B1。对容器来说,就像在同一个二层网络里一样。
配置 overlay 网络
overlay 网络需要 Swarm 模式(或者 Consul/etcd 做 key-value 存储):
# 初始化 Swarm
docker swarm init --advertise-addr 10.0.1.1
# 在其他节点加入
docker swarm join --token <token> 10.0.1.1:2377
# 创建 overlay 网络
docker network create \
--driver overlay \
--attachable \
--subnet 10.10.0.0/16 \
myoverlay-net
# 部署服务到 overlay 网络
docker service create \
--name webapp \
--network myoverlay-net \
--replicas 3 \
myapp:latest--attachable 参数允许普通容器(非 service)也能连接到这个网络,方便调试。
踩坑实录三:overlay 网络 MTU 配置导致大包传输失败
现象:服务在 overlay 网络里运行,小请求正常,但大文件传输(>1400 字节的包)偶发失败,有时会卡很久。
原因:VXLAN 封装会增加包头开销(50 字节左右),如果 overlay 网络的容器 MTU 还是默认的 1500,加上 VXLAN 头之后超过了底层物理网络的 MTU,导致分片或者丢包。
解法:创建 overlay 网络时指定较小的 MTU:
docker network create \
--driver overlay \
--opt com.docker.network.driver.mtu=1450 \
--subnet 10.10.0.0/16 \
myoverlay-net或者在 /etc/docker/daemon.json 里全局配置:
{
"mtu": 1450
}修改后重启 Docker 守护进程生效。这个问题不是所有环境都会遇到,具体取决于底层网络的 MTU 设置,但遇到了很难想到是这个原因,记录一下。
三种网络模式选型指南
| 场景 | 推荐网络模式 | 原因 |
|---|---|---|
| 单机开发/测试 | 自定义 bridge | 简单,支持 DNS 解析 |
| 单机生产,高性能需求 | host | 无 NAT 开销 |
| 多容器 compose 部署 | 自定义 bridge | compose 自动创建 |
| 多主机容器互通 | overlay | 跨主机二层网络 |
| 监控/运维 agent | host | 需要访问宿主机网络栈 |
| 极度安全隔离 | none + 手动配置 | 默认无网络,完全手控 |
还有一个 none 网络:--network none,容器没有任何网络接口(除了 loopback),完全网络隔离。用于批处理任务、加密计算等不需要网络的场景。
实用网络调试命令
# 查看所有网络
docker network ls
# 查看网络详情(包括连接的容器和 IP)
docker network inspect myapp-network
# 进入容器内部检查网络
docker exec -it mycontainer sh
# 然后用 ip addr、ping、curl 等工具调试
# 从宿主机测试容器网络
docker run --rm --network myapp-network \
nicolaka/netshoot \
curl http://myservice:8080/health
# 抓包分析(需要 privileged 权限)
docker run --rm --network container:mycontainer \
--cap-add NET_ADMIN \
nicolaka/netshoot \
tcpdump -i eth0 -w /tmp/capture.pcap
# 查看 iptables 规则(了解 NAT 情况)
iptables -t nat -L -n -v | grep DOCKERnicolaka/netshoot 这个镜像集成了几乎所有网络调试工具,强烈推荐加入你的工具箱。
Docker 网络是个看起来简单、实际上水很深的话题。bridge 是默认选择没错,但在性能敏感场景、多主机场景,选错网络模式会导致难以定位的问题。
理解每种模式的底层原理,遇到问题才能快速定位。希望这篇文章能帮到你。
