嘿,朋友。如果你正在被 Kubernetes 的网络折腾得掉头发,或者看着一堆 NetworkPolicy 和 CNI 插件的名字感到头大,那你来对地方了。
我见过太多人把 K8s 网络当成黑盒,直到线上服务莫名其妙不通,才不得不去翻那一层层让人头秃的日志。其实,K8s 的网络设计逻辑非常优雅,只要理解了它底层的“游戏规则”,剩下的都是执行层面的小事。今天,我们不讲枯燥的定义,而是像拆解一台精密的瑞士手表一样,把你从 Pod 的 IP 一直讲到 Linux 内核的网络栈,最后再带你亲手排查几个真实的“疑难杂症”。
准备好了吗?让我们开始这段旅程。
一、 核心哲学:为什么 K8s 网络这么“奇怪”?
在深入技术细节之前,你必须先接受一个前提假设,这是 K8s 网络设计的基石,也是很多新手入坑的地方。
1.1 平坦的 IP 空间
在传统的物理机或早期虚拟机时代,IP 地址通常带有层级结构(比如 192.168.1.x 表示某机房、某机架、某服务器)。但在 Kubernetes 中,所有 Pod 都生活在同一个扁平的网络命名空间里。
这意味着什么?
- Pod A 可以直接
curlPod B 的 IP,无需任何 NAT 转换。 - 即使 Pod A 和 Pod B 运行在不同节点上,对它们自己来说,对方就像就在本机一样。
1.2 每个 Pod 一个 IP
这是最容易误解的一点。
- 误区:一个 Pod 可以配置多个 IP。
- 真相:一个 Pod 只分配一个唯一的 IP 地址。这个 IP 属于 Pod 内的所有容器(在 Pod 级别共享网络命名空间)。
如果你在一个 Pod 里跑了两个容器(比如 Sidecar 模式),它们看到的外部网络接口和 IP 是完全一样的。它们之间的通信不走网卡,而是直接通过 localhost。
1.3 网络与主机解耦
Pod 的网络资源(IP、veth pair、netns)由 CNI 插件 管理,而不是由 kubelet 直接管理。这给了社区巨大的灵活性,允许 Calico、Flannel、Cilium 等不同方案共存。
二、 解剖 Pod:从 Linux Namespace 说起
要理解 Pod 网络,首先要理解 Linux 内核的 Namespace(命名空间) 机制。
2.1 什么是 Network Namespace?
想象一下,你的 Linux 服务器是一栋大楼,每个 Network Namespace 就是大楼里的一个个独立隔音房间。
- 每个房间都有自己的网卡、IP 表、路由表、防火墙规则。
- 住在房间 A 的人看不到房间 B 的网络流量。
- 除非在房间之间修一条隧道(Tunnel)或者连接管(Pipe),否则两个房间完全隔离。
Pod 的本质,就是一组共享了 Network Namespace 的容器。
2.2 可视化实验:亲眼看看 Pod 的网络
让我们用一个简单的 Python 脚本来验证这一点。假设你有一个运行中的 Nginx Pod。
# 1. 获取 Pod 的 PID (进程ID)
POD_PID=$(kubectl get pod nginx-5d6b9f7c8-abc12 -n default -o jsonpath='{.status.containerStatuses[0].containerID}' | cut -d':' -f3)
# 注意:containerID 格式通常是 docker://xxx 或 containerd://xxx,这里需要转换成进程PID
# 更通用的方法是通过 cgroup
POD_CGROUP=$(kubectl get pod nginx-5d6b9f7c8-abc12 -n default -o jsonpath='{.status.containerStatuses[0].cgroupPath}')
NODE_IP=$(kubectl get node -o wide | grep Ready | head -1 | awk '{print $1}')
# 2. SSH 到该节点,进入 Pod 的网络命名空间查看
kubectl exec -n default nginx-5d6b9f7c8-abc12 -- ip addr
输出可能长这样:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
inet 127.0.0.1/8 scope host lo
2: eth0@if1024: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP
inet 10.244.1.5/24 brd 10.244.1.255 scope global eth0
你看,这里只有一个 eth0 接口,IP 是 10.244.1.5。这就是 Pod 的“脸面”。
2.3 veth Pair:连接两个世界的桥梁
既然 Pod 在网络隔离的房间(Namespace)里,那外面的世界(Node)怎么和它通信?答案是 veth pair(虚拟以太网设备对)。
veth pair 就像一根软水管的两头:
- 一头插在 Pod 的 Network Namespace 里(名字叫
eth0)。 - 另一头插在宿主机的 Network Namespace 里(名字叫
vethxxxx)。 - 数据从一头进去,必然从另一头出来。
[ Pod Network NS ] [ Node Network NS ]
+----------------+ +----------------+
| | | |
| eth0 <=====> | veth1234 | |
| |__________| |
+----------------+ (物理连接) +----------------+
| |
Pod IP Node IP
10.244.1.5 192.168.1.10
这就是为什么你可以从宿主机 ping 通 Pod IP,也可以从 Pod 里 curl 宿主机 IP。
三、 跨节点通信:CNI 插件的几种流派
当 Pod 在 Node A,目标 Pod 在 Node B 时,数据包怎么飞过去?这是 CNI 插件大显身手的地方。目前主流有三派:Flannel(简单隧道派)、Calico(路由+BGP派)、Cilium(eBPF派)。
3.1 Flannel: VXLAN 隧道模式
Flannel 是最早普及的方案之一,它的工作原理非常直观:封装。
当 Node A 上的 Pod 要访问 Node B 上的 Pod:
- 数据包从 Pod 发出,进入
eth0。 - 到达宿主机的
flannel.1虚拟网桥。 - Flannel 发现目标 IP 在另一个节点,于是把原始数据包包进一个新的 UDP 数据包里。
- 新数据包的源 IP 是 Node A 的物理 IP,目标 IP 是 Node B 的物理 IP。
- 通过物理网络发送。
- Node B 收到后,剥掉外层 UDP 头,还原原始数据包,交给目标 Pod。
优点:配置简单,支持覆盖任意底层网络(公有云、私有机房)。 缺点:因为多了封装和解封装,会有性能损耗(约 5-10%)。
3.2 Calico: IPIP + BGP 路由模式
Calico 走的是另一条路:不封装,只路由。
Calico 给每个 Pod 分配一个真实的 IP,并通过 BGP 协议将这些 IP 宣告给网络中的路由器。
- 当 Node A 要访问 Node B 的 Pod IP 时,Linux 内核的路由表会直接将数据包路由到 Node B 的物理网卡。
- 不需要 VXLAN 隧道,不需要 GRE 封装。
优点:性能极高,几乎无损耗,支持复杂的网络策略(NetworkPolicy)。 缺点:要求底层网络支持 BGP,这在某些公有云或受限网络环境中可能配置困难。
3.3 Cilium: eBPF 革命
Cilium 是近年来最火的选手,它完全抛弃了 iptables 和传统的 veth 模式,转而使用 Linux 内核的 eBPF(扩展伯克利包过滤器)。
eBPF 允许你在不修改内核源码的情况下,在内核空间运行沙箱程序。Cilium 用 eBPF 程序来:
- 处理 Pod 的 IP 分配。
- 实现负载均衡(替代 kube-proxy)。
- 实施网络策略(比 iptables 更快、更灵活)。
为什么 eBPF 这么快? 传统的 iptables 规则是链式结构的,随着规则增多,查找时间变长(O(n))。而 eBPF 可以将逻辑编译成机器码直接运行在内核中,且查找复杂度是 O(1)。
四、 Service 网络:从 kube-proxy 到 eBPF
Pod 的 IP 是易变的(重启就变),所以我们有了 Service,一个稳定的虚拟 IP(Cluster IP)。但 Service 是怎么把流量转发到后端 Pod 的呢?这里有两个关键组件:kube-proxy 和 iptables/IPVS。
4.1 kube-proxy 的工作模式
kube-proxy 是一个运行在每个节点上的后台进程,它监听 API Server,发现 Service 和 Endpoint 的变化,然后更新本机的网络规则。
模式一:iptables(传统模式)
这是默认模式。当有一个 Service my-svc 指向 3 个 Pod 时,kube-proxy 会在 iptables 的 nat 表中插入规则:
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "default/my-svc" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "default/my-svc" -m tcp --dport 80 -j KUBE-SVC-XXXX ...
问题所在: 如果你有很多 Service 和大量 Endpoints,iptables 规则会变得极其庞大。每次规则更新,内核都需要重新编译链,这会导致网络抖动,甚至引起丢包。在生产环境大规模集群中,这往往是性能瓶颈。
模式二:IPVS(高性能模式)
IPVS(IP Virtual Server)是 Linux 内核的一个模块,设计用于负载均衡。它比 iptables 更高效,因为它使用哈希表来查找规则,而不是线性遍历链。
要启用 IPVS,你需要:
- 加载内核模块:
modprobe ip_vs - 在 kube-proxy 配置中指定模式:
# kube-proxy configmap
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
然后重启 kube-proxy 即可。你会看到 iptables 规则消失了,取而代之的是 IPVS 规则。
模式三:eBPF(Cilium 模式)
如前所述,Cilium 完全跳过了 kube-proxy,用 eBPF 程序直接在内核中处理流量转发。这是目前性能最强的方案,尤其在大规模集群中,eBPF 的延迟比 IPVS 更低。
4.2 外部访问:NodePort 和 LoadBalancer
当外部用户想要访问你的 Service 时,流量怎么走?
- NodePort:在每个节点的同一个端口(如 30080)上监听。流量进入节点后,由 iptables/IPVS 转发到后端 Pod。
- LoadBalancer:在云环境中,K8s 会调用云厂商的 API,创建一个云负载均衡器(如 AWS NLB、阿里云 SLB),并将其 IP 绑定到 Service 的
EXTERNAL-IP。
kubectl get svc my-app
# 输出:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# my-app LoadBalancer 10.96.0.50 203.0.113.50 80:30080/TCP 10d
五、 网络策略(NetworkPolicy):微观层面的防火墙
很多开发者知道 Security Group(安全组),但不知道 K8s 也有自己的防火墙——NetworkPolicy。
5.1 默认行为:允许所有
重要提醒:如果没有安装支持 NetworkPolicy 的 CNI 插件(如 Calico、Cilium),NetworkPolicy 资源会被忽略!Flannel 默认不支持。
如果支持(比如用了 Calico),默认情况下,Pod 之间互相是通的。
5.2 实战:创建一个“只进不出”的 Policy
假设你有一个数据库 Pod,你只想让 frontend 命名空间的 Pod 访问它,且数据库只能主动发响应,不能主动连接外网。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
namespace: backend
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
env: production
ports:
- protocol: TCP
port: 3306
egress:
- to:
- namespaceSelector: {} # 允许访问所有命名空间(用于 DNS 解析等)
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
解读:
podSelector选中 MySQL Pod。ingress只允许env=production的命名空间访问 3306 端口。egress只允许 DNS 查询和连接 Redis。其他所有出站流量都被静默丢弃。
这就是为什么在生产环境中,我们总是建议开启 NetworkPolicy,实现“零信任”网络模型。
六、 实战排查:当网络不通时,我该看什么?
这是本文最实用的部分。我会带你模拟几个经典的故障场景,并提供排查命令。
场景一:Pod 能 ping 通宿主机,但 ping 不通同 Namespace 的其他 Pod
可能原因 1:CNI 插件未正常工作
检查节点上的 CNI 插件日志。
# 查看 Calico 节点日志
kubectl logs -n calico-system calico-node-xxxxxx
# 或者查看 kube-proxy 日志
kubectl logs -n kube-system kube-proxy-xxxxxx
可能原因 2:安全组或防火墙拦截
有些云平台的安全组默认只允许入站流量,或者禁用了 ICMP。虽然 Pod 间通信理论上不走物理防火墙,但如果底层用了 VXLAN(如 Flannel),外层 UDP 流量可能受到限制。
检查 iptables:
# 在宿主机上执行
iptables -L -n -v | grep 10.244
看看是否有 REJECT 或 DROP 规则拦截了 Pod 网段。
场景二:Service ClusterIP 无法访问,但 Pod IP 可以
排查步骤:
检查 Endpoint:
kubectl get endpoints my-svc -n default # 如果没有 Endpoints,说明后端 Pod 的 Label 选择器不匹配,或者 Pod 处于 NotReady 状态检查 kube-proxy:
kubectl get pods -n kube-system | grep proxy kubectl logs -n kube-system kube-proxy-xxxxxx如果 kube-proxy 崩溃, iptables 规则就不会更新。
手动验证 DNAT: 在节点上执行:
iptables -t nat -L KUBE-SERVICES -n -v看看是否有针对该 Service ClusterIP 的规则。如果没有,就是 kube-proxy 没生效。
场景三:跨节点 Pod 通信延迟高或丢包
可能原因:MTU 不匹配
这是最常见却最容易被忽视的问题。
- 物理网卡的 MTU 通常是 1500。
- VXLAN 封装会增加 50 字节的开销(24位 VNI + 8位 UDP + 8位 VXLAN + 20位 IP)。
- 如果 Pod 的 MTU 还是 1500,大包会被分片或丢弃。
解决方案:
确保 CNI 插件配置的 MTU 小于物理网络 MTU 减去开销。
- Flannel VXLAN:建议设置 MTU 为 1450。
- Calico IPIP:建议设置 MTU 为 1440。
检查当前 MTU:
# 在 Pod 内
ip link show eth0
# 在 Node 上检查 flannel.1 接口
ip link show flannel.1
如果 MTU 不对,修改 CNI 配置:
// flannel cni config
{
"name": "cni-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true,
"mtu": 1450 // 修改这里
}
}
]
}
然后重启 kubelet 和 CNI 组件。
场景四:CoreDNS 解析失败
现象:curl my-svc.default.svc.cluster.local 超时,但 curl <Pod-IP> 正常。
排查步骤:
- 检查 DNS 服务状态: “`bash kubectl get svc -n kube-system | grep dns kubectl get pods -n kube-system |
