Docker 网络入门:桥接、自定义与主机网络

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

在前面几篇文章中,我们已经能把 Flask + Redis 计数器应用跑起来,也学会了用 Volume 持久化数据。但有一个问题我们一直没有深究:Flask 容器是怎么找到 Redis 容器的?

你在 app.py 里写的 host='redis' 是怎么解析成 Redis 容器的 IP 地址的?如果你同时起了三个 Flask 容器,它们能互相通信吗?默认情况下它们是否彼此隔离?端口映射 -p 8080:80 在网络上到底发生了什么?

这些,就是本篇要回答的问题。在 Docker 的世界里,网络不仅仅是一条"连接线",更是容器之间、容器与外界交互的"神经系统"。理解 Docker 网络模型,是理解 Kubernetes 中 Pod 网络、Service 发现和 Ingress 路由的关键基石。读完这一篇,你会发现 K8s 的网络设计有着和 Docker 一脉相承的逻辑。

一、Docker 网络全景:CNM 模型与驱动类型

在开始动手之前,我们先对 Docker 网络的整体架构有一个鸟瞰式了解,这能帮助你在后续实验中每一步都知道"自己在操作什么"。

Docker 的网络架构遵循 CNM(Container Network Model,容器网络模型) 规范。CNM 定义了三个核心概念:

  • Sandbox(沙盒):容器的网络栈,包含网卡、路由表、DNS 配置等。每个容器至少拥有一个沙盒,同一 Pod 中的多个容器可以共享同一个沙盒(Kubernetes 的设计正是基于此)。

  • Endpoint(端点):容器通过端点连接到网络,一个沙盒可以有多个端点连接到不同的网络。端点是 veth pair(虚拟以太网对)中位于容器侧的一端。

  • Network(网络):一组可以互相通信的端点集合,相当于一个广播域。同一个网络内的容器默认可以直接通信,不同网络间则默认隔离。

从驱动层面来看,Docker 提供了六种网络驱动:

日常开发中,你会频繁接触到的是 bridgehost 两种驱动。Swarm 场景下会用到 overlay ,而 macvlanipvlan 则更多出现在网络拓扑复杂的企业环境中。对于本系列来说,bridge 和 host 已经足够覆盖 90% 的学习和实战场景。

二、默认桥接网络:Docker 自带的"局域网"

2.1 bridge 网络原理

当你启动 Docker 服务后,Docker 会在宿主机上自动创建一个虚拟网桥 docker0。这个 docker0 像一个虚拟交换机,每个连接到这个桥的容器,都会被分配一个 172.17.0.0/16 网段的 IP 地址。

bash 复制代码
宿主机 (192.168.1.100)
    │
    ├── docker0 (172.17.0.1)  ← 虚拟网桥,也是容器访问宿主机的"默认网关"
    │       │
    │       ├── veth-aaa ←→ eth0@容器A (172.17.0.2)
    │       ├── veth-bbb ←→ eth0@容器B (172.17.0.3)
    │       └── veth-ccc ←→ eth0@容器C (172.17.0.4)

Docker 通过 veth pair (虚拟以太网对)技术将容器连接到网桥------veth pair 是一对虚拟网卡,一端插在容器的网络命名空间中(容器内看到的 eth0),另一端插在宿主机的 docker0 网桥上。这样一来,容器 A 发往容器 B 的数据包,就走了一条清晰的路径:A 的 eth0veth-aaadocker0 网桥 → veth-bbb → B 的 eth0

bash 复制代码
# 查看 docker0 网桥
ip addr show docker0

输出示例:

bash 复制代码
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    link/ether 02:42:a1:b2:c3:d4 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0

这里 172.17.0.1 就是 docker0 网桥的 IP,也是所有连接到这个桥的容器的默认网关。容器想访问外网时,数据包会先到这里,然后通过宿主机的 NAT(网络地址转换)转发到物理网络。

2.2 默认 bridge 网络 vs 自定义 bridge 网络

需要注意:Docker 的"默认 bridge 网络"(名称就叫 bridge)和我们自己创建的"自定义 bridge 网络"虽然底层都是 bridge 驱动,但行为有本质区别:

核心结论:默认 bridge 网络仅适合零散测试,任何多容器协作场景都应使用自定义 bridge 网络。 下面动手验证这个结论。

2.3 动手验证:默认 bridge 的局限性

bash 复制代码
# 启动两个测试容器,连接到默认 bridge 网络
docker run -d --name test1 nginx:alpine
docker run -d --name test2 alpine sleep 3600

# 查看它们的 IP
docker inspect test1 --format='{{.NetworkSettings.IPAddress}}'
# 输出:172.17.0.2
docker inspect test2 --format='{{.NetworkSettings.IPAddress}}'
# 输出:172.17.0.3

# 通过 IP 通信:✅ 可以
docker exec test2 ping -c 2 172.17.0.2
# 输出:64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.1ms

# 通过容器名通信:❌ 无法解析
docker exec test2 ping -c 2 test1
# 输出:ping: bad address 'test1'   ← 默认 bridge 不支持 DNS 解析!

结论 :默认 bridge 网络中的容器只能通过 IP 通信,无法使用容器名。这在多容器场景下是致命的------你不可能每次重启容器后手动更新所有 IP 地址。解决这个问题的方法,就是创建自定义 bridge 网络

bash 复制代码
# 清理测试容器
docker rm -f test1 test2

三、自定义 Bridge 网络:容器互联的正确姿势

3.1 创建自定义网络

bash 复制代码
docker network create my-net

此时 Docker 会在宿主机上创建一个新的虚拟网桥 (名称类似 br-<network_id>),与 docker0 完全独立。连接到这个自定义网络的容器,拥有自己的子网(默认 172.18.0.0/16 等),并且内置了 DNS 解析服务------容器可以通过容器名直接访问同网络下的其他容器。

bash 复制代码
# 查看已创建的网络
docker network ls

输出:

bash 复制代码
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f6a7b8c9d0e1   host      host      local
b2c3d4e5f6a7   my-net    bridge    local
c3d4e5f6a7b8   none      null      local

3.2 将容器连接到自定义网络

bash 复制代码
# 在创建容器时指定网络
docker run -d --name web --network my-net nginx:alpine
docker run -d --name db --network my-net alpine sleep 3600

# 通过容器名访问
docker exec db ping -c 2 web
# 输出:64 bytes from web.my-net (172.18.0.2): ...  ← 通过 DNS 解析出 IP!

自定义 bridge 网络内置了一个 嵌入式 DNS 服务器 (地址固定为 127.0.0.11)。当容器通过容器名发起网络请求时,DNS 查询会被转发到这个嵌入式 DNS 服务器,它根据容器名返回对应的 IP 地址。这就是 host='redis' 能够工作的底层机制------在第 10 篇的实战中你会反复用到它。

3.3 容器连接到多个网络

一个容器可以同时连接到多个网络,这在网络隔离场景中非常有用:

bash 复制代码
# 创建两个隔离网络:frontend(前端)和 backend(后端)
docker network create frontend
docker network create backend

# Redis 只属于 backend 网络(不暴露给前端)
docker run -d --name redis --network backend redis:alpine

# Flask 同时连接两个网络(前端入口 + 后端访问 Redis)
docker run -d --name flask-app --network frontend flask-redis-counter:2.0
docker network connect backend flask-app

# 现在:
# redis 和 flask-app 在 backend 中互通 ✅
# web 用户通过 frontend 访问 flask-app ✅
# web 用户无法直接访问 redis ✅(redis 不在 frontend 网络中)

这种"前端-后端"分层隔离模式,正是 Kubernetes 中 NetworkPolicy 的设计前身。

3.4 动态连接与断开网络

bash 复制代码
# 将运行中的容器连接到网络
docker network connect my-net existing-container

# 将容器从网络中断开
docker network disconnect my-net existing-container

小技巧 :当你发现某个容器突然连不上数据库时,先用 docker inspect <container> 查看它的网络列表,确认它是否连接了正确的网络。日常排查中,容器"连不上"的根因有半数以上是网络配置问题。

3.5 自定义子网和 IP

有时候你需要精确控制容器的 IP 地址(比如与外部遗留系统对接):

bash 复制代码
# 创建自定义子网的网络
docker network create \
  --subnet=192.168.100.0/24 \
  --gateway=192.168.100.1 \
  custom-subnet

# 启动容器并指定 IP
docker run -d --name static-web \
  --network custom-subnet \
  --ip 192.168.100.10 \
  nginx:alpine

# 验证 IP
docker inspect static-web --format='{{.NetworkSettings.Networks.custom-subnet.IPAddress}}'
# 输出:192.168.100.10

在生产环境中请谨慎使用手动指定 IP------它增加了配置复杂度和管理负担。Kubernetes 彻底放弃了手动 IP 管理,全部交由网络插件(CNI)自动分配和回收。

四、Host 网络:让容器"裸奔"在宿主机上

4.1 什么是 host 网络?

host 网络模式下,容器不再拥有独立的网络栈,而是与宿主机共享同一个网络命名空间。这意味着:

  • 容器直接使用宿主机的 IP 地址

  • 容器监听某个端口,就等同于宿主机监听该端口(无需 -p 映射)

  • 没有网络隔离,容器可以直接访问宿主机的所有网络接口

bash 复制代码
docker run -d --name host-nginx --network host nginx:alpine
# 此时直接访问宿主机的 80 端口,就是 Nginx 的服务
curl http://localhost:80
# 输出:Nginx 欢迎页

4.2 Host 网络的优缺点

host 网络在特定场景下非常有用,比如运行高性能网络代理(Envoy/Istio Sidecar)、网络监控组件(Prometheus Node Exporter),或者需要从宿主机层面抓取网络数据的工具。但绝大多数应用容器不需要、也不应该使用 host 网络------自定义 bridge 网络在性能开销和隔离安全性之间取得了更好的平衡。

五、None 网络:完全断网

bash 复制代码
docker run -d --name isolated --network none alpine sleep 3600

docker exec isolated ip addr
# 输出:1: lo: <LOOPBACK,UP,LOWER_UP> ...  ← 只有回环接口,没有 eth0

none 网络模式下,容器内除了 lo(回环接口)之外没有任何网络接口。适用于安全计算、离线数据处理或自定义网络插件接管网络的场景。

六、端口映射的底层原理

-p 8080:80 我们用了很多次,但它背后发生了什么?端口映射的底层依赖 Linux iptables 的 NAT(网络地址转换)规则。Docker 在宿主机 iptables 的 DOCKER 链中自动添加 DNAT(目标地址转换)规则。

bash 复制代码
# 启动一个带端口映射的容器
docker run -d --name nginx-test -p 8080:80 nginx:alpine

# 查看 Docker 添加的 iptables 规则
sudo iptables -t nat -L DOCKER -n | grep 8080

输出示例:

bash 复制代码
DNAT       tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpt:8080 to:172.17.0.2:80

这条规则的意思是:任何访问宿主机 8080 端口的 TCP 流量,都将被重写目标地址为 172.17.0.2:80(即容器内 Nginx 的 IP 和端口)。端口映射的本质,就是一条 DNAT 规则。 这也解释了为什么 host 网络不需要端口映射------它直接使用宿主机 IP,数据包走的就是宿主机的标准网络栈,不经过 NAT 转换。

七、排除网络故障:端口冲突排查

端口映射最常见的错误就是端口冲突。两个容器(或宿主机服务)不能同时监听同一个宿主机端口:

bash 复制代码
docker run -d --name web1 -p 8080:80 nginx:alpine   # ✅ 成功
docker run -d --name web2 -p 8080:80 nginx:alpine   # ❌ 失败
# Error: driver failed programming external connectivity on endpoint:
# Bind for 0.0.0.0:8080 failed: port is already allocated

排查步骤:

bash 复制代码
# 1. 查看宿主机哪些端口已被占用
sudo netstat -tlnp | grep 8080
# 或
sudo lsof -i :8080

# 2. 查看 Docker 容器占用了哪些端口
docker ps --format "table {{.Names}}\t{{.Ports}}"

# 3. 更换端口或清理冲突容器
docker rm -f web1
docker run -d --name web2 -p 8080:80 nginx:alpine   # 现在可以了

八、命令速查表

九、本篇总结

这一篇我们系统学习了 Docker 的网络模型:

  • CNM 三要素:Sandbox(容器网络栈)、Endpoint(连接点)、Network(广播域)

  • 六种网络驱动:bridge(默认单机)、host(共享宿主机)、overlay(跨主机)、ipvlan/macvlan(物理网络对接)、none(完全断网)

  • bridge 网络的核心差异 :默认 bridge 无 DNS 解析,自定义 bridge 内置 DNS 服务器(127.0.0.11),是容器名互访的基础

  • 端口映射的底层本质-p 是通过 iptables DNAT 将宿主机端口流量重定向到容器 IP

  • 网络隔离模式:容器可同时连接多个网络,实现前端-后端分层隔离

理解 Docker 的网络模型,等于为 Kubernetes 的学习铺好了路------K8s 的 Pod 共享同一个网络命名空间(类似 host 模式),Service 通过标签选择器和虚拟 IP 实现服务发现(类似自定义 bridge 的 DNS 解析),而 CNI 插件(Calico、Flannel)则是 Docker CNM 模型在集群层面的扩展。

下一篇文章------第 9 篇:Docker 网络进阶:容器间通信与 DNS 解析,我们将深入 DNS 解析的工作原理、容器间通信的全链路数据流,并带你动手搭建一个三层微服务网络。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
用户21816970493012 小时前
golang socket(三) TCP协议 实现聊天功能 TCPConn
后端
Kir1to12 小时前
线程的三种创建方式与生命周期及线程池
后端
Rust研习社12 小时前
MSRV 是什么?一文说清楚
后端·rust·编程语言
XovH12 小时前
Docker 网络进阶:容器间通信与 DNS 解析
后端
月読h12 小时前
Hermes QQbot websocket Problems
后端
Byron__13 小时前
SpringBoot 核心面试知识点(自动配置/启动流程/注解/Starter)
spring boot·后端·面试
程序员cxuan13 小时前
这个插件,直接让 Java 小白秒变资深开发
人工智能·后端·程序员
ZengLiangYi13 小时前
Prompt 工程:让 LLM 输出结构化 JSON
前端·javascript·后端
Oo_行者_oO13 小时前
MyBatis-Plus 字段数学计算封装
后端