提到 Kubernetes (K8s),很多刚接触的朋友第一反应是“容器编排”,但真正让 K8s 变得强大且复杂的,其实是它的网络模型。你可以把 K8s 想象成一个巨大的、动态变化的城市。在这个城市里,每一个 Pod(最小部署单元)就像是一间独立的小公寓。
如果这些公寓之间不能互相打电话,或者外面的人找不到这些公寓的地址,那这个城市就瘫痪了。K8s 的核心挑战就在于:如何在成千上万个不断创建和销毁的“小公寓”之间,建立稳定、高效、安全的通信网络。
今天,我们不谈枯燥的理论定义,而是通过一个真实的“城市交通规划”视角,带你深入理解 K8s 的网络底层逻辑,以及如何解决最头疼的跨节点通信和服务发现问题。
一、 基石:为什么 Pod 网络必须“扁平化”?
在传统的虚拟机时代,每个 VM 都有独立的 IP 段,跨 VM 通信需要 NAT(网络地址转换),这就像是每栋楼都有自己的邮编,快递得层层转交。但在 K8s 的设计哲学中,Google 和 CNCF 社区定下了一个非常激进但也极其重要的原则:
Pod 之间可以直接通信,无需 NAT;每个 Pod 都可以独立访问互联网;同一节点上的所有 Pod 可以互相看到对方。
这意味着什么?意味着 K8s 要求整个集群拥有一个扁平的大二层网络(Layer 2 Network)。无论你的 Pod 跑在 Node A 还是 Node B,它都仿佛直接插在同一根网线(交换机)上。
为了实现这个“魔法”,K8s 引入了 CNI (Container Network Interface) 插件。常见的如 Calico, Flannel, Cilium, Weave 等。它们的工作流程大致如下:
- IP 分配:当一个新的 Pod 被调度到某个节点时,CNI 插件会从预定义的 IP 池中分配一个唯一的 IP 地址给这个 Pod。
- 虚拟网卡对:在宿主机的 Linux 内核中,创建一对虚拟网卡(veth pair)。一端放入 Pod 的命名空间(作为 eth0),另一端放在宿主机的网络命名空间中(通常命名为 veth+随机字符串)。
- 桥接或路由:CNI 插件会将宿主机上的那一端网卡连接到本地的网桥(如 cni0)或通过路由规则指向其他节点。
举个栗子:
假设你有两个节点 Node-1 和 Node-2。
Pod-A在Node-1上,IP 是10.244.1.5/24。Pod-B在Node-2上,IP 是10.244.2.3/24。
对于 Pod-A 来说,它认为 Pod-B 就在隔壁房间,直接发报文 10.244.2.3 即可。它不知道也不关心中间隔了多少公里,也不关心 Pod-B 到底在哪台物理机上。这种“透明通信”是 K8s 网络体验良好的基础。
二、 跨节点隔离:隧道与路由的艺术
既然 Pod 之间要像在同一台机器上一样通信,那么当数据包离开 Node-1,进入物理网络,再到达 Node-2 时,必须经历一个“封装”或“路由”的过程。不同的 CNI 插件采用不同的策略来解决这个跨节点网络隔离与转发的问题。
1. Flannel 的 VXLAN 方案(简单粗暴的隧道)
Flannel 是最早流行的 CNI 之一,它主要使用 VXLAN (Virtual Extensible LAN) 技术。
- 原理:当
Node-1上的Pod-A想发给Node-2上的Pod-B时,Node-1的 kube-proxy 或 flannel 守护进程会发现目标 IP 不在本地子网。于是,它把原始的数据包(源 IP: 10.244.1.5, 目的 IP: 10.244.2.3)封装在一个新的 UDP 数据包里。 - 外层头:外层源 IP 是
Node-1的物理 IP,外层目的 IP 是Node-2的物理 IP。 - 传输:这个 UDP 包通过物理网络传输到
Node-2。 - 解封装:
Node-2收到后,剥掉外层头,还原出原始的内层数据包,交给Pod-B。
缺点:增加了 CPU 开销和网络延迟,因为要进行加解密和封装解封装。但在大多数场景下,性能损耗可接受。
2. Calico 的 BGP 路由方案(高性能的路由)
Calico 则完全不走隧道,它利用 Linux 内核的路由表。
- 原理:Calico 的每个节点运行一个 BGP (Border Gateway Protocol) 客户端。它会把自己节点上所有 Pod 的 CIDR 宣告给集群中的其他节点。
- 路由表:当
Node-1想要访问Pod-B(10.244.2.3) 时,它查路由表,发现去往10.244.2.0/24的下一跳是Node-2的物理 IP。 - 直接转发:数据包不需要封装,直接以原始 IP 包的形式发送。
优点:零封装开销,性能极高,支持精细的网络策略(Network Policy)。 挑战:需要物理网络设备支持 BGP,或者依赖 Calico 的 IPIP 模式作为 fallback。
3. Cilium 的 eBPF 方案(未来已来)
Cilium 是目前最火的新星,它基于 Linux 内核的 eBPF (extended Berkeley Packet Filter)。
- 原理:eBPF 允许你在内核态运行沙箱程序,而不需要修改内核源码。Cilium 将网络策略、负载均衡、监控等功能全部下沉到内核态。
- 优势:速度极快,安全性极高,且能实现基于身份(Identity-based)而非 IP 的策略。
总结:无论哪种方案,最终目的都是为了让上层应用感觉不到底层的复杂性。作为开发者,你只需要知道:只要 Pod 有 IP,就能通。
三、 服务发现与负载均衡:Service 的秘密
解决了 Pod 之间的通信,接下来是更现实的问题:我怎么访问我的应用?
Pod 是短暂的。当一个 Pod 崩溃重启,或者水平扩展(HPA)增加副本时,它的 IP 地址会变。如果你硬编码 IP 去调用,一旦 IP 变了,服务就挂了。
这就是 Kubernetes Service 存在的意义。Service 是一个抽象概念,它定义了一组逻辑上的 Pod,并提供了一个固定的入口点(VIP - Virtual IP)。
Service 的类型
- ClusterIP (默认):只在集群内部可访问,提供一个稳定的内部 IP。
- NodePort:在每个节点上开放一个静态端口,外部可以通过
NodeIP:NodePort访问。 - LoadBalancer:云厂商提供的负载均衡器,自动暴露外网 IP。
- ExternalName:将服务映射到 DNS 名称。
核心机制:kube-proxy 与 iptables/IPVS
Service 如何将流量转发到后端 Pod?这主要靠 kube-proxy。kube-proxy 运行在每个节点上,它监听 API Server,感知 Service 和 Endpoint(后端 Pod 列表)的变化,并实时更新节点的网络规则。
模式一:iptables (传统模式)
这是早期的实现方式。kube-proxy 会生成大量的 iptables 规则。
- 过程:当数据包进入节点,匹配到 Service 的 ClusterIP 时,iptables 会将其 DNAT(目标地址转换)到某个后端 Pod 的 IP。
- 算法:随机或轮询选择后端 Pod。
- 问题:随着 Service 和 Pod 数量增加,iptables 规则会变得极其庞大,导致匹配效率下降,甚至出现丢包或延迟抖动。
模式二:IPVS (高性能模式)
IPVS (IP Virtual Server) 是 Linux 内核中的一个模块,专门用于负载均衡。
- 优势:IPVS 使用哈希表存储规则,查询效率为 O(1),远优于 iptables 的线性搜索。它支持更多的负载均衡算法(如 RR, LC, SH, DH 等)。
- 配置:在 K8s 中,只需设置
kube-proxy的mode: ipvs即可启用。
# kube-proxy configmap 示例
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
scheduler: rr # 轮询算法
strictARP: true
真实案例:从 Debug 看 Service 工作原理
假设你部署了一个 Nginx 服务:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
当你执行 curl http://nginx-service 时,发生了什么?
- DNS 解析:K8s 内部的 CoreDNS 将
nginx-service.default.svc.cluster.local解析为 Service 的 ClusterIP(例如10.96.0.15)。 - 流量进入:请求到达发起 Pod 所在节点的网桥。
- IPVS/iptables 拦截:节点的 kube-proxy 检测到目标 IP 是
10.96.0.15,命中 Service 规则。 - 负载均衡:
- 如果是 IPVS 模式,内核根据调度算法(如 Round Robin)选择一个后端 Pod IP(例如
10.244.1.5)。 - 执行 SNAT/DNAT,将目标 IP 替换为
10.244.1.5。
- 如果是 IPVS 模式,内核根据调度算法(如 Round Robin)选择一个后端 Pod IP(例如
- 路由转发:数据包根据本地路由表,发送到
10.244.1.5所在的节点(可能是本机,也可能是其他节点)。 - 交付 Pod:最终数据包进入
nginx-pod-1的 eth0 网卡,被 Nginx 容器处理。
关键点:整个过程对用户透明,且即使 nginx-pod-1 挂了,kube-proxy 会自动将其从后端列表中移除,流量自动流向健康的 Pod。
四、 进阶:Ingress 与外部流量入口
Service 的 ClusterIP 只能在集群内访问。如果需要从互联网访问你的应用,你需要 Ingress。
Ingress 不是 K8s 原生的资源类型,而是一个 API 对象,它需要一个 Ingress Controller(如 Nginx Ingress Controller, Traefik, HAProxy)来实现。
工作流程
- 用户请求:
www.myapp.com - DNS 解析:指向 LoadBalancer 提供的公网 IP(即 Ingress Controller 的 Service IP)。
- Ingress Controller:监听该 IP 的 80⁄443 端口,并根据 Ingress 资源中的规则进行反向代理。
- 规则匹配:
“`yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
”`- host: www.myapp.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80 - path: /web pathType: Prefix backend: service: name: web-service port: number: 80 - 转发:Ingress Controller 将
/api的请求转发给api-service,将/web的请求转发给web-service。
注意:Ingress 通常用于 HTTP/HTTPS 七层负载均衡。如果是 TCP/UDP 四层流量,建议使用 NodePort 或 LoadBalancer 类型的 Service。
五、 网络策略:安全隔离实战
有了网络,安全问题随之而来。K8s 提供了 NetworkPolicy 资源,可以实现微服务级别的细粒度访问控制。
场景:只允许前端访问后端
假设你有 frontend 和 backend 两个服务。默认情况下,所有 Pod 都可以互相通信。为了安全,我们希望阻止其他所有 Pod 访问 backend,只允许 frontend。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
解析:
podSelector: 作用于标签为app: backend的 Pod。policyTypes: [Ingress]: 表示这是一条入站规则。from: 只允许标签为app: frontend的 Pod 发起连接。ports: 限制目标端口为 8080。
重要提示:NetworkPolicy 只有在你的 CNI 插件支持的情况下才生效!Calico、Cilium、Weave Net 等都支持,但默认的 Flannel 不支持。如果你用了不支持的 CNI,写了 NetworkPolicy 也没用。
六、 常见故障排查指南
即使理解了原理,线上也会出问题。以下是几个高频场景及解决思路:
1. Pod 无法解析 Service 域名
- 现象:
nslookup nginx-service超时或失败。 - 检查:
- CoreDNS Pod 是否 Running?
kubectl get pods -n kube-system | grep coredns - CoreDNS 日志是否有报错?
kubectl logs <coredns-pod-name> -n kube-system - 检查
/etc/resolv.conf是否正确指向 K8s 内置 DNS。
- CoreDNS Pod 是否 Running?
2. 跨节点 Pod 不通
- 现象:
ping 10.244.2.3从 Node-1 的 Pod 发出,Node-2 的 Pod 收不到。 - 检查:
- CNI 插件是否正常工作?
kubectl get pods -n kube-system | calico/flannel - 物理网络防火墙是否放行了 CNI 使用的协议(如 VXLAN UDP 8472 端口,或 BGP TCP 179 端口)?
- 查看节点间的 ARP 表是否完整。
- CNI 插件是否正常工作?
3. Service 流量丢失
- 现象:访问 Service IP 无响应,但直接访问 Pod IP 正常。
- 检查:
- kube-proxy 是否在运行?
kubectl get pods -n kube-system | grep kube-proxy - IPVS 规则是否存在?
ipvsadm -Ln(需安装 ipvsadm 工具) - 检查 Endpoint 是否正确?
kubectl get endpoints <service-name>
- kube-proxy 是否在运行?
七、 结语:拥抱复杂性,享受简洁性
Kubernetes 的网络模型看似复杂,实则逻辑清晰:扁平 IP + 动态路由/隧道 + 标准化服务发现。
作为开发者,你不需要成为网络专家也能玩转 K8s,但你必须理解它的边界。当遇到网络问题时,不要盲目猜测,而是沿着 Pod -> Node -> CNI -> Physical Network -> Service -> Ingress 这条链路逐步排查。
记住,K8s 的强大在于它将基础设施的复杂性隐藏在了抽象层之下。当你能够自如地运用 Service 进行流量分发,利用 NetworkPolicy 保障安全,借助 Ingress 接入外部世界时,你就真正掌握了 K8s 网络的精髓。
希望这篇详解能帮你拨开云雾,建立起对 K8s 网络模型的立体认知。如果有具体的网络故障案例,欢迎随时交流,我们一起拆解分析。
