1. 容器网络初探

容器网络是容器化平台中最容易被低估、却最容易在真实环境中出问题的一层。要理解 Kubernetes 的网络模型,最有效的路径不是直接背诵 CNI 插件清单,而是先回到起点:一个容器启动后,它的网络接口从哪里来?它如何与同一宿主机上的其他容器通信?又如何与外部世界通信?Docker 的默认桥接网络正是回答这些问题的最小完整样本。

一、容器网络要解决的基本需求

无论使用 Docker 还是 Kubernetes,容器网络都必须回答四类问题:

  • 地址分配:每个容器需要至少一个 IP 地址,且同一网络内不能冲突。
  • 同宿主机互通:同一台机器上的两个容器要能通过 IP 直接通信。
  • 跨宿主机互通:不同机器上的容器要能通信,这是集群网络的核心难点。
  • 与外部通信:容器要能访问外网,外部也要能按需访问容器提供的服务。

Docker 默认桥接网络(bridge)解决了前两个问题,并借助 NAT 部分解决第四个问题;但它没有解决跨宿主机通信,也没有提供稳定的服务发现。这正是 Kubernetes 网络模型要补齐的部分。

二、Docker 默认桥接网络的工作机制

当 Docker 守护进程启动时,会在宿主机上创建一块名为 docker0 的虚拟网桥,默认网段通常是 172.17.0.0/16。每启动一个使用默认 bridge 网络的容器,Docker 会创建一对 veth(虚拟以太网)设备:一端留在容器的网络命名空间内,命名为 eth0;另一端接入宿主机的 docker0 网桥。容器从 172.17.0.0/16 中分配一个 IP,网关指向 docker0 的地址(通常为 172.17.0.1)。

可以用下面的命令观察这一结构:

# 查看 docker0 网桥及其地址
ip addr show docker0

# 启动一个测试容器
docker run -d --name web nginx:alpine

# 查看容器 IP 与 veth 配对
docker inspect -f '{{.NetworkSettings.IPAddress}}' web
ip link show master docker0

假设容器获得地址 172.17.0.2,此时在宿主机上执行 ping 172.17.0.2 可以通,容器内 ping 172.17.0.1 也可以通。这说明二层桥接与同网段转发已经生效。但要注意:默认 bridge 网络中的容器之间只能通过 IP 互访,不能通过容器名解析,因为默认 bridge 不提供内嵌 DNS。若要让容器按名称互访,需要自定义 bridge 网络:

docker network create --subnet 172.20.0.0/16 appnet
docker run -d --name api --network appnet nginx:alpine
docker run -it --rm --network appnet alpine ping -c 2 api

在自定义网络中,Docker 会启用内嵌 DNS,容器名 api 可被解析为对应 IP。这是从“能通”走向“好用”的关键一步。

三、从单机桥接走向集群网络

Docker 默认桥接的局限在集群场景下会迅速暴露。假设有两台宿主机 A(172.17.0.0/16)和 B(172.17.0.0/16),A 上的容器地址是 172.17.0.2,B 上的容器地址也可能是 172.17.0.2。两个地址完全相同,且宿主机之间没有为这些容器网段建立路由,跨机通信无法直接完成。此外,容器重建后 IP 会变化,调用方无法依赖固定地址。

Kubernetes 网络模型对此给出了三条明确约束:

  • 所有 Pod 之间可以不经过 NAT 直接通信。
  • 所有节点与所有 Pod 之间可以不经过 NAT 直接通信。
  • Pod 看到的自身 IP 与其他 Pod 看到的该 Pod IP 一致。

这意味着集群必须为每个 Pod 分配全局唯一、可路由的地址,并由 CNI 插件负责把数据包从源 Pod 送到目标 Pod,无论两者是否在同一节点。Docker 的 bridge 只覆盖了“同宿主机同网段”这一小部分,Kubernetes 则要求把这一能力扩展到整个集群。

Docker 默认桥接网络结构(单宿主机) 宿主机 Host docker0 网桥 172.17.0.1/16 容器 web eth0: 172.17.0.2 veth → docker0 nginx:alpine 容器 db eth0: 172.17.0.3 veth → docker0 mysql:8 同宿主机容器互通:172.17.0.2 ↔ 172.17.0.3 外部网络 / Internet 经 iptables NAT 出站 MASQUERADE 局限:跨宿主机不通 网段重叠、无全局路由 K8s 网络模型要求 Pod IP 全局唯一、免 NAT 直连

四、Kubernetes 网络模型的关键转变

从 Docker 默认桥接到 Kubernetes 网络模型,本质上是把“每台宿主机各自为政的私有网段”升级为“整个集群统一编址、统一路由的扁平网络”。在 Kubernetes 中,每个 Pod 拥有独立 IP,同一 Pod 内的多个容器共享该 IP 和网络命名空间,通过 localhost 互访;不同 Pod 之间则像同一局域网内的主机一样直接通信。

这一模型并不由 kubelet 自己实现,而是通过 CNI(Container Network Interface)规范交给插件完成。kubelet 在创建 Pod 沙箱时调用 CNI 插件,插件负责:分配 IP、配置 veth 与网桥或路由、设置路由规则,并在 Pod 删除时回收资源。常见的 Calico、Flannel、Cilium 都是这一规范的不同实现。

理解要点:Docker 默认 bridge 是“单机二层桥接 + NAT 出站”的模型;Kubernetes 网络模型是“集群级扁平三层可达”的模型。前者是后者的局部特例,后者是前者在分布式场景下的扩展与规范化。

五、一个可对照的观察实验

为了把上述差异落到可观察的现象上,可以分别启动 Docker 容器和 Kubernetes Pod,对比它们的地址与路由:

# Docker:查看默认 bridge 容器地址
docker run -d --name c1 nginx:alpine
docker inspect -f '{{.NetworkSettings.IPAddress}}' c1
# 典型输出:172.17.0.2

# Kubernetes:查看 Pod 地址
kubectl run nginx --image=nginx:alpine
kubectl get pod nginx -o wide
# 典型输出:10.244.1.5,节点为 node-1

# 在 Pod 内查看路由
kubectl exec nginx -- ip route
# 默认路由通常指向 10.244.1.1 或节点网关

可以看到,Docker 容器的地址落在宿主机本地网段,而 Kubernetes Pod 的地址来自集群统一的 Pod CIDR(例如 10.244.0.0/16),并由 CNI 插件保证该地址在整个集群内可路由。判断网络是否正常,最直接的方式是从一个 Pod 直接 ping 另一个节点上的 Pod IP,若不通,则问题通常出在 CNI 插件、路由表或节点间网络策略上,而不是应用本身。