前言

容器一多,问题就从"跑起来"变成"怎么连":容器之间怎么互访?端口映射的原理是什么?为什么生产推荐自定义网络?这篇把 Docker 网络讲透。

一、五种网络模式总览

docker network ls
# NETWORK ID   NAME      DRIVER
# xxx          bridge    bridge      # 默认网桥 docker0
# xxx          host      host        # 共享宿主机网络栈
# xxx          none      null        # 无网络
模式 命令 特点 场景
bridge(默认) --network bridge 独立网络栈 + NAT 绝大多数场景
host --network host 直接用宿主机网络,无隔离 追求网络性能
container --network container:xx 复用另一容器的网络栈 K8s Pod 的雏形
none --network none 只有 lo,完全隔离 安全敏感/离线任务
自定义 bridge docker network create 支持 DNS、隔离性好 生产推荐

二、bridge:默认模式与端口映射原理

docker run -d --name web -p 8080:80 nginx

内部发生了什么:

宿主机                          容器 web
┌────────────────────┐        ┌──────────────┐
│ eth0: 10.0.4.13    │        │ eth0:        │
│  │                 │        │  172.17.0.2  │
│ docker0: 172.17.0.1│◄──────►│  (veth pair) │
│  │                 │        │  gw=172.17.0.1│
│ iptables DNAT      │        └──────────────┘
│  :8080 → 172.17.0.2:80
└────────────────────┘
  1. 安装 Docker 时创建了虚拟网桥 docker0(172.17.0.0/16)
  2. 每个容器拿到一张虚拟网卡(veth pair 一端在容器、一端挂网桥)
  3. -p 8080:80 本质是一条 iptables DNAT 规则:
iptables -t nat -L DOCKER -n
# DNAT tcp dpt:8080 to:172.17.0.2:80
  1. 容器出网靠 SNAT/MASQUERADE,源地址被改写成宿主机 IP
# 在容器里验证
docker exec web ip addr          # eth0 172.17.0.2/16
docker exec web ip route         # default via 172.17.0.1

IP 不固定——容器重建后 IP 会变,这就是要靠自定义网络 DNS 的原因。

三、host 模式:零开销,全暴露

docker run -d --network host nginx:alpine
  • 容器直接用宿主机的 eth0/lo,没有 NAT、没有端口映射(-p 会被忽略)
  • 性能 = 裸机进程(高吞吐网络应用收益明显)
  • 代价:端口冲突风险——宿主机 80 被占,容器 nginx 起不来
  • 排障口子:ss -tlnp 看到宿主机端口,实际是容器在听
# 典型用法:监控 agent、需要高性能转发的组件
docker run -d --network host --name node-exporter prom/node-exporter

四、container 模式与 none

# container 模式: 共享另一个容器的网络命名空间(IP/端口/hosts 全共享)
docker run -d --name app myapp
docker run -d --name sidecar --network container:app busybox sleep 3600
# sidecar 里 localhost:8080 就是 app 的 8080 —— K8s Pod 内多容器互通的原理

# none 模式: 只有 lo
docker run --rm --network none busybox ip addr
# 1: lo ... (没有 eth0)

五、自定义网络:生产标配

默认 docker0 网络的痛点:容器间只能靠 IP 互访(没有 DNS),且不同网络完全隔离。

# 创建
docker network create --driver bridge --subnet 172.20.0.0/16 mynet

# 挂到网络上跑
docker run -d --name db --network mynet -e MYSQL_ROOT_PASSWORD=pass mysql:8.0
docker run -d --name app --network mynet myapp

自定义网络自带 DNS:

docker exec app ping -c2 db          # 直接用容器名!
docker exec app getent hosts db      # 172.20.0.2

# 应用配置里写 db:3306 即可, 不用管 IP

多网络隔离:

docker network create frontend
docker network create backend
docker run -d --name web  --network frontend nginx
docker run -d --name api  --network frontend --network backend myapi
docker run -d --name db   --network backend  mysql:8.0
# web ↔ api 通, api ↔ db 通, web ↔ db 不通(按需连通)
docker network inspect mynet        # 网络详情: 容器列表/IP/网关
docker network connect mynet web    # 给运行中的容器再接一个网络
docker network rm mynet             # 删除(需先断开所有容器)

六、容器与外界通信全景

容器 → 外网        ✔ SNAT 改写源地址(bridge 模式开箱即用)
外网 → 容器        ✔ -p 端口映射(DNAT)
容器 → 宿主机      ✔ 网关地址: docker0 的 IP(172.17.0.1)或 host.docker.internal
宿主机 → 容器      ✔ 直接访问容器 IP(bridge 模式同宿主机可直连 172.17.0.2)
容器 ↔ 容器        ✔ 自定义网络 + DNS 容器名互访

一个容易被忽略的点——容器访问宿主机上的服务:

# 宿主机服务必须监听 0.0.0.0 或 docker0 IP, 只听 127.0.0.1 则容器连不上
docker exec app curl http://172.17.0.1:9090/metrics

七、常见坑速查

现象 原因
容器间用容器名连不上 在默认 docker0 网络上(无 DNS),换自定义网络
-p 映射后外部不通 安全组/防火墙;或宿主机端口被占
容器重启后 IP 变了 正常现象,永远用 DNS 名不用 IP
host 模式容器起不来 端口与宿主机冲突
172.17 网段与公司内网冲突 --subnet 自定义网段,或改 daemon.json 的 bip
删不掉网络 还有容器连着,docker network disconnect

小结

需求 方案
日常单容器对外服务 bridge + -p
多容器互通 自定义网络 + 容器名 DNS
网络性能敏感 host 模式(注意端口冲突)
按需隔离分组 多网络 + 按容器挂载
理解底层 veth pair + docker0 + iptables NAT

本文是「云原生」系列第 5 篇。