玩转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:查看现有网络(摸清地形)
【目的】 动手前先看看机器上已有哪些网络,认识"默认三件套 + 自定义网络"各自长什么样,并找到自定义网络的活例子。
【命令】
bash
docker network ls
【命令详解】
docker network--- Docker 的网络管理命令组ls--- list,列出所有网络
【过程与结果】
sql
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 网络,作为后面容器互连的"内部电话网"。
【命令】
bash
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,作为被访问的目标(模拟真实项目里"数据库 / 后端服务"的角色)。
【命令】
bash
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、不配置任何东西)。
【命令】
bash
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 行,避免刷屏
【过程与结果】
xml
[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 课的悬念。
【命令】
bash
docker network inspect mynet
【命令详解】
docker network inspect <名字>--- 输出该网络的详细配置(JSON 格式)
【过程与结果】(节选关键字段并注释)
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 课「实战:容器化一个真实后端应用」做准备