4_Docker网络实战指南

Docker 网络是容器通信的基础设施层。六个场景串联核心知识:单容器上网、两容器互通、多服务隔离、跨主机通信,直到特殊需求与生产排障。

1. 场景一:跑第一个容器

没有网络,容器就是一座孤岛——它无法访问外部服务,外部也无法访问它。Docker 的网络模型解决的第一个问题,就是让容器既能上网,又不干扰宿主机和其他容器。

1.1 Docker 怎么给容器配网络

每次启动容器,Docker 自动完成三件事:

概念类比对应 Linux 原语
Sandbox容器独立的网络房间Network Namespace
Endpoint房间里的网口veth pair 的容器侧
Network连接各房间的交换机Linux Bridge

这三个概念构成 CNM(Container Network Model),是 Docker 网络管理的统一规范:

%%{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 network

容器 A 拥有两个 Endpoint,同时接入 frontend 和 backend 两个 Network。一个 Sandbox 可以连接多个 Network——这是容器多网卡能力的基础。

1.2 Bridge 网络:默认模式

不指定 --network 时,容器自动加入默认的 bridge 网络,通过 docker0 虚拟网桥上网。数据包的完整路径:

容器 eth0(172.17.0.2)→ veth pair → docker0 网桥 → iptables POSTROUTING(MASQUERADE,将源地址改为宿主机 IP)→ 宿主机 eth0 → 外网

返回路径由 conntrack 自动处理:响应到达宿主机后,内核查找连接跟踪表,将目标地址还原为容器 IP,再通过 docker0 和 veth pair 送回容器。整个过程对容器透明。

Docker 启动时创建 docker0 网桥,为每个容器创建一对 veth(虚拟网线),一头放进容器的 Network Namespace,一头接到网桥:

1
2
3
4
5
6
# 查看宿主机上 Docker 创建的 veth
ip link show type veth
# 输出类似:vethXXXXXX@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

# 查看 veth 连接到哪个网桥
bridge link show

容器能上网了,但外部无法主动访问它——这是端口映射要解决的问题。

1.3 端口映射:让外部访问容器

-p 参数通过 iptables DNAT 规则将宿主机端口的流量转发到容器端口,本质是把 “ 访问宿主机 8080” 改写为 “ 访问容器 IP:80”。

命令/指令作用示例
EXPOSE 80(Dockerfile)纯文档声明,不打开端口告诉使用者 “ 我监听 80”
-p 8080:80宿主机 8080 → 容器 80docker run -p 8080:80 nginx
-p 127.0.0.1:8080:80仅本机可访问生产环境推荐
-p 8080:80/udpUDP 协议映射DNS、日志收集等场景
-p 8080-8090:8080-8090范围映射需要多端口的服务
-P自动映射所有 EXPOSE 端口到随机高端口快速测试

⚠️ -p 直接操作 iptables,会绕过 UFW / firewalld 等系统防火墙。即使 UFW 只开放了 22 和 443,-p 3306:3306 仍会让 MySQL 暴露在公网。生产环境务必绑定具体 IP(-p 127.0.0.1:8080:80),并在云平台安全组做二次防护。

1.4 本节验收

1
2
3
4
5
6
7
8
9
10
11
12
13
# 启动一个 nginx,映射到本机 8080
docker run -d --name web -p 127.0.0.1:8080:80 nginx

# 验证端口映射生效
curl http://localhost:8080
# 预期:返回 nginx 欢迎页 HTML ✅

# 查看 Docker 写入的 iptables DNAT 规则
iptables -t nat -L DOCKER -n | grep 8080
# 预期:DNAT tcp dpt:8080 to:172.17.0.x:80 ✅

# 清理
docker rm -f web

2. 场景二:两容器互通

容器能上网了,但两个容器怎么通信?直接用 IP?容器重启 IP 就变,硬编码会导致服务间连接断裂——这是生产环境的事故高发区。

2.1 默认 Bridge 的痛点

默认 bridge 网络(docker0)没有内置 DNS。两个容器无法通过容器名互相访问,只能用 IP 或已废弃的 --link

2.2 自定义 Bridge:内置 DNS

自定义 bridge 网络内置了 DNS 服务(监听 127.0.0.11:53),接入同一网络的容器直接用容器名通信:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建自定义网络
docker network create --driver bridge \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
my-app-net

# 启动两个容器
docker run -d --name api --network my-app-net nginx
docker run -d --name db --network my-app-net postgres

# api 容器内直接用容器名访问 db,DNS 自动解析
docker exec api ping -c 2 db # ✅ 无需知道 db 的 IP
维度默认 bridge(docker0)自定义 bridge
DNS 解析不支持容器名,只能用 IP 或废弃的 --link内置 DNS(127.0.0.11),按容器名解析
隔离性所有未指定网络的容器共享同一网桥按网络分组,不同网络默认互不可达
子网配置只能通过 daemon.json 修改,需重启 Docker支持自定义子网、IP 范围、网关

只要有多容器通信需求,始终使用自定义 bridge,不要依赖默认 bridge

2.3 本节验收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
docker network create net-a
docker run -d --name c1 --network net-a alpine sleep 3600
docker run -d --name c2 --network net-a alpine sleep 3600

# 验证 DNS 解析
docker exec c1 ping -c 2 c2
# 预期:ping 通,响应来自 172.x.x.x ✅

# 验证跨网络隔离
docker network create net-b
docker run -d --name c3 --network net-b alpine sleep 3600
docker exec c1 ping -c 2 c3
# 预期:ping: bad address 'c3'(DNS 解析失败,隔离生效)✅

# 验证动态加入网络
docker network connect net-b c1
docker exec c1 ping -c 2 c3
# 预期:ping 通(热插拔成功)✅

# 清理
docker rm -f c1 c2 c3 && docker network rm net-a net-b

3. 场景三:多服务隔离

两容器互通解决了,但真实项目有十几个微服务。所有容器同在一个网络,API 网关能直连数据库——一旦网关被攻破,数据库直接暴露。需要按职责分层隔离。

3.1 Compose 网络分层

Docker Compose 自动为每个项目创建独立的 bridge 网络。在 docker-compose.yml 中声明多个网络,按角色分组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# docker-compose.yml
services:
api-gateway:
image: nginx
ports:
- "127.0.0.1:80:80" # 仅绑定 localhost
networks:
- frontend

user-service:
image: my-user-svc
networks:
- frontend
- backend

order-service:
image: my-order-svc
networks:
- frontend
- backend

postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
networks:
- backend

redis:
image: redis:7
networks:
- backend

networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 禁止容器出外网
%%{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

    class EXT external
    class GW,US,OS frontend
    class US2,OS2,PG,RD backend

设计要点:

  • api-gateway 只接入 frontend,不在 backend 网络中,无法路由到 postgres 和 redis
  • backend 设置 internal: true,容器无法主动发起出网连接,避免数据库层外泄数据
  • user-service 和 order-service 同时接入两个网络,各有 “ 双网卡 “,充当 frontend 与 backend 的桥梁
  • 端口映射绑定 127.0.0.1,外部流量须经过反向代理

3.2 本节验收

1
2
3
4
5
6
7
8
9
10
11
12
13
docker compose up -d

# 验证 api-gateway 无法直连 postgres
docker compose exec api-gateway ping -c 1 postgres
# 预期:ping: bad address 'postgres'(不在同一网络)✅

# 验证 user-service 可以连通两端
docker compose exec user-service ping -c 1 api-gateway # 通 ✅
docker compose exec user-service ping -c 1 postgres # 通 ✅

# 验证 backend 不能出外网
docker compose exec postgres ping -c 1 8.8.8.8
# 预期:超时(internal: true 禁止出网)✅

4. 场景四:跨主机通信

单机问题解决了。容器分布到多台宿主机后,Bridge 网络无能为力——它只是本机的虚拟交换机,无法跨越物理边界。

4.1 Overlay 网络与 VXLAN

Overlay 网络通过 VXLAN 技术在宿主机之间建立隧道:容器发出的完整二层以太网帧,被封装进 UDP 数据包(目标端口 4789),通过宿主机物理网络传输,到达目标宿主机后解封装还原。对容器而言,仿佛和对方在同一个局域网里。

封装使用 UDP 而非 TCP,是因为内层流量可能已经是 TCP。外层再用 TCP 会形成 TCP-over-TCP:两层重传定时器相互干扰,丢包时产生指数级重传风暴。UDP 只负责 “ 送达 “,可靠性交给内层协议处理。

每个 VXLAN 帧增加约 50 字节的头部开销。大量小包场景下需关注 MTU 配置,避免封装后超出 MTU 导致分片。

4.2 Swarm Overlay 实战

1
2
3
4
5
6
7
8
# 初始化 Swarm(在 manager 节点)
docker swarm init

# 创建 overlay 网络
docker network create --driver overlay --attachable my-overlay

# 不同节点上的容器互通
docker service create --name web --network my-overlay --replicas 3 nginx

使用 Kubernetes 时,跨主机网络通常由 CNI 插件(Flannel、Calico 等)替代 Docker 原生 overlay,原理相同但实现更灵活。

4.3 本节验收

1
2
3
4
5
6
7
# 验证 overlay 网络创建成功
docker network ls | grep overlay
# 预期:DRIVER 列显示 overlay ✅

# 验证服务副本就绪
docker service ls
# 预期:web 服务 REPLICAS 显示 3/3 ✅

5. 场景五:特殊需求

前四个场景覆盖了绝大多数情况。剩下 10% 的特殊需求——极致性能、完全断网、固定物理 IP——需要对应的网络模式。先用决策树找准方向:

%%{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

5.1 Host 网络:零 NAT 开销

容器直接共享宿主机的 Network Namespace,没有 veth pair,没有 NAT,也没有网络隔离。

场景:Prometheus Node Exporter 需要采集宿主机真实的 CPU、内存、网络指标。Bridge 模式下,容器看到的是 veth 虚拟网卡,采集到的是容器自身数据而非宿主机数据。

1
2
3
4
5
6
7
docker run -d --network host --name node-exporter \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
prom/node-exporter \
--path.procfs=/host/proc \
--path.sysfs=/host/sys
# 直接监听宿主机 9100 端口,无需 -p

代价:容器端口直接占用宿主机端口,两个容器无法监听同一端口。适用于监控 Agent、高吞吐代理等无需隔离的场景。

5.2 None 网络:完全断网

容器只有 loopback 接口,彻底断网。

场景:金融风控模型处理征信数据,监管要求数据不能有任何网络外传的可能。即使容器被攻破,攻击者也无法将数据发出。

1
2
3
4
5
6
7
8
9
# 数据通过卷挂载进去,结果通过卷取出来,全程无网络
docker run --network none \
-v /data/input:/input:ro \
-v /data/output:/output \
risk-model:latest python run_model.py

# 验证没有网络
docker exec -it <container_id> ip addr
# 只有 lo 接口,没有 eth0 ✅

适用场景:纯 CPU 计算、离线数据处理、安全沙箱。

5.3 Macvlan 与 IPvlan:固定物理网络 IP

两者都让容器直接出现在物理网络上,拥有独立 IP,完全绕过 NAT。

场景:某制造企业的 ERP 系统,IP 地址(192.168.1.100)硬编码在几十台设备配置中,容器化后必须保持原 IP 不变。Bridge 模式无法实现——容器在 172.17.x.x 子网里,必须走 NAT。

1
2
3
4
5
6
7
8
9
10
# 创建 Macvlan 网络
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
erp-net

# 容器使用原 IP
docker run -d --network erp-net --ip 192.168.1.100 --name erp legacy-erp:latest
# 所有设备不改配置,直接访问 192.168.1.100 ✅
维度MacvlanIPvlan
MAC 地址每个容器独立 MAC共享父接口 MAC
交换机兼容需要混杂模式,部分交换机限制 MAC 数量无需混杂模式
云平台标准云虚拟机不可用(AWS EC2/GCP 默认禁用混杂模式)兼容性更好
宿主机通信宿主机无法直接访问容器(内核限制)L3 模式下添加静态路由后可达

需要物理网络独立 IP 时,优先选 IPvlan(兼容性更好);仅在需要独立 DHCP 地址且交换机支持时使用 Macvlan。

5.4 本节验收

1
2
3
4
5
6
7
8
# 验证 Host 网络:容器和宿主机共享端口空间
docker run -d --network host --name test-host nginx
ss -tlnp | grep :80 # 宿主机直接监听 80 ✅
docker rm -f test-host

# 验证 None 网络:容器完全断网
docker run --rm --network none alpine ip addr
# 只有 lo 接口,无 eth0 ✅

6. 排障指南

网络问题排查的核心思路:从最近的地方开始,一层一层往外推。

6.1 分层排查

  1. 容器自身网络正常吗?
    docker exec <c> ip addr 确认有 eth0 和正确 IP。没有则重启容器。

  2. 能 ping 通对方容器名吗?
    docker exec <c1> ping -c 1 <c2>。DNS 解析失败(bad address)→ 跳到第 3 步;能 ping 通 → 跳到第 4 步。

  3. 两个容器在同一自定义网络吗?
    docker network inspect <net> 确认两个容器都在列表中。用的是默认 bridge → 改用自定义 bridge。

  4. 应用层能连通吗?
    docker exec <c1> curl <c2>:<port>。不通 → 检查服务是否绑定 0.0.0.0(而非 127.0.0.1)。

  5. 外部能访问吗?
    iptables -t nat -L DOCKER -n 确认 DNAT 规则存在。规则存在仍不通 → 检查云平台安全组和宿主机防火墙(-p 会绕过 UFW,参见第 1.3 节安全警告)。

  6. 仍然不通?
    tcpdump -i docker0 -n port <port> 抓包,定位包在哪一层丢失。

6.2 工具速查

排查目标工具命令
容器网卡和 IPipdocker exec <c> ip addr
容器间通信ping / curldocker exec <c1> ping <c2>
DNS 解析nslookupdocker exec <c> nslookup db
网桥状态bridgebridge link show
iptables 规则iptablesiptables -t nat -L -n -v
抓包分析tcpdumptcpdump -i docker0 -n port 80
查容器所在网络dockerdocker network inspect <net>

6.3 容器访问宿主机

从容器内访问宿主机服务(如本地数据库),优先使用 host.docker.internal

1
docker run --rm -it mysql mysql -h host.docker.internal -u root -p
环境支持方式
Docker Desktop(Mac/Windows)默认支持,开箱即用
Linux Docker Engine 20.10+需加 --add-host=host.docker.internal:host-gateway
Docker Compose(Linux)extra_hosts 中配置
1
2
3
4
5
services:
app:
image: my-app
extra_hosts:
- "host.docker.internal:host-gateway"

不要用 172.17.0.1——该地址仅在默认 bridge 网络中有效,自定义网络的网关地址不同。