把容器想象成一座公寓楼里的住户,每个容器都有自己的房间、独立的门禁和专属的通讯线路。但住户之间如何串门?访客如何从城市外部进入公寓?楼与楼之间如何互通?这就是容器网络要解决的问题。Docker 容器网络就像城市规划师设计的交通系统:Bridge 网络是小区内部的共享花园和走廊,Host 网络是直接开在主干道上的沿街商铺,Overlay 网络是连接不同城区的跨江大桥,而 Macvlan 网络则是给每户在市政公路上开设的专属出口。端口映射像是物业前台的总机转接服务,容器 DNS 则是社区的电话簿。理解了这套“城市交通规划”,你就掌握了微服务架构中服务互联的核心密码。

岗位关联

容器网络是微服务通信的“神经系统”——不懂网络模型,你只能把容器跑起来;懂了网络模型,才能设计出健壮、安全的微服务架构。

目录
  1. 可视化演示
  2. 网络命名空间:每栋楼的独立门禁
  3. Bridge网络:小区共享主干道
  4. Host网络:直接住在主干道
  5. Overlay网络:跨城高架桥
  6. Macvlan网络:在主干道上开专属出口
  7. 端口映射:物业总机的转接服务
  8. 容器DNS:城市电话簿
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示网络命名空间隔离、bridge端口映射、DNS服务发现与多网络安全策略。

本节配有交互动效(进入视口后自动加载,约 3KB)。

网络命名空间隔离原理 每个容器拥有独立网络协议栈包括网卡路由表 容器间网络命名空间完全隔离互不干扰 NS = {NIC, Route, iptables} 独立网络栈是容器网络隔离的基石

网络命名空间:每栋楼的独立门禁

想象一座城市里每栋公寓楼都有自己独立的门禁系统和大堂。住户出门后,必须先通过自家门禁才能进入公共街道。Docker 的网络命名空间(Network Namespace)就是容器这栋“公寓楼”的独立门禁——每个容器都有自己的网络栈、路由表、防火墙规则和 Socket 表,彼此之间完全隔离。你在容器 A 里配置的 IP 地址、路由规则,容器 B 完全感知不到,就像两栋楼的门禁系统互不相通一样。

这种隔离是 Linux 内核提供的底层能力。当 Docker 创建一个容器时,它会为容器分配一个全新的网络命名空间,容器内部的网络接口、路由表、ARP 缓存都是独立的一套。但容器终究要跟外界通信,于是 Docker 用一对虚拟以太网线缆连接内外——这就是 veth pair(虚拟网线对)。这根网线的一头插在容器的网络命名空间里,另一头插在宿主机或一个虚拟交换机上。

跨命名空间通信必须通过 veth pair 这座“天线桥”。veth 一端是容器内的 eth0,另一端连到宿主机上的虚拟交换机(bridge)或网络设备,数据包就像一个访客刷卡出门后,沿着桥走到公共街区——命名空间隔离开内务,veth 负责内外联通。理解了这层“门禁 + 桥梁”的体系,后面所有网络驱动都是在设计不同的“街区规划方案”。

NetNScontainer={netstack,routes,iptables,sockets}\text{NetNS}_{container} = \{ \text{netstack}, \text{routes}, \text{iptables}, \text{sockets} \}
容器网络命名空间独占四样东西:网络协议栈、路由表、防火墙规则和 Socket 连接表——就像每栋楼独立的大门、走廊、门禁卡系统。
  1. 独立网络栈(netstack)容器拥有自己的 IP 地址、网卡设备、协议栈配置,不受宿主机和其他容器干扰。
    netstack={IPaddr,MACaddr,MTU,…}\text{netstack} = \{ \text{IP}_{addr}, \text{MAC}_{addr}, \text{MTU}, \ldots \}
  2. 独立路由表(routes)容器内部的流量去往何处,由自己的路由表决定,与宿主机互不影响。
    routes={dstsubnet→gatewayip}\text{routes} = \{ \text{dst}_{subnet} \to \text{gateway}_{ip} \}
  3. 独立防火墙(iptables)容器内部的 ACCEPT/DROP 规则自成体系,犹如每栋楼自己的访客登记名单。
    iptables={rulei:chaini,targeti}\text{iptables} = \{ \text{rule}_i : \text{chain}_i, \text{target}_i \}
  4. 独立 Socket 表(sockets)容器内监听的端口、已建立的连接都在自己的进程空间内记账。
    sockets={(srcip:port,dstip:port)}\text{sockets} = \{ (\text{src}_{ip:port}, \text{dst}_{ip:port}) \}

Bridge网络:小区共享主干道

Bridge 是 Docker 默认的网络驱动,也是小白最常接触的“小区规划”。想象一个小区里有一片共享花园和一条主干道,所有住户的门口都有一条小径连接到主干道上。容器之间的通信就是通过这条共享主干道完成的——一个容器的数据包从自家门口出发,沿小径并入主干道,再拐进另一个容器的家门口。

在技术上,这条“主干道”是一个 Linux 网桥(Linux Bridge),它在内核中模拟了一个二层交换机。Docker 启动时会创建一个名为 docker0 的默认网桥,分配一个私有网段(通常是 172.17.0.0/16),然后为每个连接到 bridge 网络的容器从该网段中分配一个 IP。容器内的 eth0 通过 veth pair 连接到 docker0,所有挂在同一网桥上的容器互相之间可以直接通信。

默认 bridge 网络有个“小遗憾”:它不提供内置 DNS 解析。容器重启后 IP 会变,像住户的车位号每次调整都不一样,其他人就找不到了。所以 Docker 官方建议用自定义网络。用 docker network create mynet 可以创建一个新的 bridge 网络,自定义网络自带 DNS 解析,容器之间可以用容器名互访。自定义 bridge 网络还支持在创建容器时指定 IP,就像在小区里为 VIP 预留固定车位。Bridge 网络是容器网络的“标配小区方案”,知道怎么划分子网、怎么规划门牌号,微服务之间的互联就成功了一半。

bridge:172.17.0.0/16⏟子网池→分配containerIP=172.17.0.x\text{bridge}: \underbrace{172.17.0.0/16}_{\text{子网池}} \xrightarrow{\text{分配}} \text{container}_{IP} = 172.17.0.x
Docker 默认网桥从 172.17.0.0/16 这个“地址库”中给每个新容器发一个门牌号(IP 地址),容器之间靠门牌号在共享主干道上互相寻址。
  1. 子网池(子网池)docker0 网桥占用一个私有 IP 段,Docker 从这里给容器分配 IP。
    subnet=172.17.0.0/16\text{subnet} = 172.17.0.0/16
  2. 网桥网关docker0 本身占用该网段的第一个可用地址,作为所有容器的默认网关。
    gateway=172.17.0.1\text{gateway} = 172.17.0.1
  3. 地址分配新容器依次从地址池获取可用 IP,如同物业给新住户按顺序发放门牌号。
    containerIP=172.17.0.x(x=2,3,4,…)\text{container}_{IP} = 172.17.0.x \quad (x = 2, 3, 4, \ldots)
  4. 转发路径容器 A 到容器 B 的数据包经 veth 到了网桥,网桥依据 MAC 地址表转发到目标 veth 口。
    packet:Aveth→MAC lookupBveth\text{packet}: A_{veth} \xrightarrow{\text{MAC lookup}} B_{veth}

Host网络:直接住在主干道

前面说 Bridge 网络是小区里的共享主干道,容器有自己的房间和门禁。那 Host 网络就像一个沿街商铺——没有自己的院子,房间大门直接开在马路边,房客出门就是主干道。在这种模式下,容器直接使用宿主机的网络命名空间,不创建独立门禁。容器没有自己的 IP 地址,它和宿主机共享一个 IP、共享端口空间、共享路由表和防火墙规则。

Host 模式的好处是性能损失极小。因为没有 veth 转发、没有 NAT 转换,数据包从容器进程出发后直接走宿主机的物理网卡,路径最短。对于对网络延迟极度敏感的组件(如高性能缓存、实时日志采集器),Host 模式能省去一层“小区绕行”。但代价也很明显:你无法为不同的容器映射同一个宿主机端口——如果容器 A 在 8080 端口起了服务,容器 B 就没法再在 8080 端口起服务,就像沿街两个铺面不可能共用同一个门牌号做生意。

Host 网络适合的场景很明确:追求极低网络延迟、且确信不会跟宿主机端口冲突的服务。比如 kubelet、监控代理这类基础设施组件,用 Host 模式可以直接绑定宿主机端口。但如果你的业务容器需要严格隔离、需要多副本共存,Host 模式就会像两家汉堡店挤同一间铺面——只能留下一家。理解 Host 模式的取舍,能帮你在“性能”与“隔离”之间做出清醒选择。

Host 模式:IPcontainer=IPhost,Portcontainer∈PortSpacehost\text{Host 模式}: \quad \text{IP}_{container} = \text{IP}_{host}, \quad \text{Port}_{container} \in \text{PortSpace}_{host}
Host 模式下容器的 IP 就是宿主机的 IP,容器端口和宿主机端口共享同一张“门牌表”——天然存在端口抢占风险。
  1. 共享 IP容器网络栈完全复用宿主机,不分配新 IP。
    IPcontainer≡IPhost\text{IP}_{container} \equiv \text{IP}_{host}
  2. 共享端口空间容器监听的端口直接占宿主机的端口号,二者合用一个端口表。
    bindcontainer:porti⇒occupyhost:porti\text{bind}_{container} : \text{port}_i \Rightarrow \text{occupy}_{host} : \text{port}_i
  3. 无 NAT 转换数据包不经过地址伪装,直接经物理网卡发出,转发路径最短。
    pathhost:process→eth0physical\text{path}_{host}: \text{process} \to \text{eth0}_{physical}

Overlay网络:跨城高架桥

当容器分布在多台宿主机上,Bridge 网络就无能为力了——不同小区的主干道是各自独立的,住户想要跨小区通信,得有一条跨城高架桥。Overlay 网络正是这样一座架设在多台宿主机之间的虚拟高架桥。它让分布在不同物理机上的容器,感觉就像连在同一个交换机上,彼此无需关心对方宿主机在哪。

Overlay 网络的核心技术是 VXLAN(Virtual Extensible LAN)。原理很简单:容器 A 发出的数据包先被封装进一个新的 UDP 包,这个外层包的源 IP 是宿主机 A 的 IP,目标 IP 是宿主机 B 的 IP;数据包顺着物理网络到达宿主机 B 后,再被解封装还原成原始数据包转交给目标容器。整个过程像是在快递盒外面再套一个更大的快递箱,写的是宿主机之间的地址,内层才是容器之间的真实通信内容。

Overlay 网络和 Docker Swarm 深度集成。当你执行 docker swarm init 初始化 Swarm 集群后,可以创建 attachable 的 Overlay 网络,服务通过这个网络跨主机通信。Overlay 网络自带服务发现功能——一个服务名可以解析到任意一个运行该服务的副本上,就像呼叫中心打了总机号自动转到任何一个空闲坐席。在实际微服务架构中,Overlay 网络是跨主机的默认选择。它的代价是封装/解封装带来的额外 CPU 开销和些许延迟,但换来的跨主机透明通信和灵活的服务编排,在分布式系统中是值得的。

VXLAN 封装:UDP(hostA→hostB)⏟外层信封⊃ETH(containerA→containerB)⏟内层原包\text{VXLAN 封装}: \quad \underbrace{\text{UDP}_{(host_A \to host_B)}}_{\text{外层信封}} \supset \underbrace{\text{ETH}_{(container_A \to container_B)}}_{\text{内层原包}}
Overlay 网络把容器数据包套进一个“外层信封”,信封上写的是宿主机地址——物理网络只看到信封,容器看到的是原信。
  1. 内层原包容器之间真实的以太网帧,包含源容器和目标容器的 MAC 与 IP。
    ETHinner={srcMAC,dstMAC,payload}\text{ETH}_{inner} = \{ \text{src}_{MAC}, \text{dst}_{MAC}, \text{payload} \}
  2. 外层封装原包被包装为 UDP 包,走宿主机之间的三层网络路径。
    UDPouter={srchostIP,dsthostIP,VNI}\text{UDP}_{outer} = \{ \text{src}_{hostIP}, \text{dst}_{hostIP}, \text{VNI} \}
  3. VNI 标识VXLAN Network Identifier 用于区分不同 Overlay 网络,防止不同小区的流量串台。
    VNI∈[1,224−1]\text{VNI} \in [1, 2^{24} - 1]
  4. 解封装目标宿主机收到外层包后剥离信封,将内层原包交给目标容器。
    decapsulate:UDPouter→ETHinner→containerB\text{decapsulate}: \text{UDP}_{outer} \to \text{ETH}_{inner} \to \text{container}_B

Macvlan网络:在主干道上开专属出口

前面的网络模式中,容器的流量要么走小区的共享主干道(bridge),要么走绕城高架(overlay)。Macvlan 网络则另辟蹊径:它直接在宿主机的物理网卡上建立多个带独立 MAC 地址的虚拟子接口,每个容器绑定一个虚拟子接口和独立 MAC 地址。这就好比市政公路修到了小区门口,每个住户直接在马路边开一个专属出口,出门就上大路,不经过任何小区内部道路。

Macvlan 模式最大的特点是“直接”。容器拥有跟宿主机同一网段的真实 IP,数据包从物理网卡进出,不经过网桥转发、不经过 NAT 伪装。从外部网络看,每个容器就像一个真实的物理主机,可以直接被外部设备访问,性能也接近原生网络。这在部署那些需要对外提供直接访问的网络设备类应用(如负载均衡器、防火墙)时非常有价值。

Macvlan 网络的“直接”也带来了限制:宿主机和 Macvlan 容器之间默认无法直接通信(因为虚拟 MAC 和物理 MAC 的归属不同),大量容器时物理网卡的 MAC 地址表会爆表,而且网络拓扑对底层网络设施(交换机、网关)有较强依赖。使用 Macvlan 前要像规划专用车道一样,仔细确认 IP 规划与外部网络的兼容性。一旦配置得当,你会得到一个性能卓越、拓扑直观的网络环境——容器就像沿街旺铺,路人直接进店,无需经过物业前台。

Macvlan:MACvirtual≠MACphysical,IPcontainer∈LANsubnet\text{Macvlan}: \quad \text{MAC}_{virtual} \neq \text{MAC}_{physical}, \quad \text{IP}_{container} \in \text{LAN}_{subnet}
Macvlan 给每个容器分配独立的虚拟 MAC 地址和局域网真实 IP,容器在网络世界里以“独立主机”的身份直接接入外部网络。
  1. 虚拟 MAC网卡驱动为每个子接口生成独立的 MAC 地址,用于二层隔离。
    MACvi∉{MAChost,MACvj}(i≠j)\text{MAC}_{v_i} \notin \{ \text{MAC}_{host}, \text{MAC}_{v_j} \} \quad (i \neq j)
  2. 共享物理网卡所有虚拟子接口复用同一块物理网卡收发数据。
    eth0→{mac0,mac1,mac2,…}\text{eth0} \to \{ \text{mac0}, \text{mac1}, \text{mac2}, \ldots \}
  3. 同网段 IP容器直接使用局域网网段的 IP,无需 NAT。
    IPcontainer∈LANsubnet∩routableLAN\text{IP}_{container} \in \text{LAN}_{subnet} \cap \text{routable}_{LAN}

端口映射:物业总机的转接服务

外部网络如何访问容器内运行的服务?Bridge 网络中的容器处于私有网段,外部设备既不知道这个网段的存在,也没有路由到达它。这时候就需要“物业总机转接”——端口映射。你把宿主机的某个端口(如 8080)绑定到容器的某个端口(如 80),外部请求访问宿主机 8080 端口时,Docker 内核的 iptables 规则会自动把流量转发到目标容器的 80 端口。这个机制在技术上叫 DNAT(Destination Network Address Translation,目标地址转换)。

docker run -p 8080:80 nginx 这条命令的实际效果是:在宿主机 iptables 的 NAT 表中添加一条规则——凡是到达宿主机 8080 端口的 TCP 流量,目标地址都改写为特定容器的 IP:80。外部用户完全感知不到这个过程,他们以为自己访问的就是宿主机的 8080 端口,就像外边打电话给物业总机说“麻烦转 302 室”,总机转接完成后双方直接通话。

除了 -p 8080:80 这种显式映射,Docker 还支持 -P 自动分配一个随机宿主机端口,相当于总机给每个房间动态分配外线号码。但动态端口不好对外暴露——你需要先查端口再告诉别人。实际生产环境中,你应该尽量用显式映射并在防火墙层面对外网 IP 做限制,避免任何 IP 都能访问总机转接服务。端口映射是把“小区里的私有房间”变成“城市里的可访问门面”的桥,桥的设计合理与否,直接决定了服务的可达性与安全性。

DNAT:0.0.0.0:8080⏟宿主机入口→iptables172.17.0.2:80⏟容器内部\text{DNAT}: \quad \underbrace{0.0.0.0:8080}_{\text{宿主机入口}} \xrightarrow{\text{iptables}} \underbrace{172.17.0.2:80}_{\text{容器内部}}
端口映射是 iptables 在宿主机上做的地址改写:外部请求目的地从“宿主机 8080”改写为“容器 172.17.0.2:80”,像物业总机把外线电话转接到房间内线。
  1. 宿主机入口外部流量到达宿主机 IP 的指定端口,这是门面的招牌位置。
    host:0.0.0.0:8080\text{host}: 0.0.0.0:8080
  2. 目标改写iptables DNAT 规则将目标 IP 和端口替换为容器地址。
    rewritedst=172.17.0.2:80\text{rewrite}_{dst} = 172.17.0.2:80
  3. 源地址转换容器回包需要经过源地址伪装,让外部设备认为回复来自宿主机。
    SNAT:srccontainer→srchost\text{SNAT}: \text{src}_{container} \to \text{src}_{host}

容器DNS:城市电话簿

在自定义 bridge 网络中,容器可以通过容器名互相访问——ping db 能通,curl http://web:8080 能通。这是怎么做到的?Docker 内置了一个 DNS 服务,就像每个小区有自己的电话簿。当你在自定义网络中启动容器时,Docker 会在 /etc/hosts 和内部 DNS 记录中登记容器名到 IP 的映射。容器内发起域名解析请求时,Docker 的嵌入式 DNS 服务(127.0.0.11)会响应解析,返回目标容器的 IP 地址。

为什么默认 bridge 网络没有这个电话簿?因为默认 bridge 网络是 Docker 早期的兼容方案,为了向后兼容没有启用内置 DNS。而自定义网络天生就带 DNS 解析功能。这就是为什么生产环境推荐使用自定义网络——容器重启后 IP 变了不重要,只要容器名不变,其他容器通过 DNS 查询总能拿到新的 IP。

容器 DNS 还支持自定义配置:--dns 可以指定额外的上游 DNS 服务器,--dns-search 可以添加域名搜索后缀。如果容器的 DNS 配置不正确,最典型的症状是 ping 不通域名但 ping 通 IP——就像你拿着过期的电话簿打电话,号码名字对不上。在微服务架构中,容器 DNS 是最基础的服务发现机制。它让服务之间通过逻辑名字互相引用,IP 变成了可有可无的细节——就像你联系朋友只需记住名字,不需要记住他搬了多少次家的地址。

DNS 解析:web⏟容器名→127.0.0.11172.17.0.2⏟当前IP\text{DNS 解析}: \quad \underbrace{\text{web}}_{\text{容器名}} \xrightarrow{\text{127.0.0.11}} \underbrace{172.17.0.2}_{\text{当前IP}}
容器 DNS 把“容器名 → IP”的映射关系动态维护在嵌入式 DNS 服务器(127.0.0.11)中,容器名就像电话簿里的姓名,IP 就是不断变化的电话号码。
  1. 查询发起容器内进程请求解析 web,DNS 请求被系统设置为发给 127.0.0.11。
    query:web→127.0.0.11\text{query}: \text{web} \to 127.0.0.11
  2. 内置解析Docker 的嵌入式 DNS 服务器在其租约表中查找容器名对应的 IP。
    DNStable:web↦172.17.0.2\text{DNS}_{table}: \text{web} \mapsto 172.17.0.2
  3. 动态更新容器重启导致 IP 变化时,DNS 记录自动更新,调用方查询总能拿到新 IP。
    restart:web↦172.17.0.5(自动更新)\text{restart}: \text{web} \mapsto 172.17.0.5 \quad (\text{自动更新})

AI 应用场景

微服务架构中的服务发现与通信

阶段:模型推理服务的部署与编排

解决的问题:AI 系统的模型推理服务通常包含多个组件——API 网关、推理引擎、特征存储、模型版本管理服务。传统部署方式中,组件之间通过写死的 IP 地址通信,一旦服务重启或迁移,IP 变化导致链路断裂。借助 Docker 自定义 bridge 网络和容器 DNS,组件之间通过服务名互访,IP 变化无感知。

具体案例:某 AI 视觉识别平台将人脸检测服务和特征比对服务部署在同宿主机上,通过 docker network create vision-net 让两个容器互联,API 网关通过 http://face-detector:8080/ 调用检测服务。当检测服务因模型更新而重启并获得新 IP 时,网关无需修改任何配置,因为 Docker DNS 自动更新了名字解析。

跨主机 AI 推理集群的 Overlay 组网

阶段:分布式训练与多节点推理的集群通信

解决的问题:单台物理机的 GPU 资源有限,大模型推理往往需要多台机器协同。Overlay 网络让分布在多台机器上的推理容器透明互联,配合负载均衡实现流量调度。

具体案例:某大模型服务商使用 Docker Swarm 集群部署 LLM 推理服务,通过 Overlay 网络将 3 台 GPU 服务器上的 12 个推理容器组成虚拟内网,客户端通过服务名访问统一的负载均衡入口,容器状态变化由 Swarm 自动调度,Overlay 网络屏蔽了跨主机通信的复杂性。

高性能模型服务的 Host 网络加速

阶段:低延迟在线推理服务的网络优化

解决的问题:在线推荐系统要求端到端延迟低于 50ms,Bridge 网络的 NAT 和 veth 转发带来额外开销。Host 网络的直通路径能显著压缩网络延迟。

具体案例:某广告推荐系统将特征读取组件改用 Host 网络部署在缓存服务器上,省去 NAT 转换的微秒级延迟。由于端口规划提前留白(统一使用 9500-9600 段),多个 Host 网络容器没有端口冲突。

Python 演示

python64 行
#!/usr/bin/env python3"""Docker 容器桥接网络模拟器:演示 bridge 网络的 IP 分配、DNS 解析与数据包转发"""  class Container:    def __init__(self, name, ip, network):        self.name = name        self.ip = ip        self.network = network        self.dns_table = {}     def register_dns(self, alias, target_ip):        self.dns_table[alias] = target_ip     def resolve(self, alias):        return self.dns_table.get(alias, None)     def send(self, dst_name, payload):        dst_ip = self.resolve(dst_name)        if not dst_ip:            print("[%s] DNS 解析失败: %s 不在电话簿中" % (self.name, dst_name))            return        print("[%s] DNS: %s -> %s" % (self.name, dst_name, dst_ip))        print("[%s] 数据包 '%s' 经 %s 网络发送到 %s" % (self.name, payload, self.network, dst_ip))        return dst_ip  class BridgeNetwork:    def __init__(self, subnet, gateway):        self.subnet = subnet        self.gateway = gateway        self.containers = {}        self._ip_counter = 2     def create_container(self, name):        ip = "%s.%d" % (self.subnet, self._ip_counter)        self._ip_counter += 1        c = Container(name, ip, "bridge")        self.containers[name] = c        for other in self.containers.values():            other.register_dns(c.name, c.ip)        return c     def print_topology(self):        print("=== bridge 网络拓扑 ===")        print("网桥: %s, 网关: %s" % (self.subnet, self.gateway))        for name, c in self.containers.items():            print("容器: %s, IP: %s" % (name, c.ip))        print()  def main():    net = BridgeNetwork("172.17.0", "172.17.0.1")    web = net.create_container("web")    app = net.create_container("app")    db = net.create_container("db")    net.print_topology()    web.send("db", "SELECT * FROM users LIMIT 10")    app.send("web", "GET /api/v1/models")    web.send("unknown", "ping")  if __name__ == "__main__":    main()
目的:用纯 Python 模拟 Docker bridge 网络——包括 IP 地址分配、容器名注册、DNS 解析和跨容器数据包发送流程,帮助你直观理解容器间通信的时序。
  1. class Container:定义容器对象,包含名字、IP、所属网络和 DNS 缓存表,等价于一个拥有独立门牌的住户。
  2. self.dns_table = {}容器的“电话簿”,记录容器名到 IP 的映射关系。
  3. def resolve(self, alias):DNS 查询方法,在电话簿中查找名字对应的 IP,找不到返回 None。
  4. class BridgeNetwork:模拟 Docker 的 bridge 网络管理器,负责 IP 分配和 DNS 注册。
  5. self._ip_counter = 2模拟从 172.17.0.2 开始分配 IP(.1 被网关占用)。
  6. def create_container(self, name):创建容器并自动分配下一个可用 IP,然后注册到所有容器的 DNS 表中。
  7. for other in self.containers.values():新容器加入后,给所有现有容器更新“电话簿”,模拟 Docker 内置 DNS 的动态更新。
  8. web.send("db", ...)演示容器间通过名字通信,而不是硬编码 IP。
  9. web.send("unknown", "ping")演示 DNS 解析失败时的行为,帮助理解网络排障。

常见误区

误区很多初学者认为 Docker 容器默认都连接在同一个网络上,容器之间天然互通,就像住在同一栋楼的所有住户共享所有走廊。

正解Docker 默认确实为所有容器创建一个 bridge 网络,但容器之间并不能自动互通。默认 bridge 网络未启用容器间通信(ICC,Inter-Container Communication)的自动配置,且每个容器默认有自己的网络隔离。更关键的是,同一个默认 bridge 网络上的容器虽然共享同一网段,但没有内置 DNS,无法用容器名互访,只能通过 IP 访问。生产环境应当创建自定义 bridge 网络,并显式加入需要互通的容器。

为何常见:docker run 后立即能上网、能访问外网,让初学者误以为网络配置是“开箱即通”的,忽略了容器网络需要显式规划。

误区有人以为 Host 网络快是因为“没有虚拟化层”,容器像是物理机上的普通进程在跑,认为 Bridge 网络是“虚拟机”性能差距巨大。

正解Bridge 网络的开销主要来自 veth 设备的转发和 NAT 地址转换,而不是“虚拟化”。veth 转发在内核态完成,开销不算大;真正的性能损耗来自 iptables 规则匹配和连接跟踪(conntrack)。Host 网络省去了这些开销,但容器不隔离网络。在某些高并发场景下 Bridge 网络的吞吐差异可能在 5%-15% 之间,并非“量级差距”。选型时要权衡隔离性、安全性和性能,而非盲目选 Host。

为何常见:初学者把“虚拟网卡”误认为“虚拟机”级别的性能损耗,高估了 Bridge 网络的开销。

误区很多人以为 `-p 8080:80` 是把宿主机的 8080 端口“映射”成一个新的设备,或者“穿透”进容器,仿佛容器真的在监听 8080 端口。

正解-p 8080:80 的本质是在宿主机 iptables 中添加 DNAT 规则:收到宿主机 8080 端口的包后,把目标地址改写为 容器IP:80。容器内部的进程始终监听 80 端口,它并不知道外部连接是经过转接的。这就是“物业总机转接”的本质——总机把外线转到内线,但内线住户从来不需要知道外线号码。

为何常见:人们使用 docker port 命令看到“8080->80“的显示,容易理解为端口”连通“或”复制”。

误区有人以为 `docker network create -d overlay mynet` 之后,任意主机上的容器都能直接通过 Overlay 网络互访,就像互联网一样自动打通。

正解Overlay 网络依赖 Docker Swarm 集群的共识机制来管理网络成员关系和 key-value 存储。只有加入同一 Swarm 集群的节点才能创建和使用 Overlay 网络。此外,生产环境创建 Overlay 网络还需要加密选项和自定义控制平面网络,否则集群管理和数据面通信可能暴露风险。Overlay 不是“公网隧道”,而是“同一管理域内的高架桥”。

为何常见:Docker 命令行让创建 Overlay 网络显得很简单,使用者忽略了背后 Swarm 控制面的必要角色。

误区有些使用者创建 Macvlan 网络后,发现容器能访问外网和别的机器,但 ping 不通宿主机。他们以为配置有误。

正解Macvlan 模式下,宿主机和容器共享同一物理网卡的 MAC 地址空间。Linux 的协议栈默认不会处理发给“虚拟子接口 MAC”的流量(该 MAC 归容器所有),所以宿主机无法直接访问 Macvlan 容器。要解决这个问题,需要额外创建 macvlan 子接口并配置 IP,或者改用 ipvlan 模式。这是 Macvlan 网络的固有特性,不是 bug——就像市政公路上住户的专属车道,物业保安走不过去是设计使然。

为何常见:人按照 Bridge 网络的经验,认为同网段的接口应该彼此互通,忽略了 MAC 层隔离的机制差异。

面试题

Q1. Docker 创建容器时默认连接到哪个网络驱动?

单选题 · easy

  • A.host
  • B.bridge← 正确答案
  • C.overlay
  • D.macvlan

答案:B。Docker 默认使用 bridge 网络驱动。host 需要显式指定 --network host,overlay 用于跨主机集群通信,macvlan 需要手动创建网络。默认网络相当于小区默认规划的主干道方案,bridge 就是这套“默认规划”,保证开箱即用。

Q2. docker run -p 8080:80 nginx 命令中,8080 表示什么?

单选题 · easy

  • A.容器内的服务端口
  • B.宿主机上暴露的服务端口← 正确答案
  • C.nginx 实际监听的端口
  • D.Docker 内部代理端口

答案:B。-p 的语法是 宿主机端口:容器端口,8080 是宿主机监听的外部入口,80 是容器内 nginx 监听的端口。就像物业总机的外线号码是 8080、转接的内线是 80,外线号码只是入口,不是内线本身。

Q3. 以下哪个网络模式下,容器直接复用宿主机的网络命名空间?

单选题 · mid

  • A.bridge
  • B.overlay
  • C.host← 正确答案
  • D.macvlan

答案:C。host 模式下容器不创建独立网络命名空间,IP、端口空间、路由表全部与宿主机共享,因此性能好但隔离性差。bridge 有独立命名空间,overlay 靠 VXLAN 封装,macvlan 是独立 MAC 虚拟子接口。

Q4. 关于 Docker 自定义 bridge 网络,下列说法正确的有?

多选题 · mid

  • A.支持容器名 DNS 解析← 正确答案
  • B.容器重启后 IP 不变
  • C.可指定子网和网关← 正确答案
  • D.只能在同一宿主机通信← 正确答案

答案:ACD。自定义 bridge 网络内置 DNS,容器名自动解析正确;创建时可指定 --subnet 和 --gateway 手动规划网段正确;bridge 网络是单机网络,跨主机需 overlay,正确。容器重启后 IP 可能变化(除非指定固定 IP),B 错误。自定义网络如同规划完善的小区,有电话簿、有门牌规划,但小区之间没有高架桥,无法跨区互访。

Q5. Overlay 网络的核心封装协议是什么?

单选题 · mid

  • A.VLAN
  • B.VXLAN← 正确答案
  • C.GRE
  • D.IPsec

答案:B。Docker Overlay 网络基于 VXLAN(Virtual Extensible LAN)技术,通过 UDP 封装让二层帧在三层网络上传送。VXLAN 用 24 位 VNI 区分不同网络段,支持超过 1600 万个隔离网络。GRE 和 IPsec 是其他隧道技术,VLAN 是传统二层隔离手段。

Q6. 端口映射的 DNAT 转发过程中,以下哪些环节涉及 iptables 规则?

多选题 · hard

  • A.外部请求目标地址改写为容器 IP← 正确答案
  • B.容器回包时源地址改写为宿主机 IP← 正确答案
  • C.容器监听端口的创建
  • D.同网段容器间的二层转发

答案:AB。DNAT 规则负责入口流量目标地址改写,SNAT/MASQUERADE 规则负责回包的源地址伪装,二者共同完成端口映射的双向通信。容器监听端口由容器内进程负责,不涉及宿主机 iptables;同网段容器间走网桥二层转发,不经过 NAT。端口映射像总机转接——外线打进来要转内线(DNAT),内线打出去要显示总机号(SNAT)。

Q7. 在 Macvlan 模式下,宿主机无法直接 ping 通容器的最主要原因是什么?

单选题 · hard

  • A.容器没有 IP 地址
  • B.容器的虚拟 MAC 与宿主机物理 MAC 隔离← 正确答案
  • C.iptables 拦截了流量
  • D.容器默认关闭了 ICMP 协议

答案:B。Macvlan 给容器分配独立的虚拟 MAC 地址。宿主机内核收到目标 MAC 为容器虚拟 MAC 的包时,不会将其交给本地网络栈(因为该 MAC 不属于宿主机网卡),导致宿主机无法直接访问容器,除非额外配置 macvlan 子接口。这是 MAC 层隔离决定的——容器像独立住户拥有自己的身份标识,宿主机这台“物业服务器”默认不认领不属于自己的信件。

Q8. Docker 内置 DNS 解析地址是多少?

单选题 · mid

  • A.8.8.8.8
  • B.114.114.114.114
  • C.127.0.0.11← 正确答案
  • D.172.17.0.1

答案:C。Docker 在容器内嵌入 DNS 解析器,监听 127.0.0.11。容器内 DNS 请求先发给 127.0.0.11,由 Docker 内置 DNS 查询容器名映射,查不到再转发给上游 DNS。这个地址就像小区总机的内线号码,住在小区里的住户拨这个号就能查到邻居的门牌号和电话。

Q9. Host 网络模式的优势和风险包括哪些?

多选题 · mid

  • A.网络延迟低← 正确答案
  • B.容器间端口隔离
  • C.无 NAT 转换开销← 正确答案
  • D.不同容器可使用同一宿主机端口

答案:AC。Host 模式省去 veth 转发和 NAT,延迟低、无 NAT 开销,A 和 C 正确。但容器共享宿主机端口空间,无法做到端口级隔离;不同容器不能在同一宿主机端口监听,B 和 D 错误。Host 模式像沿街商铺——开门就是大路、进出方便,但门牌号是公用的,两家店不能共用同一个门牌。

Q10. 关于默认 bridge 网络与自定义 bridge 网络的区别,理解正确的是?

单选题 · hard

  • A.默认 bridge 网络也有内置 DNS
  • B.自定义网络可指定子网但不能设置网关
  • C.默认 bridge 网络容器可用 IP 直连,但没有 DNS 解析← 正确答案
  • D.自定义网络不支持固定 IP 分配

答案:C。默认 bridge 网络为了向后兼容,未启用内置 DNS,容器只能用 IP 互访,C 正确。自定义网络支持内置 DNS、可指定子网与网关、支持固定 IP 分配,A、B、D 错误。默认 bridge 像老小区没有电话簿,住户只能靠记门牌号;自定义网络是现代化小区,打名字就能找到人。