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 则要求把这一能力扩展到整个集群。
四、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 容器和 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 插件、路由表或节点间网络策略上,而不是应用本身。