Docker 教程

Docker 网络模型

网络允许独立的容器之间、容器与宿主机之间,以及容器与外部网络之间进行通信。如果没有正确的网络配置,容器将处于完全隔离的状态,绝大多数多服务应用将无法运作。

理解 Docker 是如何管理网络,对于设计健壮、可扩展和安全的容器部署架构的关键。它决定了你的 Web 服务器容器如何与数据库容器对话,你的应用如何响应客户端请求,以及分布式系统中的服务如何相互发现和交互。

本章将为你揭开 Docker 核心网络模型的神秘面纱,重点讲解 Bridge(桥接)Host(主机)Overlay(覆盖) 网络。无论是单机开发环境还是复杂的多节点生产环境,这三种模式都是不可或缺的。

1. Docker 网络核心概念

Docker 提供了几种不同的“网络驱动(Network Drivers)”来满足各种用例和部署场景。每种驱动都为容器通信提供了一种独特的方式,在隔离性、性能和复杂性方面各有权衡。理解这些基础驱动是掌握容器化应用交互方式的核心。

1.1 Bridge 网络 (桥接模式)

Bridge 网络是 Docker 容器最常见、也是默认的网络驱动。当你安装 Docker 时,系统会自动创建一个名为 bridge 的默认桥接网络。除非你指定了其他网络驱动,否则新创建的容器都会自动连接到这个默认网络。

原理解析:
Bridge 网络就像你宿主机上的一个虚拟交换机。Docker 创建了一个虚拟网桥(在 Linux 上通常叫 docker0),它作为一个软件网桥,负责在连接到它的容器之间路由网络流量。在这个桥接网络上的每个容器,都会在这个网桥的子网内获得一个自己的私有 IP 地址。

同一桥接网络上的容器可以使用它们的 IP 地址或容器名称相互通信(Docker 在自定义的 Bridge 网络中提供了内置的 DNS 服务用于名称解析)。为了与外部世界通信(或让外部流量访问容器),Docker 使用了网络地址转换 (NAT) 和端口映射(正如我们在上一章“暴露端口”中所学的那样)。

它是如何工作的:

  1. 当 Docker 启动时,它在宿主机上创建一个虚拟网桥接口(例如 docker0)。
  2. 每次在 bridge 网络上启动一个容器时,Docker 都会创建一对虚拟以太网接口(veth pair)。这一对接口的一端连接到容器的网络命名空间(在容器内显示为 eth0),另一端连接到宿主机的 docker0 网桥上。
  3. Docker 从网桥的子网中分配一个 IP 地址给容器的 eth0 接口。
  4. Docker 在宿主机上配置 iptables 规则,为容器发出的连接启用 NAT,并处理外部进来的端口映射。

真实业务场景:

  • 场景 1:Web 应用架构
    • 需求: 你有一个前端 web_app 容器(如 Nginx 提供静态文件并作为反向代理)和一个后端 api_server 容器(如 Python Flask 提供动态数据)。它们需要互相通信。
    • 实现: 默认情况下,它们都连接到 bridge 网络。web_app 可以把 80 端口暴露给宿主机,而 api_server 只需要在内部保留 5000 端口。web_app 然后可以使用内部 IP 或容器名将请求代理给 api_server
    • 优势: 提供了容器与宿主机之间的隔离,允许容器安全通信,而无需将后端直接暴露给公网。
  • 场景 2:开发环境搭建
    • 需求: 开发者需要在本地运行特定版本的 PostgreSQL 数据库和 Redis 缓存,以配合代码开发。
    • 实现: 启动 postgresredis 容器,它们默认在 bridge 网络上。宿主机上的本地代码通过端口映射(如 localhost:5432)连接它们。如果开发的应用也被打包成了容器,它们可以直接通过 postgres:5432 进行内部连接。
    • 优势: 确保了所有开发者环境的一致性,且与本机其他服务隔离。

代码示例 (审查默认 Bridge 网络):

# 显示所有 Docker 网络列表
docker network ls

# 预期的输出中会包含 'bridge'
# NETWORK ID     NAME      DRIVER    SCOPE
# ...
# d6c9d5f0b12a   bridge    bridge    local
# ...

# 检查默认的 'bridge' 网络,查看其配置和已连接的容器
docker network inspect bridge

# 输出示例 (为了简洁已裁剪,实际输出很长):
# [
#     {
#         "Name": "bridge",
#         "IPAM": {
#             "Config": [
#                 {
#                     "Subnet": "172.17.0.0/16", # Bridge 的默认子网
#                     "Gateway": "172.17.0.1"    # Bridge 的网关 IP
#                 }
#             ]
#         },
#         "Containers": {}, # 在你运行容器前,这里是空的
#         "Options": {
#             "com.docker.network.bridge.name": "docker0" # 实际的宿主机网桥接口名称
#         }
#     }
# ]

# 现在,运行一个简单的 Nginx 容器,将宿主机的 8080 映射到容器的 80
docker run -d -p 8080:80 --name mynginx nginx

# 再次检查 'bridge' 网络,查看挂载的容器
docker network inspect bridge

# 你现在会在 "Containers" 下看到你的 'mynginx' 容器:
# ...
#         "Containers": {
#             "a1b2c...": {
#                 "Name": "mynginx",
#                 "IPv4Address": "172.17.0.2/16", # 分配给 Nginx 容器的内部 IP
#             }
#         },
# ...

1.2 Host 网络 (主机模式)

顾名思义,Host 网络驱动移除了容器和 Docker 宿主机之间的网络隔离。当你使用 host 网络模式时,容器直接共享宿主机的网络命名空间。这意味着容器直接使用宿主机的 IP 地址和网络接口。

原理解析:
配置了 host 网络驱动的容器不再拥有自己私有的、带虚拟接口的网络栈。相反,它直接使用宿主机的接口、IP 地址和端口范围。这实际上意味着,容器内运行的服务可以直接通过宿主机的 IP 和端口被访问,完全不需要任何 NAT 或端口映射

它是如何工作的:

  • Docker 本质上告诉容器去“借用”宿主机的网络栈。
  • 容器内的 localhost 就是宿主机的 localhost
  • 容器内应用打开的任何端口都会直接绑定到宿主机的接口上。如果宿主机上已经有个 Web 服务器占用了 80 端口,而一个使用 host 模式的容器也试图监听 80 端口,就会发生端口冲突。

真实业务场景:

  • 场景 1:高性能网络应用
    • 需求: 某些应用需要极低延迟的网络通信,或者需要处理海量的网络流量(如专用的网络监控工具或高性能代理)。
    • 实现: 使用 --network host 运行该容器,可以让它绕过 Docker 网络栈的虚拟网桥开销,直接与物理网卡通信,从而获得接近裸机的网络性能。
  • 场景 2:访问宿主机绑定的特定服务
    • 需求: 你想让容器连接到一个只在宿主机 127.0.0.1 上监听的本地服务(比如一个本地内存 Redis)。
    • 实现: 使用 host 模式,容器的 localhost 变成了宿主机的 localhost,因此可以直接连接 127.0.0.1:端口号。

代码示例 (以 Host 模式运行容器):

# 使用 host 网络运行一个 Nginx 容器。
# 注意我们没有使用 -p (端口映射),因为容器的 80 端口直接就是宿主机的 80 端口。
docker run -d --network host --name mynginx-host nginx

# 如果你宿主机的 80 端口已经被占用了,这个命令会报错 "port already in use"。
# 如果运行成功,你可以直接通过宿主机的 IP 或 localhost 访问 Nginx。
# 示例: curl http://localhost/

# 你可以从容器内部检查网络配置:
docker run --network host --rm alpine/git ip a

# 预期的输出会直接列出宿主机的所有物理网卡 (eth0, wlan0 等) 和 IP 地址,
# 而不是容器自己的虚拟网卡。

重要提示(针对 Mac/Windows 用户): 在原生的 Linux 系统上,--network host 意味着容器完全使用宿主机网络。但在 Docker Desktop (macOS/Windows) 上,Docker 运行在一个轻量级虚拟机 (VM) 中。在这种情况下,--network host 意味着容器共享的是 Docker Desktop VM 的网络栈,而不是你 Mac/Windows 宿主机本身的物理网络。这是一个非常重要的区别。

1.3 Overlay 网络 (覆盖模式)

Overlay 网络专为跨越多个 Docker 宿主机运行的分布式应用而设计。它们使得运行在不同 Docker 守护进程上的容器能够无缝通信,就好像它们都在同一个本地局域网中一样。Overlay 网络主要用于 Docker Swarm 模式或 Kubernetes 等容器编排工具中。

原理解析:
不同于被限制在单台宿主机的 bridge 网络,overlay 网络跨越多个宿主机。它通过使用 VXLAN (虚拟可扩展局域网) 等技术将容器流量进行封装来实现这一点。当主机 A 上的容器想与主机 B 上的容器通信时,overlay 网络驱动会处理流量的路由和封装,让容器觉得它们是直接相连的。

它是如何工作的(概念层面):

  1. 分布式控制平面: 在多主机环境(如 Docker Swarm)中,一个分布式的键值存储负责管理跨所有节点的网络配置和状态。
  2. VXLAN 隧道: 当不同主机上的容器通信时,它们的流量被打包进 VXLAN 数据包中。这些数据包随后通过底层的物理网络在 Docker 宿主机之间传输。
  3. 封装与解封装: 每个 Docker 主机充当一个 VXLAN 隧道端点。它将发出的流量封装成 VXLAN 数据包发往目标主机;目标主机接收后解封装,将原始流量交给目标容器。

真实业务场景:

  • 场景:微服务架构与集群扩展
    • 需求: 你的公司有多个微服务(如认证服务、产品目录服务、订单处理服务)。为了高可用性,它们被分散部署在多台物理服务器上。
    • 实现: 创建一个 overlay 网络并将所有服务连接进去。主机 A 上的订单服务可以直接通过名称调用主机 B 上的产品服务,overlay 网络确保了无缝通信。
    • 优势: 使得分布式微服务能透明地在整个集群中通信,完全抽象掉了底层的物理网络拓扑。

2. 实战演练与演示

让我们通过运行容器来实际观察这些网络行为。

2.1 演示:默认 Bridge 网络通信

我们将在默认桥接网络上运行两个容器,看看它们如何通信。

1. 运行一个保持存活的 alpine 容器 (发送端):

docker run -d --name alpine-sender alpine sleep 3600

2. 查找它的 IP 地址:

docker network inspect bridge

在 "Containers" 部分找到 alpine-sender 的 IP(假设是 172.17.0.2)。

3. 运行第二个容器 (接收端) 去 Ping 第一个:

# 请将 172.17.0.2 替换为你实际查到的 IP
docker run --rm --name alpine-receiver alpine ping -c 3 172.17.0.2

你应该能看到成功的 ping 响应。这证明了同一 bridge 上的容器可以通过 IP 互通。

4. 清理:docker rm -f alpine-sender alpine-receiver

2.2 演示:Host 网络行为

我们使用 host 网络运行一个 Web 服务器,验证它的直接可访问性。

1. 确保你本机的 80 端口是空闲的。

2. 使用 host 模式运行 Nginx:

docker run -d --network host --name mynginx-host-demo nginx

(注意没有 -p 参数)

3. 验证访问: 打开浏览器访问 http://localhost,你应该能看到 Nginx 的欢迎页。

4. 清理: docker rm -f mynginx-host-demo

2.3 概念推演:Overlay 网络如何工作

假设你有两个 Docker 主机:node1node2

  • node1 上运行 frontend(前端容器)
  • node2 上运行 backend(后端容器)

如果没有 Overlay: frontend 无法直接通过内网 IP 访问到另一台机器上的 backend
有了 Overlay (在 Swarm 集群中):

  1. 创建一个 overlay 网络:docker network create -d overlay my-overlay
  2. 将两个服务都连接到这个网络。
  3. node1 上的 frontend 可以直接向 http://backend:8080 发送请求。Docker 内置 DNS 会将 backend 解析出来,VXLAN 隧道会将请求安全地送达 node2。即使你把 backend 扩容到 3 个实例,网络也会自动处理负载均衡!