【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Python系列文章目录
Go语言系列文章目录
Docker系列文章目录
01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
08-【Docker-Day 8】高手进阶:构建更小、更快、更安全的 Docker 镜像
09-【Docker-Day 9】实战终极指南:手把手教你将 Node.js 应用容器化
10-【Docker-Day 10】容器的“持久化”记忆:深入解析 Docker 数据卷 (Volume)
11-【Docker-Day 11】Docker 绑定挂载 (Bind Mount) 实战:本地代码如何与容器实时同步?
12-【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- Python系列文章目录
- Go语言系列文章目录
- Docker系列文章目录
- 摘要
- 一、Docker 网络模型概览
- 1.1 为什么需要容器网络
- 1.2 Docker 的网络驱动
- 二、深入理解 Bridge 网络
- 2.1 Bridge 网络的工作原理
- 2.1.1 默认的 `docker0` 网桥
- 2.1.2 veth-pair:连接容器与网桥的“网线”
- 2.2 容器间通信
- 2.2.1 通过 IP 地址通信
- 2.2.2 通过容器名通信
- 三、连接外部世界:端口映射
- 3.1 端口映射的原理
- 3.2 两种映射方式:`-p` 与 `-P`
- 3.2.1 `-p`:指定端口映射
- 3.2.2 `-P`:随机端口映射
- 3.3 `iptables` 的幕后工作
- 四、总结
摘要
本文是《Docker 与 Kubernetes 从入门到精通》系列第 12 篇。在前面的文章中,我们已经学会了如何创建和管理容器,并实现了数据的持久化。然而,孤立的容器无法构成强大的应用,它们之间以及与外部世界的通信至关重要。本文将深入探讨 Docker 的默认网络模式——Bridge 网络,系统性地解析其工作原理、容器间通信机制以及如何通过端口映射将容器服务暴露给外部。通过本文,你将彻底理解容器是如何在隔离的环境中构建起一个“社交网络”的。
一、Docker 网络模型概览
在深入 Bridge 网络之前,我们首先需要建立一个关于 Docker 网络的宏观认识。
1.1 为什么需要容器网络
Docker 容器本质上是宿主机上的一个隔离进程。这种隔离性体现在文件系统、进程空间以及网络栈上。每个容器都拥有自己独立的网络命名空间(Network Namespace),这意味着它有自己的 IP 地址、路由表和网络接口(如 eth0
)。
这种设计带来了极大的好处,比如避免了应用间的端口冲突。但同时也引出了一个核心问题:这些相互隔离的容器该如何通信?又该如何与外部世界(包括宿主机)进行交互?
为了解决这个问题,Docker 设计了一套强大的网络子系统,通过不同的网络驱动(Driver)来满足各种复杂的网络需求。
1.2 Docker 的网络驱动
Docker 提供了多种网络驱动,以支持不同的网络拓扑和应用场景。你可以通过 docker network ls
命令查看当前系统支持的网络驱动。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
g7h8i9j0k1l2 host host local
m3n4o5p6q7r8 none null local
常见的网络驱动包括:
bridge
(网桥模式): Docker 的默认网络驱动。它会为每个容器分配一个独立的网络命名空间,并使用一个虚拟网桥将它们连接起来。本文将重点剖析此模式。host
(主机模式): 容器将直接共享宿主机的网络命名空间,不进行隔离。容器的性能最高,但牺牲了隔离性,容易产生端口冲突。none
(无网络模式): 容器拥有自己的网络命名空间,但没有任何网络配置。它只有一个lo
(loopback) 接口,完全与外界隔离。overlay
(覆盖网络): 用于连接运行在不同宿主机上的容器,是实现 Docker Swarm 等集群通信的核心。macvlan
: 允许为容器分配一个 MAC 地址,使其在网络中显示为一台独立的物理设备。
本篇我们将聚焦于最常用、也是默认的 bridge
网络。
二、深入理解 Bridge 网络
当你启动一个容器而未指定任何 --network
参数时,它就会默认连接到名为 bridge
的网络。这个网络是在 Docker 服务启动时自动创建的。
2.1 Bridge 网络的工作原理
Bridge 网络的核心思想是在宿主机上创建一个虚拟的以太网桥(Virtual Bridge),所有连接到此网络的容器都会通过这个网桥进行通信,就像物理世界中的多台计算机连接到同一个交换机上一样。
2.1.1 默认的 docker0
网桥
当 Docker 服务启动时,它会在宿主机上创建一个名为 docker0
的虚拟网络接口。这个接口就是一个网桥,它有一个自己的 IP 地址,并充当所有连接到默认 bridge
网络的容器的网关。
我们可以使用 ip addr
或 ifconfig
命令在宿主机上查看到它:
# 在 Linux 宿主机上执行
$ ip addr show docker0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group defaultlink/ether 02:42:ac:11:00:01 brd ff:ff:ff:ff:ff:ffinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0valid_lft forever preferred_lft forever
从输出中可以看到,docker0
网桥被分配了一个 IP 地址 172.17.0.1
,其子网掩码为 /16
。所有连接到这个网络的容器都会从 172.17.0.0/16
这个网段中获得一个 IP 地址。
2.1.2 veth-pair:连接容器与网桥的“网线”
那么,隔离的容器是如何连接到宿主机的 docker0
网桥上的呢?答案是 veth-pair (Virtual Ethernet Pair)。
veth-pair 是一对成双出现的虚拟网络设备,可以把它们想象成一根虚拟的“网线”。这根“网线”的一端插在容器的网络命名空间里(通常被命名为 eth0
),另一端则插在宿主机的 docker0
网桥上。任何从一端进入的数据包都会原封不动地从另一端出来。
工作流程总结:
- Docker 守护进程创建
docker0
虚拟网桥。 - 每当启动一个容器时,Docker 会创建一个 veth-pair。
- veth-pair 的一端(如
veth-xxxxx
)连接到docker0
网桥。 - veth-pair 的另一端放入容器的网络命名空间,并被重命名为
eth0
。 - Docker 从
docker0
的 IP 地址段中为容器的eth0
分配一个 IP 地址(如172.17.0.2
)。
这样,所有容器都连接到了同一个二层网络(docker0
网桥),它们之间可以自由通信。
2.2 容器间通信
在同一个 bridge
网络下,容器间的通信主要有两种方式。
2.2.1 通过 IP 地址通信
由于所有容器都在同一个网段,它们可以直接使用对方的 IP 地址进行通信。
实战演练:
-
启动两个 Nginx 容器
c1
和c2
。docker run -d --name c1 nginx:alpine docker run -d --name c2 nginx:alpine
-
获取容器
c2
的 IP 地址。# 使用 docker inspect 命令,并用 Go 模板过滤出 IP 地址 docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' c2 # 输出可能为:172.17.0.3
-
在容器
c1
中ping
容器c2
的 IP。# 进入 c1 容器的 shell docker exec -it c1 sh# 在 c1 容器内执行 ping 命令(alpine 镜像默认没有 ping,需要先安装) # / # apk add --no-cache iputils # / # ping 172.17.0.3 PING 172.17.0.3 (172.17.0.3): 56 data bytes 64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.123 ms 64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.089 ms ...
可以看到,通信是成功的。
2.2.2 通过容器名通信
虽然 IP 地址通信是可行的,但在动态环境中,容器重启后 IP 地址可能会改变。更可靠的方式是使用容器名进行通信。Docker 内置了一个 DNS 服务器,可以将容器名解析为其对应的 IP 地址。
重要说明:在较旧的 Docker 版本中,默认的 bridge
网络不支持通过容器名进行 DNS 解析。你需要创建自定义的 bridge
网络。但在现代 Docker 版本中,默认的 bridge
网络也已经支持此功能。
实战演练:
继续使用上面的 c1
和 c2
容器。
-
在容器
c1
中直接ping
容器c2
的名字。# 进入 c1 容器的 shell docker exec -it c1 sh# 在 c1 容器内执行 ping 命令 # / # ping c2 PING c2 (172.17.0.3): 56 data bytes 64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.115 ms 64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.092 ms ...
可以看到,
c1
成功将c2
这个名字解析为了它的 IP 地址172.17.0.3
。这是目前推荐的容器间通信方式。
提示:虽然默认
bridge
网络支持了 DNS 解析,但在生产环境中,我们强烈建议创建自定义的bridge
网络来隔离不同的应用栈。自定义网络提供了更好的隔离性和网络管理能力,我们将在后续文章中详细介绍。
三、连接外部世界:端口映射
默认情况下,容器内的端口只在 docker0
这个内部网络中可见,宿主机和外部网络是无法直接访问的。为了将容器内的服务(如一个 Web 服务器)暴露出去,我们需要进行端口映射。
3.1 端口映射的原理
端口映射的本质是在宿主机上设置 NAT (Network Address Translation) 规则。具体来说,Docker 通过操纵宿主机的 iptables
,创建了一条 DNAT
(Destination NAT) 规则。
这条规则会监听宿主机的某个端口,并将所有访问该端口的流量,其目标地址和端口都重定向到容器的私有 IP 地址和指定端口上。
3.2 两种映射方式:-p
与 -P
在 docker run
命令中,我们通过 -p
或 -P
参数来设置端口映射。
3.2.1 -p
:指定端口映射
-p
参数允许你精确地指定映射关系,其格式为 -p <host_port>:<container_port>
。
实战演练:
-
启动一个 Nginx 容器,并将宿主机的
8080
端口映射到容器的80
端口。docker run -d --name web_server -p 8080:80 nginx:alpine
-
查看容器运行状态和端口映射。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 nginx:alpine "/docker-entrypoint.…" 5 seconds ago Up 4 seconds 0.0.0.0:8080->80/tcp web_server
PORTS
列清楚地显示了0.0.0.0:8080->80/tcp
,表示宿主机上所有网络接口 (0.0.0.0
) 的8080
端口的 TCP 流量,都会被转发到容器的80
端口。 -
现在,你可以通过浏览器或
curl
命令访问宿主机的8080
端口来访问 Nginx 服务。# 在宿主机上执行 $ curl http://localhost:8080<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ... </html>
3.2.2 -P
:随机端口映射
-P
(大写) 参数则更加自动化。它会读取镜像中通过 EXPOSE
指令声明的所有端口,并将它们随机映射到宿主机上的一个高位端口(通常在 32768-60999 之间)。
实战演练:
官方的 Nginx 镜像的 Dockerfile 中已经包含了 EXPOSE 80
。
-
使用
-P
参数启动一个 Nginx 容器。docker run -d --name random_port_web -P nginx:alpine
-
查看随机映射的端口。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES g7h8i9j0k1l2 nginx:alpine "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 0.0.0.0:49153->80/tcp random_port_web
可以看到,Docker 自动将容器的
80
端口映射到了宿主机的49153
端口。
这种方式在你需要批量启动多个临时服务实例,且不关心具体端口号时非常有用。
3.3 iptables
的幕后工作
对于有兴趣深入了解的读者,可以查看 Docker 在 iptables
中创建的规则。
# 在宿主机上执行
sudo iptables -t nat -L DOCKER
你会看到类似下面的规则,这正是实现端口映射的核心。
Chain DOCKER (2 references)
target prot opt source destination
...
DNAT tcp -- anywhere anywhere tcp dpt:8080 to:172.17.0.4:80
这条 DNAT
规则清晰地表明:将所有目标端口为 8080
的 TCP 流量,其目标地址和端口修改为 172.17.0.4:80
,从而实现了流量的转发。
四、总结
本篇文章我们详细剖析了 Docker 最基础也最重要的网络模式——Bridge 网络。现在,让我们回顾一下核心知识点:
- 核心组件:Bridge 网络的核心是宿主机上的一个虚拟网桥(默认为
docker0
),它充当了所有连接到该网络的容器的虚拟交换机。 - 连接方式:每个容器通过一个 veth-pair(虚拟网线)连接到
docker0
网桥,从而获得一个独立的、位于docker0
网段的 IP 地址。 - 容器间通信:在同一个
bridge
网络中的容器,可以直接通过对方的 IP 地址进行通信。在现代 Docker 版本中,也支持通过容器名进行通信,这是 Docker 内置 DNS 服务提供的便利。 - 外部访问:默认情况下容器是隔离的,需要通过端口映射(
-p
或-P
参数)才能将容器内的服务暴露给宿主机和外部网络。 - 工作原理:端口映射的底层实现依赖于宿主机的
iptables
的DNAT
规则,它将对宿主机特定端口的访问流量转发至容器的 IP 和端口。
理解了 Bridge 网络,你就掌握了 Docker 单机网络通信的基石。在下一篇文章中,我们将继续探索 host
、none
以及功能更强大的自定义 bridge
网络,为构建更复杂的应用架构打下坚实的基础。