Docker 网络模式
是什么
Docker 通过 Linux 的 network namespace 给每个容器一套独立的网络栈(网卡、路由表、iptables 规则等),再通过不同的网络驱动把这套栈接到宿主机/外部世界。详见 namespace.md。
容器看到的网络和实际的网络数据流是两回事,网络模式决定的是后者——容器的 veth 怎么连出去、用不用 NAT、IP 怎么分配。
容器内的视角: 实际数据流(取决于网络模式):
┌──────────────┐ ┌──────────────┐
│ eth0@ifN │ │ eth0 (veth) │ ← 容器内看到的
│ 172.17.0.2 │ │ 172.17.0.2 │
└──────┬───────┘ └──────┬───────┘
│ │
│ ↓ 落到 docker0 桥(bridge 模式)
│ ┌──────┐
│ │docker0│ 172.17.0.1
│ └───┬──┘
│ │ 走 iptables NAT
│ ┌───▼──┐
│ │ eth0 │ 宿主网卡
│ └──────┘
└─ 在 host 模式下,这层 veth 不存在,容器直接用宿主 eth0
五种模式一览
| 模式 | 命令写法 | 容器有独立 IP | 端口映射需要 | 性能 | 典型场景 |
|---|---|---|---|---|---|
| bridge | --network bridge (默认) |
✅ docker0 网段 | 需要 -p |
中 | 单机开发、测试 |
| host | --network host |
❌ 用宿主机 IP | 不需要 | 最优 | 高性能网络、监控 agent |
| none | --network none |
❌ 只有 lo | 不需要 | — | 完全隔离的批处理任务 |
| container | --network container:<id> |
❌ 共享别的容器 | 看被共享容器 | 中 | sidecar 模式 |
| 自定义网络 | --network <用户创建的网络> |
✅ 自定义网段 | 看类型 | 中 | 多容器互通、DNS、服务发现 |
后两种其实是用户创建的网络(默认 driver 是 bridge),通过
docker network create创建后再 attach。
bridge 模式(默认)
工作原理
┌──────────────────┐
│ 容器 A │
│ vethA 172.17.0.2│
└────────┬─────────┘
│ peer 端
┌────────▼─────────┐
│ docker0 │ ← Linux bridge (brctl)
│ 172.17.0.1 │ 内核级交换机
└────────┬─────────┘
│
┌────────▼─────────┐
│ 容器 B │
│ vethB 172.17.0.3│
└──────────────────┘
│
│ (出网时)
┌────────▼─────────┐
│ iptables NAT │ ← 容器访问外网用
│ 172.17.0.0/16 │
└────────┬─────────┘
│
┌────────▼─────────┐
│ 宿主机 eth0 │
└──────────────────┘
- 每个容器有一对 veth pair,一端在容器内(叫
eth0),另一端挂在docker0这个 Linux bridge 上 docker0是 Docker 创建的虚拟交换机,IP 通常是172.17.0.1/16- 容器访问外网靠 iptables 做 MASQUERADE(SNAT)
- 外部访问容器靠 iptables 做 DNAT(这就是
-p 8080:80的本质)
PS:
- docker0 是 Docker 早期设计的默认桥。生产环境不要用默认 bridge——容器之间无法用容器名互相解析,且没有自定义子网。
- 容器看到的网卡名不一定是 eth0,如果是自定义网络可能是 eth1 等。
端口映射原理
docker run -p 8080:80 nginx
# iptables 实际生成的规则(简化)
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 \
-j DNAT --to-destination 172.17.0.2:80
# 任何访问宿主 8080 的包,目的地址改成容器 IP:80
这条 DNAT 链就是 容器外网访问慢、性能差、连接数受限 的根源——每包都过 iptables。生产高并发要避开它(用 host 网络或直接 hostPort)。
host 模式
容器没有独立网络栈,直接用宿主机的 eth0,共享 IP 和端口空间。
docker run --network host nginx
# 容器里直接 netstat -tlnp 看到的就是宿主机的所有端口
# 不需要 -p 映射,nginx 直接监听宿主 80
普通 bridge 模式: host 模式:
┌──────────────┐ ┌──────────────┐
│ 容器内 │ │ 容器内 │
│ nginx:80 │ │ nginx:80 │ ← 同一网络栈
└──────┬───────┘ └──────┬───────┘
│ veth │
┌──────▼───────┐ ┌──────▼───────┐
│ 宿主机 │ │ 宿主机 │
│ eth0:8080 │ ← iptables DNAT 过来 │ eth0:80 │ ← 直接监听
└──────────────┘ └──────────────┘
| 优势 | 代价 |
|---|---|
| 性能最优(少一跳 veth) | 端口冲突——不能用宿主机已占用的端口 |
| 无 NAT,双向连通性完整 | 容器之间无网络隔离 |
| localhost 直通 | 容器看到的 localhost = 宿主机的 |
| 适合监控、网络、抓包工具 | macvlan/ipvlan 也能解决部分场景 |
PS:
- Kubernetes 的 hostNetwork: true 字段对应的就是这种模式
- 在 Mac/Windows 桌面上,Docker Desktop 跑的是 VM,host 模式其实指 VM 的网络栈,不是宿主机——这点容易踩坑
none 模式
容器只有 lo 回环网卡,没有其他网络接口。
docker run --network none alpine ip addr
# 1: lo: <LOOPBACK> mtu 65536
# inet 127.0.0.1/8
# 没有 eth0,没有任何对外通道
适用:
- 离线批处理(解压文件、跑数据转换)
- 安全沙箱(不连网,无法外泄)
- 配合 docker network connect 按需接入
container 模式
新容器共享指定容器的 network namespace,IP 和端口完全一样。
# 启动 sidecar 模式:日志收集器跟业务容器共享网络
docker run -d --name app nginx
docker run -d --name logger \
--network container:app \
fluent/fluent-bit
# logger 看到的网络跟 app 一模一样,能用 localhost 访问 app 的 80
docker exec logger curl http://localhost
| 场景 | 例子 |
|---|---|
| 紧耦合 sidecar | 日志/metrics agent 跟主容器共享端口 |
| 网络调优/调试 | 临时启动 netshoot 共享问题容器的网络栈 |
| 性能敏感 | 替代 pid:container 网络版本 |
PS:
- 跟 K8s 的 shareProcessNamespace 思路类似,但这里是网络维度
- 不能被多个容器共享:第二个 --network container:X 启动的会失败
自定义网络(推荐生产用)
docker network create 创建的网络,比默认 bridge 多了三个关键能力:
docker network create \
--driver bridge \
--subnet 10.10.0.0/24 \
--gateway 10.10.0.1 \
my-net
# 启动容器并加入
docker run -d --name web --network my-net nginx
docker run -d --name db --network my-net redis
# 关键能力 1: 容器名 DNS 解析(默认 bridge 没有!)
docker exec web ping db
# PING db (10.10.0.3): 56 data bytes ← 真的能 ping 通
# 关键能力 2: 容器间自动服务发现
docker exec web nslookup db
自定义网络 vs 默认 bridge
| 维度 | 默认 bridge | 自定义 bridge |
|---|---|---|
| 容器名 DNS | ❌(只 IP) | ✅ 内置 DNS |
| 容器隔离 | 弱(都能互通) | 强(同网络才互通) |
| 子网可配 | ❌ 写死 172.17 | ✅ 任意 |
| 链接容器(link) | 需要 --link 旧机制 |
自动可解析 |
| 多主机 | ❌ | ❌(用 overlay) |
PS:
所有新项目都应该用自定义网络,不要用默认 bridge。最大的区别就是 DNS——容器名能解析,极大简化 Compose / 多容器互访。
容器间通信
1. 同一自定义网络内:
web → db ✅ 容器名直接通
web → db:6379 ✅ 端口直通,不需要 -p 暴露给宿主
2. 跨网络:
web (net-a) → db (net-b) ❌ 不通,需要把 db attach 到 net-a
docker network connect net-a db
3. 跨主机:
Docker Swarm 模式 → overlay 网络(vxlan 封装)
K8s → CNI 插件(calico/cilium/flannel)
DNS 解析细节
容器内 /etc/resolv.conf 默认指向 Docker 内置 DNS(127.0.0.11):
docker exec web cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:2
容器内 DNS 查询流程:
app → 127.0.0.11 (docker 内置 DNS)
│
├── 容器名/db.service? → 查本网络 → 返回 10.10.0.3
├── 外部域名? → 转发给宿主 /etc/resolv.conf
└── 不通? → 失败
自定义网络里只有显式
docker run --name X的容器才会被注册到 DNS。docker-compose起的服务天然都注册了。
跨主机网络(overlay)
单 Docker 引擎下没有"跨主机网络"概念;要在多机间打通,要用 Swarm 模式 + overlay 网络:
docker swarm init
docker network create --driver overlay --attachable my-overlay
# 任何加入 swarm 的节点上的容器,都能加入 my-overlay
多主机 overlay (vxlan):
host1 容器 A (10.0.0.2) host2 容器 B (10.0.0.3)
│ │
▼ ▼
┌─────────┐ vxlan udp 4789 ┌─────────┐
│ vxlan0 │ ──────────────────► │ vxlan0 │
└────┬────┘ └────┬────┘
│ │
eth0 ◄─────── 物理网络 ──────► eth0
简单集群用
docker-compose --scale+ overlay 也行,但生产一般直接上 K8s,CNI(Calico/Cilium)提供更丰富的网络策略和性能。
调试命令
# 看容器实际网络配置
docker inspect <container> | jq '.[0].NetworkSettings'
# 看所有网络
docker network ls
docker network inspect <net_name>
# 看 veth pair
ip link show | grep veth
# 一端 vethXXX 在容器内(容器里叫 eth0),另一端在 docker0
# 抓包(用 netshoot 镜像)
docker run -it --rm --network container:<target> nicolaka/netshoot tcpdump -i eth0
# 看 iptables 规则(bridge 模式的关键)
sudo iptables -t nat -L -n | grep -A2 'Chain DOCKER'
# 看路由
docker exec <container> ip route
常见坑
Mac/Win 上 --network host 不"host"
Docker Desktop 用 LinuxKit VM,host 网络指的是 VM 内部,不是你笔记本。要从笔记本访问 VM 里的容器,照样 -p 映射。
默认 bridge 容器名不通
现象:
docker run --name web -d nginx
docker run --name db -d redis
docker exec web ping db
→ ping: bad address 'db'
解决:自己创建网络,docker run --network mynet
端口映射被宿占用
docker run -p 80:80 nginx
# Error: bind: address already in use
# 解决:换端口,或用 host 网络
docker run --network host nginx
# 但要确认宿主 80 没被别的进程占
iptables 规则被冲掉
有些 systemd 服务(firewalld、ufw)会清空自定义链,docker 的 NAT 规则被冲掉后容器上不了网:
# 临时恢复
sudo systemctl restart docker
# 根治:
# 1. ufw 改 /etc/ufw/ufw.conf 里 manage_iptables=false
# 2. firewalld 加 docker0 到 trusted zone