4_Docker网络实战指南
Docker 网络是容器通信的基础设施层。选错网络模式可能带来安全暴露、性能损耗或排障噩梦。本文从 Linux 内核的网络原语出发,逐层拆解 Docker 网络的实现机制,覆盖单机网络、跨主机网络和生产实战。
1. 全景架构
1.1 容器网络模型(CNM)
如果没有统一的网络模型,每创建一个容器就要手动配置 IP、手动建虚拟网卡、手动写路由规则。10 个容器重复 10 次,容器一重启 IP 就变,之前的配置全部作废。
CNM(Container Network Model)是 Docker 的网络管理规范,由 libnetwork 库实现,把复杂的网络配置拆成三个组件:
| 概念 | 类比 | 作用 | 对应 Linux 原语 |
|---|---|---|---|
| Sandbox | 独立的房间 | 容器的独立网络栈(网卡、路由表、DNS),互不干扰 | Network Namespace |
| Endpoint | 房间的网口 | 连接 Sandbox 到 Network 的虚拟接口 | veth pair 的一端 |
| Network | 交换机 | 一组可互通的 Endpoint,接到同一个 Network 的容器才能通信 | Linux Bridge / VXLAN |
一个 Sandbox 可以拥有多个 Endpoint,接入多个 Network——这是容器多网卡能力的基础。
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
subgraph S1 [Sandbox: 容器 A]
E1["Endpoint 1"]
E2["Endpoint 2"]
end
subgraph S2 [Sandbox: 容器 B]
E3["Endpoint 3"]
end
subgraph N1 [Network: frontend]
direction LR
end
subgraph N2 [Network: backend]
direction LR
end
E1 -->|"接入"| N1
E3 -->|"接入"| N1
E2 -->|"接入"| N2
classDef sandbox fill:#3B82F6,stroke:#2563EB,color:#fff
classDef endpoint fill:#10B981,stroke:#059669,color:#fff
classDef network fill:#F59E0B,stroke:#D97706,color:#fff
class S1,S2 sandbox
class E1,E2,E3 endpoint
class N1,N2 network1.2 五种网络驱动
Docker 提供 5 种网络驱动,覆盖不同使用场景:
| 驱动 | 一句话理解 | 适用场景 | 性能 | 隔离性 |
|---|---|---|---|---|
| bridge | 容器通过虚拟交换机通信,出网走 NAT | 单机多容器(最常用) | 中 | 高 |
| host | 容器直接用宿主机网络栈,无隔离 | 监控 Agent、高吞吐代理 | 最高 | 无 |
| overlay | 宿主机之间建隧道,跨机器通信 | Swarm 集群跨主机 | 中低 | 高 |
| macvlan | 容器直接出现在物理网络上,有独立 IP | 需要固定 IP 对接的旧系统 | 高 | 高 |
| none | 完全断网,只有 localhost | 安全沙箱、离线计算 | N/A | 完全隔离 |
不知道选哪个?按这个决策树走:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
flowchart TD
Q1{"容器需要访问网络吗?"}
Q2{"容器在多台机器上?"}
Q3{"对网络延迟极端敏感?"}
Q4{"需要容器拥有物理网络独立 IP?"}
Q1 -->|"不需要"| NONE["none: 完全隔离"]
Q1 -->|"需要"| Q2
Q2 -->|"是,跨主机"| OVERLAY["overlay: 自动建隧道"]
Q2 -->|"否,单机"| Q3
Q3 -->|"是"| HOST["host: 零 NAT 开销"]
Q3 -->|"否"| Q4
Q4 -->|"是"| MACVLAN["macvlan: 直连物理网络"]
Q4 -->|"否"| BRIDGE["bridge(自定义): 最通用"]
classDef decision fill:#3B82F6,stroke:#2563EB,color:#fff
classDef result fill:#10B981,stroke:#059669,color:#fff
class Q1,Q2,Q3,Q4 decision
class NONE,OVERLAY,HOST,MACVLAN,BRIDGE result五种驱动中 Bridge 覆盖了绝大多数场景,下面重点拆解它的实现机制。
2. Bridge 网络
Bridge 是 Docker 默认且最常用的网络模式。理解它就是理解 Docker 单机网络的全部。
2.1 数据包流转路径
一个容器发出 HTTP 请求到外网,数据包经历以下路径:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
sequenceDiagram
participant App as "容器内应用"
participant eth0_c as "容器 eth0 (veth 内端)"
participant veth_h as "宿主机 vethXXX (veth 外端)"
participant br0 as "docker0 网桥"
participant ipt as "iptables"
participant eth0_h as "宿主机 eth0"
participant Internet as "外部网络"
App->>eth0_c: ① 应用发包 src: 172.17.0.2
eth0_c->>veth_h: ② veth pair 直通到宿主机侧
veth_h->>br0: ③ 网桥收包,查 MAC 转发表
br0->>ipt: ④ 进入 iptables FORWARD 链
ipt->>ipt: ⑤ POSTROUTING MASQUERADE (SNAT)
ipt->>eth0_h: ⑥ 路由到出口网卡
eth0_h->>Internet: ⑦ 发往外网返回路径是逆过程:外网响应到达宿主机 eth0 → conntrack 表匹配原始连接 → DNAT 还原目标为容器 IP → 经 docker0 网桥 → 通过 veth pair 回到容器。
2.2 默认 Bridge Vs 自定义 Bridge
| 维度 | 默认 bridge (docker0) | 自定义 bridge |
|---|---|---|
| DNS 解析 | 不支持容器名解析,只能用 IP 或 --link(已废弃) | 内置 DNS(127.0.0.11),自动按容器名解析 |
| 隔离性 | 所有未指定网络的容器共享同一网桥 | 按网络分组,不同网络的容器默认互不可达 |
| 子网配置 | 不可自定义 | 支持自定义子网、IP 范围、网关 |
| 热插拔 | 不支持运行中连接/断开 | 支持 docker network connect/disconnect |
默认 bridge 的 DNS 缺失是生产事故高发区。容器重启后 IP 变化,硬编码 IP 通信会导致服务间连接断裂。自定义 bridge 通过内置 DNS 解决这个问题。
1 | # 创建自定义网络 |
2.3 手动模拟 Bridge 网络
docker network create 一条命令就搞定了,但底层到底发生了什么?手动走一遍,遇到网络问题就知道该查哪里。
1 | # === 第一步:创建网桥(虚拟交换机) === |
这 6 步与 Docker 自动行为的对照:
| 手动操作 | Docker 自动完成 | 对应命令 |
|---|---|---|
| 创建网桥 | 启动时创建 docker0 | docker network create |
| 创建 Namespace | 每个容器自带 | docker run |
| 创建 veth pair 并连接 | 容器启动时创建 | docker run --network |
| 配置 IP 和路由 | IPAM 自动分配 | 自动 |
| 配置 NAT | 自动写 iptables 规则 | -p 端口映射 |
理解了 Bridge 的底层实现,接下来看 Docker 在 iptables 中写了哪些规则。
2.4 Iptables 规则链
Docker 的端口映射(-p 8080:80)本质是在 iptables 中写转发规则。
核心概念:
- DNAT(目标地址转换):外部请求目标从 “ 宿主机:8080” 改写为 “ 容器 IP:80”
- SNAT/MASQUERADE(源地址转换):容器出网时,源地址从容器 IP 改为宿主机 IP
以 docker run -d -p 8080:80 --name web nginx 为例,Docker 写入以下规则:
1 | # 查看 NAT 表规则 |
完整数据流:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
flowchart LR
subgraph IN [外部请求进入]
direction LR
A1["PREROUTING"] --> A2["DOCKER 链: DNAT 改写目标地址"] --> A3["FORWARD: 允许转发"] --> A4["到达容器"]
end
subgraph OUT [容器响应返回]
direction LR
B1["容器发包"] --> B2["POSTROUTING: MASQUERADE 改写源地址"] --> B3["发往外部"]
end
classDef chain fill:#3B82F6,stroke:#2563EB,color:#fff
classDef action fill:#10B981,stroke:#059669,color:#fff
class A1,A3,B2 chain
class A2,A4,B1,B3 action排障要点:端口映射不通时,按顺序检查——(1) iptables -t nat -L -n 确认 DNAT 规则存在;(2) iptables -L FORWARD -n 确认 FORWARD 链允许转发;(3) 检查 firewalld / ufw 是否覆盖了 Docker 规则。
2.5 端口映射:-p Vs -P Vs EXPOSE
| 命令/指令 | 作用 | 示例 |
|---|---|---|
EXPOSE 80 (Dockerfile) | 纯文档声明,不打开任何端口 | 告诉使用者 “ 我监听 80” |
-p 8080:80 | 精确映射:宿主机 8080 → 容器 80 | docker run -p 8080:80 nginx |
-p 127.0.0.1:8080:80 | 绑定到特定 IP,仅本机可访问 | 生产环境推荐 |
-P | 自动映射所有 EXPOSE 端口到随机高端口 | 快速测试 |
-p 80:80 默认绑定 0.0.0.0,所有网络接口(含公网 IP)均可访问。生产环境务必绑定具体 IP。
Bridge 网络解决了单机容器通信问题,但 Host、None、Macvlan 在特定场景下不可替代。
3. Host、None 与 Macvlan
3.1 Host 网络
容器直接共享宿主机的 Network Namespace,没有独立网络栈,没有 veth pair,没有 NAT。
场景:Prometheus Node Exporter 需要采集宿主机的 CPU、内存、网络指标。Bridge 模式下,容器看到的网卡是 veth 虚拟网卡,采集到的是容器自身数据而非宿主机数据。
1 | docker run -d --network host --name node-exporter \ |
代价:完全丧失网络隔离。容器端口直接占用宿主机端口,两个容器不能监听同一端口。
性能参考:Bridge 的 NAT 转发引入约 2-5% 的额外延迟。对每秒百万级请求的场景有意义;大多数 Web 应用用 Bridge 足够。
3.2 None 网络
容器拥有独立的 Network Namespace,但仅有 loopback 接口——彻底断网。
场景:银行风控模型处理客户征信数据,监管要求数据不能有任何网络外传的可能。即使容器被攻破,攻击者也无法将数据发送出去。
1 | # 数据通过卷挂载进去,结果通过卷取出来,全程无网络 |
适用场景:纯 CPU 计算、离线数据处理、安全沙箱。也可手动用 ip link 为 None 容器绑定自定义网络接口,实现完全自定义的网络拓扑。
3.3 Macvlan 与 IPvlan
两者都让容器直接出现在物理网络上,拥有独立 IP,绕过 NAT。
场景:某制造企业有一套 10 年历史的 ERP 系统,IP 地址(192.168.1.100)硬编码在几十台设备配置中。容器化后需要保持原 IP 不变。Bridge 模式做不到——容器在 172.17.x.x 子网里,必须走 NAT。
1 | # 创建 Macvlan 网络 |
Macvlan 与 IPvlan 的工作原理差异:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
flowchart TD
subgraph MAC [Macvlan 模式]
direction TB
PHY1["物理网卡 eth0"] --> M1["子接口 MAC-A: 容器 1"]
PHY1 --> M2["子接口 MAC-B: 容器 2"]
M1 -.- NOTE1["每个容器独立 MAC"]
end
subgraph IP [IPvlan L3 模式]
direction TB
PHY2["物理网卡 eth0"] --> I1["子接口: 容器 1"]
PHY2 --> I2["子接口: 容器 2"]
I1 -.- NOTE2["共享父接口 MAC, 三层路由"]
end
classDef phy fill:#3B82F6,stroke:#2563EB,color:#fff
classDef container fill:#10B981,stroke:#059669,color:#fff
classDef note fill:#F59E0B,stroke:#D97706,color:#fff
class PHY1,PHY2 phy
class M1,M2,I1,I2 container
class NOTE1,NOTE2 note| 维度 | Macvlan | IPvlan |
|---|---|---|
| MAC 地址 | 每个容器独立 MAC | 共享父接口 MAC |
| 交换机兼容 | 需要混杂模式,部分交换机限制 MAC 数量 | 无需混杂模式 |
| 云平台 | 几乎不可用(AWS/GCP 禁用混杂模式) | 兼容性更好 |
| DHCP | 每个容器可独立获取 DHCP 地址 | 共享 MAC,DHCP 受限 |
| 宿主机通信 | 宿主机无法直接访问容器(内核限制) | L3 模式下宿主机可达 |
Macvlan 的 “ 宿主机无法访问容器 “ 是 Linux 内核限制:同一物理接口的不同 MAC 地址之间不能直接通信。解决方法是在宿主机上额外创建 Macvlan 子接口,但会增加配置复杂度。
选型建议:需要物理网络独立 IP → 优先 IPvlan(兼容性好)。仅在需要独立 DHCP 地址且交换机支持时用 Macvlan。
单机网络模式掌握后,多容器编排是下一个实际问题。
4. Docker Compose 多容器网络
Docker Compose 自动为每个项目创建独立的 bridge 网络,项目内容器通过服务名互通,项目间天然隔离。
4.1 微服务网络拓扑
1 | # docker-compose.yml |
网络拓扑可视化:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
flowchart TD
EXT["外部请求 :80"] --> GW["api-gateway"]
subgraph FN [frontend 网络]
GW
US["user-service"]
OS["order-service"]
end
subgraph BN [backend 网络 - internal]
US2["user-service"]
OS2["order-service"]
PG["postgres"]
RD["redis"]
end
US -.->|"同一容器, 双网卡"| US2
OS -.->|"同一容器, 双网卡"| OS2
classDef external fill:#EF4444,stroke:#DC2626,color:#fff
classDef frontend fill:#3B82F6,stroke:#2563EB,color:#fff
classDef backend fill:#10B981,stroke:#059669,color:#fff
classDef bridge fill:#F59E0B,stroke:#D97706,color:#fff
class EXT external
class GW,US,OS frontend
class US2,OS2,PG,RD backend设计要点:
- api-gateway 只接入 frontend,不能直连数据库
- postgres 和 redis 在
internal: true的 backend 中,无法访问外网 - user-service 和 order-service 同时接入两个网络,充当桥梁
- 端口映射绑定
127.0.0.1,前面再放反向代理
4.2 验证网络隔离
1 | # 启动 |
5. 网络排障
网络问题排查的核心思路:从最近的地方开始,一层一层往外推。
5.1 分层排障流程
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'lineColor': '#60A5FA'}}}%%
flowchart TD
START["容器网络不通"] --> Q1{"容器内能 ping 自己吗?"}
Q1 -->|"不能"| FIX1["容器网络栈异常,重启容器"]
Q1 -->|"能"| Q2{"能 ping 对方容器名吗?"}
Q2 -->|"DNS 解析失败"| Q3{"在同一自定义网络吗?"}
Q3 -->|"不在"| FIX2["加入同一网络"]
Q3 -->|"在"| FIX3["检查是否用了默认 bridge,改用自定义 bridge"]
Q2 -->|"能 ping 通"| Q4{"应用能连通吗?"}
Q4 -->|"不能"| FIX4["检查服务是否监听正确端口"]
Q4 -->|"能"| Q5{"外部能访问吗?"}
Q5 -->|"不能"| FIX5["检查 iptables 和防火墙"]
Q5 -->|"能"| FIX6["网络正常,问题在应用层"]
classDef question fill:#3B82F6,stroke:#2563EB,color:#fff
classDef fix fill:#10B981,stroke:#059669,color:#fff
class Q1,Q2,Q3,Q4,Q5 question
class FIX1,FIX2,FIX3,FIX4,FIX5,FIX6 fix5.2 排障工具速查
| 排查目标 | 使用时机 | 工具 | 命令 |
|---|---|---|---|
| 容器网卡和 IP | 确认容器自身网络正常 | ip, ss | docker exec <c> ip addr |
| 容器间通信 | 两容器互相 ping 不通 | ping, curl | docker exec <c1> ping <c2> |
| DNS 解析 | 容器名报 “bad address” | nslookup | docker exec <c> nslookup db |
| 网桥状态 | 怀疑网桥有问题 | bridge | bridge link show |
| iptables 规则 | 端口映射不通 | iptables | iptables -t nat -L -n -v |
| 抓包分析 | 需要看具体包内容 | tcpdump | tcpdump -i docker0 -n port 80 |
| Docker 网络详情 | 查容器在哪个网络 | docker | docker network inspect <net> |
5.3 容器访问宿主机
从容器内访问宿主机服务(如本地数据库),优先使用 host.docker.internal:
1 | # 容器内访问宿主机的 MySQL |
- Docker Desktop (Mac/Windows) 默认支持
- Linux 上 Docker 20.10+ 需要加
--add-host=host.docker.internal:host-gateway
不要用 172.17.0.1——该地址仅在默认 bridge 网络中有效,自定义网络的网关地址不同。
6. 理解检验
概念辨析
默认 bridge 和自定义 bridge 的关键区别是什么?技术根因是什么?
自定义 bridge 内置 DNS 服务(监听 127.0.0.11:53),通过 iptables DNAT 规则拦截容器的 DNS 请求并代为解析。默认 bridge 没有这层 iptables 规则,因此只能通过 IP 或已废弃的
--link通信。VXLAN 封装为什么用 UDP 而不用 TCP?
VXLAN 传输的是内层的完整以太网帧,内层协议可能已经是 TCP。如果外层再用 TCP,等于做两层可靠传输(TCP over TCP),重传风暴会导致严重的性能塌方。UDP 只负责 “ 送到 “,可靠性交给内层协议处理。
场景设计
三个微服务(API 网关 + 用户服务 + 订单服务)部署在同一台机器,API 网关需对外暴露 80 端口,其他仅内部通信。如何设计网络?
- 创建两个自定义 bridge:
frontend(API 网关 + 用户服务 + 订单服务)和backend(用户服务 + 订单服务 + DB) - API 网关映射
-p 127.0.0.1:80:80,只接入 frontend - DB 放在
internal: true的 backend 中,API 网关无法直连 DB,实现最小权限原则
实践验证
1 | # 1. 创建自定义网络 |
完成标准:第 2 步 DNS 解析成功,第 3 步隔离生效,第 4 步热插拔后互通。