单主机网络 — bridge、端口发布与反向代理¶
这一章回答三个问题: 容器之间怎么通信(bridge 网络 + 内置 DNS); 容器里的服务怎么被外面访问(端口发布,一道受控的门); 怎么按 URL 把流量分给不同服务(第 7 层路由,反向代理)。 主线例子:c3 容器 ping c4——通;ping 隔壁网络的 c2——100% 丢包。 一次成功和一次失败,就把 Docker 网络模型的设计讲完了。
1. 顶层全景:一张网图的三种读法¶
宿主机 ── eth0(物理/虚拟网卡)
│
docker0(Linux bridge,路由器角色,占 172.17.0.1)
│ 子网 172.17.0.0/16
├─ veth ── c1(eth0, 172.17.0.2)
├─ veth ── c2(eth0, 172.17.0.3) ← 同网:互通
│
sample-net(自建 bridge, 172.18.0.0/16)
├─ veth ── c3 ─┐
├─ veth ── c4 │ 同网互通;跨网(sample-net → bridge)默认防火墙隔离
│
对外:不开门(默认 ingress 全挡)→ 端口映射 -p 8080:80(受控的门)
图说:读法一"同网互通",读法二"跨网隔离",读法三"对外只走发布的门"。
2. 网络即防火墙(SDN 的默认立场)¶
Docker 的网络模型安全优先:默认创建的网络是软件定义网络(SDN), 同网络的容器自由通信,不同网络的容器互相隔离,除非显式地把某个容器同时接进两个网1。
书里的三服务例子值得照抄,因为它就是最小权限网络的设计样板2:
需求:webAPI 能调 productCatalog,但不能直接碰 database;
productCatalog 两边都能调。
做法:front 网(webAPI, productCatalog)+ back 网(productCatalog, database)
productCatalog 一肩两网,webAPI 和 database 各只在自己的网里。
收益:webAPI 被攻破,攻击者仍够不到 database——还得再破一层 productCatalog。
图说:网络边界 = 防火墙边界;多接一个网 = 多开一扇门,要 deliberate(刻意)。
配套规矩(书里的最佳实践):SDN 创建又快又免费,让每个网络只装需要互相对话的服务;
别把所有容器都丢在默认 bridge 上——同一网络里的服务越少,一个被攻破时的爆炸半径越小3。
现代发行版上防火墙规则由 nftables 管理(旧的是 iptables),排查命令是
sudo nft list ruleset4。还有一步到位的安全清单:只发布需要的服务;
本地开发优先绑 127.0.0.1;生产在反向代理/ingress 终结 TLS;UDP 无连接更易伪造,
非必要不开5。
3. bridge 网络解剖(主线例子)¶
Docker 守护进程首次启动时自动建一个 Linux 网桥 docker0,并定义名为 bridge 的默认网络;不显式指定网络的容器都挂这里6。查看与解剖:
$ docker network ls → bridge driver=bridge scope=local
$ docker network inspect bridge
→ IPAM: subnet 172.17.0.0/16, gateway 172.17.0.1
→ 容器可用地址 172.17.0.2 ~ 172.17.255.255;172.17.0.1 留给网桥自己[^7]
两个容易踩的坑先交代。其一,默认只放行出口(egress),入口(ingress)全挡——
容器能上外网,但外面进不来,这正是"端口发布"存在的理由(见 §5)8。
其二,公司内网/VPN 常与 172.17.0.0/16 撞车,改 daemon.json 的
"bip": "192.168.100.1/24" 可换默认子网7。
自建网络与内置 DNS(主线例子的前半段):
$ docker network create --driver bridge sample-net
$ docker network inspect sample-net | grep Subnet → 172.18.0.0/16(自动分下一块)
c3、c4 挂到 sample-net:
$ docker container run --name c3 --rm -d --network sample-net alpine:3.22 ping 127.0.0.1
$ docker container run --name c4 --rm -d --network sample-net alpine:3.22 ping 127.0.0.1
进 c3:
/ # ping c4
→ PING c4 (172.20.0.3): 64 bytes from 172.20.0.3 ... ← 通了,而且是按名字通的!
按名字能 ping 通,是自建网络相对默认 bridge 的关键差异: 同一用户自定义网络上的容器,自动获得 Docker 内置 DNS 的名字解析—— 这就是第 09 章"服务发现"在单机的形态9。
跨网隔离(主线例子的后半段):
从 c3(在 sample-net)ping 隔壁 bridge 网络的 c2:
/ # ping c2 → ping: bad address 'c2' ← 名字都解析不了
/ # ping 172.17.0.3
→ 11 packets transmitted, 0 packets received, 100% packet loss
→ 网络提供了额外一层隔离,也因此多一层安全[^10]
结论要背:不同网络上的容器默认互相防火墙隔离;DNS 名字解析不跨网络。
其余 knobs:--subnet "10.1.0.0/16" 自定义子网;Docker 20.10 起 --ipv6
双栈(IPv4 + fd00:dead:beef::/48 之类 IPv6 前缀);VPN/overlay 环境下用
--opt com.docker.network.driver.mtu=1400 调 MTU 防分片;子网别重叠11。
一个容器还能 docker network connect sample-net c6 加入多个网——
这正是 §2 三服务例子的实现手段;有容器挂着的网络删不掉(network rm 会报
active endpoints 错误)12。
4. host 与 none:两个特殊网络¶
host 网络去掉容器与宿主机的网络命名空间隔离:容器没有自己的私有 IP,
直接用宿主机的网络栈绑接口13。适用:对延迟敏感的高性能网络服务、
需要广播/多播发现、要直接检查宿主网络的调试工具。三条警示同样硬:
仅 Linux 完整支持(Mac/Windows 上 --network host 挂的是 Desktop VM 的网络,
宿主机访问仍要发布端口);安全上容器被攻破等于宿主网络被看穿;
与宿主机应用撞端口会直接冲突。生产跑业务容器没有理由用 host 网络,
用用户自定义网络14。
none(null)网络:给容器建命名空间但不接任何网络——只剩 loopback(lo), 够不到任何别的容器和外网15。适用:安全敏感负载、数据全靠卷挂载的离线处理、 取证/分析容器。纪律:不需要网络的服务,就该显式跑在 none 网络上—— 默认放行出口恰恰是大多数容器根本不需要的权限15。
5. 端口发布:对外只有一道受控的门¶
容器和宿主机各有独立的端口空间,默认毫无关系。要对外服务,就把容器端口接到 一个空闲的宿主端口上——这个动作叫 publish(发布)。书里把它比作在防火墙 上开一道门:不开大门,只开一个受控的闸16。
| 方式 | 命令 | 结果 |
|---|---|---|
| 随机发布 | -P(大写) |
Docker 从临时端口段(如 32768–60999)挑一个;docker container port web → 80/tcp -> 0.0.0.0:5500017 |
| 指定映射 | -p 8080:80 |
宿主 8080 → 容器 80;-p 3000:4321/udp 显式开 UDP(TCP 和 UDP 要分别映射)18 |
| 只声明 | Dockerfile EXPOSE 80 |
只是元数据,什么都不开;-P 自动映射时才轮到它起作用19 |
EXPOSE ≠ publish 这一组区别是本章考试重点:EXPOSE 是镜像里的"意向书", publish 是运行时在宿主包过滤器(iptables/nftables,或 Desktop VM 的等效规则)上 凿门19。
还有个从单机直通 Kubernetes 的概念:让一个新容器加入另一个容器的网络命名空间
(--network container:myapp),两个容器就共享 IP、端口和 loopback——
这是 Kubernetes Pod 的地基:一个 Pod 里的多个容器(主应用 + 日志/监控/网格边车)
像同进程一样通过 localhost 交互。调试时可以用 nicolaka/netshoot 工具镜像
这样贴到应用容器上,在完全一致的网络现场里跑 curl/dig/tcpdump20。
(共享命名空间 = 共享全部网络权限,一个被攻破能看所有流量,同样要谨慎20。)
6. 反向代理:按 URL 分流(第 7 层路由落地)¶
第 09 章说过第 7 层路由按 URL 分发请求;本章把它演完。场景是绞杀者模式的
第一刀:单体 city-tours(Python/Flask,监听 5000)容器化后,把目录功能 catalog
拆成 Node.js 微服务(监听 3000)——但客户端的 URL 不能变:
acme.com/catalog?... 还得原样工作21。
Traefik 是开源的云原生边缘路由器,与 Docker 的集成方式优雅: 它挂载 docker.sock,读每个容器上的标签(labels)自动发现路由规则22:
catalog 容器(优先级 10,精确匹配 /catalog 前缀):
--label traefik.enable=true ← 默认 false,要显式启用
--label traefik.port=3000 ← 转发到容器内 3000
--label traefik.priority=10
--label traefik.http.routers.catalog.rule="Host(\"acme.com\") && PathPrefix(\"/catalog\")"
city-tours 容器(优先级 1,兜底):
--label traefik.priority=1
--label traefik.http.routers.city-tours.rule="Host(\"acme.com\")"
Traefik 本体:
docker container run -d --name traefik \
-p 8080:8080 -p 80:80 \
-v /var/run/docker.sock:/var/run/docker.sock \
traefik:v2.0 --providers.docker=true \
--providers.docker.exposedbydefault=false --api.insecure=true
效果:acme.com/catalog* → catalog:3000;acme.com 其余 → city-tours:5000
优先级 10 的规则先吃 /catalog,优先级 1 的兜底其余;8080 是 Traefik 的管理 UI[^22]
绞杀者第一刀落成了:URL 没变、客户端无感、单体内部却已少了一块。 这就是第 09 章 Fowler 警告与第 7 层路由两个知识点的合流。
7. 作者的判断与证据¶
- "网络即防火墙、按服务切网"是本章的核心立场,依据是 SDN 的默认隔离语义 和爆炸半径论证13。
- "业务容器没有理由用 host 网络":安全 + 端口冲突 + 丢隔离,三条理由都硬14。
- Traefik 方案是"众多选择之一":书里明说 Nginx/HAProxy 同样能做; 选 Traefik 是因为标签自动发现与 Docker 契合22。
- 本章机制均有可复现实验(ping 通/丢包、port 映射、curl --resolve),证据强度高。
判断(我们的,不是书里的): 本章真正的"隐藏主线"是默认拒绝(default deny): 入口默认全挡、跨网默认隔离、EXPOSE 默认不开、Traefik 默认不暴露 (exposedbydefault=false)。Docker 网络的设计哲学是"每个动作都要显式授权"—— 记住这一条,比记住所有命令都值钱;它也正是 Kubernetes NetworkPolicy 思想的源头。 如果错,会错在: 默认 bridge 网络上容器间其实是互通的(隔离性弱于用户自定义网), 严格 default-deny 需要靠用户自定义网络 + 外部策略引擎补齐; 但"对外默认关闭、发布需显式"这个语义在所有网络类型上都成立。
8. 边界与局限¶
- 单主机视野:overlay/多主机网络(Weave、Calico、Contiv)只给了一张类型表, 用法留给编排章节(第 14 章)23。
- 防火墙规则由实现层管理(nftables/VM 等效规则),书里只教了查看,没教手写规则; 深度定制要另学 nftables。
- Traefik 的示例是 v2.0 的命令行风格,新版本配置格式有变化;概念(标签路由、 优先级)稳定,照抄命令前先查版本。
- IPv6/双栈与 MTU 微调只给了命令,没有场景化的排障故事。
9. 可带走的¶
- 网络即防火墙:同网互通、跨网隔离、DNS 不跨网;一个容器可挂多网做"中转"910。
- 最小权限组网:每个网只装需要互访的服务;别用默认 bridge 装一切3。
- bridge 解剖:docker0、172.17.0.0/16、.1 是网关、容器从 .2 开始排号7。
- 默认只出不让进;对外只能走 publish 这道门816。
-p 宿主:容器(可带 /udp),-P随机,EXPOSE只是声明171819。- host 网络:省 NAT、丢隔离;业务容器别用;none 网络:断网服务的正确姿势1315。
--network container:X= 共享网络命名空间 = Kubernetes Pod 的地基 + 现场调试利器(netshoot)20。- 第 7 层路由用反向代理(Traefik 读标签自动发现),URL 不变就能把单体拆成微服务2122。
10. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| SDN 防火墙语义 | Network firewalling | text/109-fm-network-firewalling.txt:37(搜「security first」) · text/109-fm-network-firewalling.txt:41(搜「freely communicate」) · text/109-fm-network-firewalling.txt:48(搜「no communication」) |
| 三服务组网例子 | Network firewalling | text/109-fm-network-firewalling.txt:51(搜「webAPI」) · text/109-fm-network-firewalling.txt:59(搜「separate networks」) · text/109-fm-network-firewalling.txt:64(搜「compromised」) |
| 网络类型表(overlay 等) | Network firewalling | text/109-fm-network-firewalling.txt:6(搜「Overlay」) · text/109-fm-network-firewalling.txt:14(搜「Weave」) |
| nftables | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:2(搜「nftables」) |
| 最小权限组网建议 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:14(搜「Least-privilege」) · text/110-fm-working-with-the-bridge-network.txt:18(搜「avoid the temptation」) |
| docker0 与默认 bridge | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:27(搜「docker0」) · text/110-fm-working-with-the-bridge-network.txt:60(搜「172.17.0.0/16」) |
| 默认只放行出口 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:97(搜「egress」) |
| bip 换子网 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:65(搜「clash」) · text/110-fm-working-with-the-bridge-network.txt:70(搜「bip」) |
| 自建网与内置 DNS | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:111(搜「user-defined bridge」) · text/110-fm-working-with-the-bridge-network.txt:116(搜「network create」) · text/110-fm-working-with-the-bridge-network.txt:126(搜「172.18.0.0/16」) |
| ping c4 通 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:296(搜「ping c4」) · text/110-fm-working-with-the-bridge-network.txt:319(搜「name resolution」) |
| 跨网 100% 丢包 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:332(搜「172.17.0.3」) · text/110-fm-working-with-the-bridge-network.txt:339(搜「100% packet loss」) · text/110-fm-working-with-the-bridge-network.txt:343(搜「isolation」) |
| 多网连接与删网限制 | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:367(搜「network connect」) · text/110-fm-working-with-the-bridge-network.txt:384(搜「active」) |
| 双栈与 MTU | Working with the bridge network | text/110-fm-working-with-the-bridge-network.txt:147(搜「--ipv6」) · text/110-fm-working-with-the-bridge-network.txt:165(搜「mtu=1400」) |
| host 网络 | The host and null network types | text/111-fm-the-host-and-null-network-types.txt:31(搜「removes the network namespace」) · text/111-fm-the-host-and-null-network-types.txt:38(搜「high-performance」) · text/111-fm-the-host-and-null-network-types.txt:44(搜「Linux only」) · text/111-fm-the-host-and-null-network-types.txt:48(搜「sniff」) |
| none 网络 | The host and null network types | text/111-fm-the-host-and-null-network-types.txt:100(搜「null」) · text/111-fm-the-host-and-null-network-types.txt:103(搜「loopback」) · text/112-fm-running-in-an-existing-network-namespace.txt:1(搜「completely isolated」) |
| 发布端口的动机 | Managing container ports | text/113-fm-managing-container-ports.txt:38(搜「expose an application」) · text/113-fm-managing-container-ports.txt:43(搜「mapping a container port」) |
| -P 随机映射 | Managing container ports | text/113-fm-managing-container-ports.txt:56(搜「-P」) · text/113-fm-managing-container-ports.txt:70(搜「55000」) |
| -p 指定与 UDP | Managing container ports | text/113-fm-managing-container-ports.txt:104(搜「-p 8080:80」) · text/113-fm-managing-container-ports.txt:112(搜「4321/udp」) |
| EXPOSE vs publish | Managing container ports | text/113-fm-managing-container-ports.txt:115(搜「only documents intent」) · text/113-fm-managing-container-ports.txt:116(搜「wires a container port」) |
| Pod 地基与 netshoot | Managing container ports | text/113-fm-managing-container-ports.txt:2(搜「foundation of Kubernetes Pods」) · text/113-fm-managing-container-ports.txt:16(搜「netshoot」) · text/113-fm-managing-container-ports.txt:28(搜「privileges」) |
| 绞杀者场景 | HTTP-level routing using a reverse proxy | text/114-fm-http-level-routing-using-a-reverse-proxy.txt:13(搜「monolithic」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:22(搜「completely starved」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:27(搜「catalog」) |
| Traefik 标签路由 | HTTP-level routing using a reverse proxy | text/114-fm-http-level-routing-using-a-reverse-proxy.txt:210(搜「edge router」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:219(搜「traefik.enable」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:239(搜「PathPrefix」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:261(搜「priority」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:274(搜「traefik:v2.0」) |
| 安全清单 | HTTP-level routing using a reverse proxy | text/114-fm-http-level-routing-using-a-reverse-proxy.txt:1(搜「Security checklist」) · text/114-fm-http-level-routing-using-a-reverse-proxy.txt:7(搜「terminate TLS」) |
-
出处:「Network firewalling」第 36–49 段(
text/109-fm-network-firewalling.txt:37,搜「security first」;:40,搜「SDNs」;:48,搜「no communication」)。 ↩↩ -
出处:「Network firewalling」第 50–65 段(
text/109-fm-network-firewalling.txt:57,搜「webAPI to communicate」;:59,搜「separate networks」;:64,搜「compromised」)。 ↩ -
出处:「Working with the bridge network」第 14–21 段(
text/110-fm-working-with-the-bridge-network.txt:14,搜「Least-privilege」;:18,搜「avoid the temptation」;:19,搜「blast radius」)。 ↩↩↩ -
出处:「Working with the bridge network」第 1–13 段(
text/110-fm-working-with-the-bridge-network.txt:2,搜「nftables」;:8,搜「nft list ruleset」)。 ↩ -
出处:「HTTP-level routing using a reverse proxy」第 1–10 段(
text/114-fm-http-level-routing-using-a-reverse-proxy.txt:4,搜「Publish only」;:7,搜「terminate TLS」;:8,搜「UDP」)。 ↩ -
出处:「Working with the bridge network」第 23–42 段(
text/110-fm-working-with-the-bridge-network.txt:27,搜「docker0」;:29,搜「default network」;:40,搜「single host」)。 ↩ -
出处:「Working with the bridge network」第 43–75 段(
text/110-fm-working-with-the-bridge-network.txt:56,搜「IPAM」;:60,搜「172.17.0.0/16」;:62,搜「172.17.0.1」;:70,搜「bip」)。 ↩↩ -
出处:「Working with the bridge network」第 97–105 段(
text/110-fm-working-with-the-bridge-network.txt:97,搜「egress」;:99,搜「veth」)。 ↩↩ -
出处:「Working with the bridge network」第 110–137 段(
text/110-fm-working-with-the-bridge-network.txt:112,搜「embedded DNS」;:116,搜「network create」;:126,搜「172.18.0.0/16」)。 ↩↩ -
出处:「Working with the bridge network」第 287–343 段(
text/110-fm-working-with-the-bridge-network.txt:296,搜「ping c4」;:328,搜「bad address」;:339,搜「100% packet loss」;:343,搜「extra layer of isolation」)。 ↩ -
出处:「Working with the bridge network」第 139–172 段(
text/110-fm-working-with-the-bridge-network.txt:140,搜「IPv6」;:149,搜「fd00:dead:beef」;:163,搜「mtu=1400」;:170,搜「overlapping」)。 ↩ -
出处:「Working with the bridge network」第 344–386 段(
text/110-fm-working-with-the-bridge-network.txt:367,搜「network connect」;:380,搜「network rm」;:384,搜「active」)。 ↩ -
出处:「The host and null network types」第 26–40 段(
text/111-fm-the-host-and-null-network-types.txt:31,搜「removes the network namespace isolation」;:36,搜「use cases」)。 ↩↩ -
出处:「The host and null network types」第 41–98 段(
text/111-fm-the-host-and-null-network-types.txt:44,搜「Linux only」;:48,搜「sniff」;:51,搜「strongly recommended」;:91,搜「Port conflicts」;:97,搜「user-defined network」)。 ↩↩ -
出处:「The host and null network types」第 100–112 段(
text/111-fm-the-host-and-null-network-types.txt:101,搜「doesn't connect」;:103,搜「loopback」;:106,搜「security-sensitive」)。 ↩↩↩ -
出处:「Managing container ports」第 33–50 段(
text/113-fm-managing-container-ports.txt:40,搜「open a gate」;:43,搜「publish a port」;:45,搜「independently」)。 ↩↩ -
出处:「Managing container ports」第 51–98 段(
text/113-fm-managing-container-ports.txt:56,搜「-P」;:65,搜「ephemeral」;:70,搜「55000」;:81,搜「HostPort」)。 ↩↩ -
出处:「Managing container ports」第 100–113 段(
text/113-fm-managing-container-ports.txt:104,搜「-p 8080:80」;:107,搜「host port」;:112,搜「4321/udp」)。 ↩↩ -
出处:「Managing container ports」第 114–119 段(
text/113-fm-managing-container-ports.txt:115,搜「only documents intent」;:116,搜「wires a container port」)。 ↩↩↩ -
出处:「Managing container ports」第 1–31 段(
text/113-fm-managing-container-ports.txt:2,搜「foundation of Kubernetes Pods」;:6,搜「same network namespace」;:16,搜「netshoot」;:28,搜「privileges」)。 ↩↩↩ -
出处:「HTTP-level routing using a reverse proxy」第 12–31 段(
text/114-fm-http-level-routing-using-a-reverse-proxy.txt:13,搜「monolithic」;:22,搜「completely starved」;:27,搜「catalog」;:30,搜「checkout」)。 ↩↩ -
出处:「HTTP-level routing using a reverse proxy」第 204–282 段(
text/114-fm-http-level-routing-using-a-reverse-proxy.txt:210,搜「edge router」;:213,搜「metadata」;:219,搜「traefik.enable=true」;:239,搜「PathPrefix」;:261,搜「priority」;:269,搜「traefik:v2.0」;:279,搜「exposedbydefault=false」)。 ↩↩↩ -
出处:「Network firewalling」第 1–34 段(
text/109-fm-network-firewalling.txt:6,搜「Overlay」;:14,搜「Weave」;:18,搜「Contiv」;:27,搜「Calico」)。 ↩