玩转Docker 06 — 容器网络
一、为什么学网络
容器不是孤立的——它们要互相通信(典型场景:后端容器 ↔ MySQL / Redis / 数据库容器),也要对外提供服务(-p 端口映射,第 1 课已学)。
本课还是 第 7 课 Docker Compose(多容器编排)的基础——Compose 的服务互连用的就是本课这套机制。
一个悬念(本课揭晓)
第 1 课 nginx 容器的访问日志里,客户端 IP 是 172.17.0.1,不是 127.0.0.1——为什么?谜底在本课。
二、Docker 网络类型
| 类型 | 是什么 | 何时用 |
|---|---|---|
| bridge(默认) | 虚拟桥接网络;容器分到独立 IP(172.17.x.x 段) | 不指定网络时,容器默认进它 |
| host | 容器直接共用宿主机的网络(无网络隔离) | 要极致网络性能、不在乎隔离 |
| none | 容器完全没有网络 | 离线计算、安全隔离 |
| 自定义 bridge | 你自己创建的 bridge 网络 | 多容器互访(关键!) |
三、核心区分:默认 bridge vs 自定义 bridge(本课最重要)
| 默认 bridge | 自定义 bridge | |
|---|---|---|
| 容器间互访 | 只能靠 IP(IP 会变、记不住,难用) | 可以用名字(内置 DNS 自动解析) |
| DNS 服务 | ❌ 没有 | ✅ Docker 内置 DNS |
| 实战地位 | 历史遗留默认 | 多容器应用的标准做法(Compose 自动建的就是这种) |
实例:本机运行的 Milvus 套件(standalone + minio + etcd + attu)就是靠一个叫
milvus的自定义网络互连——milvus 连 etcd、minio 用的就是容器名。
四、实验全过程(含目的、命令详解、结果解读)
实验 1:查看现有网络(摸清地形)
【目的】 动手前先看看机器上已有哪些网络,认识"默认三件套 + 自定义网络"各自长什么样,并找到自定义网络的活例子。
【命令】
docker network ls
【命令详解】
docker network— Docker 的网络管理命令组ls— list,列出所有网络
【过程与结果】
NETWORK ID NAME DRIVER SCOPE
297a9a73c2c2 bridge bridge local
e22f8f5d5f3e host host local
8dc1ab22bc5e milvus bridge local
18fe25d46125 none null local
逐行解读:
bridge(DRIVER=bridge)— 默认桥接网络。容器不指定网络时都进它,网段 172.17.x.x。host(DRIVER=host)— 宿主机网络,容器直接共用宿主机网卡,无隔离。milvus(DRIVER=bridge)— 👀 一个自定义 bridge 网络!Milvus 那套容器就挂在它上面,靠名字互连(活例子)。none(DRIVER=null)— "无网络",挂上它的容器完全断网。DRIVER列说明网络的实现方式;SCOPE=local表示作用范围是本机。
【结论】 机器上 = 三个内置网络 + 若干自定义网络共存;多容器互连的标准做法就是自定义网络。
实验 2:创建自定义网络
【目的】 亲手建一个自定义 bridge 网络,作为后面容器互连的"内部电话网"。
【命令】
docker network create mynet
【命令详解】
docker network create <名字>— 创建网络;不指定类型时默认创建 bridge 类型(即"自定义 bridge")mynet— 给网络起的名字(自定义网络才有可读名字,默认 bridge 没有)
【过程与结果】
80e8068374e7f1494be8ac52ec3c31403c6f8a57ea3593f0ffe807652510513f
回显新网络的 64 位 ID → 创建成功。Docker 已自动给它分配了一个独立网段(实验 5 会看到是 172.19.0.0/16)。
【结论】 一条命令就拥有了一个专属网络,后续容器挂上来即可互访。
实验 3:在 mynet 上启动一个"服务容器" web
【目的】 在自定义网络上跑一个 nginx 容器并起名 web,作为被访问的目标(模拟真实项目里"数据库 / 后端服务"的角色)。
【命令】
docker run -d --name web --network mynet nginx
【命令详解】(docker run 结构 = 选项 + 镜像 [+ 命令])
-d— 后台运行(detach),不占终端--name web— 容器命名为web。这个名字就是它在网络里的"域名"(自定义网络的 DNS 按名字解析,实验 4 验证)--network mynet— 把容器挂到mynet网络。⚠️ 不写这个参数容器就进默认 bridge(那可没有名字互访!)nginx— 用哪个镜像(本地已有,秒起)- ⚠️ 故意没加
-p:因为接下来要从另一个容器访问它(走 mynet 内部网络),不是从宿主机访问。-p只用于把端口发布到宿主机。
【过程与结果】
2d21e611326975f0254baa2aa824a0a787d962e7a5e1ba3b8cd1ca00f0fe9dfe
回显容器 ID → web 容器已在 mynet 上后台运行(nginx 监听容器内 80 端口)。
【结论】 容器已入网。此时宿主机访问不到它(没 -p),但同网络的容器可以——这正是下个实验要验证的。
实验 4:从另一个容器用"名字"访问 web(本课高潮)
【目的】 验证本课核心命题:同一自定义网络里的容器,能不能只用"名字"就找到并访问另一个容器(不查 IP、不配置任何东西)。
【命令】
docker run --rm --network mynet nginx sh -c 'echo "[1] 名字 web 解析到 IP:"; getent hosts web; echo "[2] 用名字抓 web 的网页:"; (wget -qO- http://web 2>/dev/null || curl -s http://web 2>/dev/null || echo "(无 curl/wget,但 [1] 已证明名字能解析)") | head -3'
【命令详解】
外层(docker run 部分):
--rm— 容器退出后自动删除(一次性测试工具容器,跑完不留垃圾;回忆第 5 课)--network mynet— 关键:临时容器也挂进 mynet,和web同网才可能互访nginx— 借用 nginx 镜像的 Linux 环境当"测试机"(本地已有,秒起)sh -c '...'— 覆盖镜像默认 CMD(第 4 课学的:run 时给命令会替换镜像的 CMD),让它执行我们的测试脚本而不是启动 nginx
内层(脚本部分,逐段):
getent hosts web— 用系统名字解析机制查web对应的 IP。getent是 glibc 自带工具(Debian 系镜像都有),测 DNS 最可靠wget -qO- http://web— 向http://web发 HTTP 请求(注意:名字当域名用!),-q静默、-O-输出到屏幕;2>/dev/null 丢弃报错|| curl -s http://web— wget 不可用就换 curl 试(双保险)|| echo "..."— 两个都没有就打印兜底说明(但 [1] 的解析已足够证明)| head -3— 只显示前 3 行,避免刷屏
【过程与结果】
[1] 名字 web 解析到 IP:
172.19.0.2 web
[2] 用名字抓 web 的网页:
<!DOCTYPE html>
<html>
<head>
逐行解读:
- [1] DNS 解析成功:名字
web→ IP172.19.0.2。这个临时容器里没配置任何 DNS——是 Docker 内置 DNS(自定义网络自带)自动把容器名解析成了它的 IP。 - [2] HTTP 连通成功:直接访问
http://web(名字当域名),真的抓到了 nginx 欢迎页的 HTML。从"名字解析"到"真实通信",全链路打通。 - 注意 IP 段是
172.19.x.x——mynet 的专属网段(每个网络独立一段;默认 bridge 用 172.17.x.x)。
【结论】 自定义网络 = 容器间的"内部电话网",容器名即域名。跨容器连接一律用名字,不用记 IP——这就是 Compose 里服务互连的机制。
实验 5:查看 mynet 网络拓扑(把前 4 步串成一张图)
【目的】 从 Docker 视角看 mynet 的完整配置——网段、网关、挂了哪些容器、各自 IP——验证实验 4 的结果并解开第 1 课的悬念。
【命令】
docker network inspect mynet
【命令详解】
docker network inspect <名字>— 输出该网络的详细配置(JSON 格式)
【过程与结果】(节选关键字段并注释)
"Driver": "bridge", // 桥接型网络
"IPAM": { "Config": [{
"Subnet": "172.19.0.0/16", // mynet 的专属网段
"Gateway": "172.19.0.1" // mynet 的网关
}]},
"Containers": {
"2d21e611...": {
"Name": "web",
"IPv4Address": "172.19.0.2/16", // ← 和实验4 getent 解析出的 IP 完全一致!
"MacAddress": "6e:3a:93:be:68:1a"
}
}
逐点解读:
Subnet: 172.19.0.0/16— mynet 的独立网段(默认 bridge 用 172.17.0.0/16,互不干扰)Gateway: 172.19.0.1— 网关。💡 第 1 课悬念在此闭环:当时 nginx 日志里的客户端 IP172.17.0.1= 默认 bridge 的网关。宿主机访问容器,请求经网桥的网关转发,容器看到的"客户端"就是网关 IP。Containers.web @ 172.19.0.2— 挂在网络上的容器及其 IP,与实验 4 的 DNS 解析结果完全一致MacAddress— 每个容器有自己独立的虚拟网卡和 MAC 地址(Network namespace 的体现:网络栈也是隔离的)
【结论】 inspect = 网络的全景地图:网段、网关、成员、IP 一目了然;也验证了 DNS 解析结果的真实来源。
五、关键认知(五条)
- 名字 = 容器在自定义网络里的"域名"。同网络容器直接用名字互访,不用记 IP。
-p只服务"宿主机/外部访问";容器间互访走内部网络,天然畅通,不需要 -p。- 每个网络有独立网段:默认 bridge = 172.17.x.x;mynet = 172.19.x.x。
- 网关 = 172.x.0.1——解开第 1 课日志
172.17.0.1之谜(宿主机经网关访问容器)。 - 容器有独立虚拟网卡 + MAC 地址(Network namespace 的体现)。
六、命令清单
| 命令 | 作用 |
|---|---|
docker network ls | 列出所有网络 |
docker network create <名字> | 创建自定义 bridge 网络 |
docker network inspect <名字> | 看网络详情(网段/网关/挂了哪些容器及其 IP) |
docker network rm <名字> | 删网络(前提:没有容器挂在上面) |
docker network prune | 删所有未使用的网络 |
docker run --network <名字> ... | 把容器挂到指定网络 |
七、易错点
- 用默认 bridge 期望名字互访 → 不行!默认 bridge 没有内置 DNS,必须用自定义网络。
- 以为容器间访问也要 -p → 不用!-p 只管宿主机/外部访问。
- 删网络报错 "has active endpoints" → 还有容器挂在网上,先
docker rm那些容器再删网络。
八、一句话总结
自定义网络 = 容器间的"内部电话网",容器名即域名。多容器应用(也是 Compose)全靠它互连——记住:跨容器连接,一律用名字。
九、本课产物(可清理)
- 容器
web(nginx,挂 mynet)→docker rm -f web - 网络
mynet→docker network rm mynet(注意顺序:先删 web 才能删网络)
十、下一课预告
第 7 课:Docker Compose —— 多容器编排 🎼
前 6 课的所有拼图(镜像/容器/Dockerfile/volume/网络)在这课合体:
- 一个
docker-compose.yml文件描述整套应用(后端 + 数据库 + Redis + ...) docker compose up一条命令拉起全部容器、自动建自定义网络、互相用名字连接(本课手动做的一切被它自动化)down/restart/logs批量管理- 剖析一个真实在用的 docker-compose.yml(活教材)
- 为第 8 课「实战:容器化一个真实后端应用」做准备