嘿,你好呀!我是Agnes。我知道你现在的状态——大概率正对着一排排红得刺眼的 NotReady Pod 或者是一个怎么都连不通的服务端点发愣。Kubernetes 的网络一直是新手(甚至老手)的头号杀手,因为它把原本简单的 Linux 网络栈拆分成了 Pod、Node、Service、Ingress 好几层,再加上各种 CNI 插件(Calico, Flannel, Cilium, Weave)的黑盒操作,出错的时候根本不知道是该查 iptables、juggling 还是隧道封装。
别急,这篇文章就是你的“网络急救包”。我们不搞那些枯燥的教科书定义,咱们就像在机房里面对面Debug一样,把从底层容器网络到上层服务发现的全链路拆开来揉碎了讲。我会带你理解 Pod IP 是怎么来的,CNI 插件到底在后台干了什么脏活,以及当你遇到 Connection timed out 时,如何像侦探一样一步步定位是网络层面的问题还是应用层面的问题。
1. 重新认识 K8s 网络:五个基本信念
在动手修 bug 之前,咱们得先达成共识。Kubernetes 官方文档里那五点“网络模型”承诺,听起来像圣经,但在实际排查故障时,它们是你判断“哪里出错了”的基准线。如果现实违背了这些,那肯定有东西没配置对,或者中间有人(或者某个插件)在搞鬼。
1.1 每个 Pod 都有唯一的 IP
这是核心中的核心。你不需要为容器端口指定任何映射,容器内部看到的 127.0.0.1 就是它自己,而其他 Pod 访问它时,直接通过它的 Pod IP 即可。这意味着什么?意味着 Pod IP 是全局路由可达的。
想象一下,如果你的集群有三个节点,每个节点上跑了几个 Pod。Pod A (10.244.1.5) 要访问 Pod B (10.244.2.8)。在传统的虚拟机时代,你得做 NAT,得改路由,得搞端口映射。但在 K8s 里,这就像是在同一个局域网内两台电脑通信一样自然。
为什么新手容易在这里栽跟头?
很多新手部署完 CNI 后,执行 kubectl get pods -o wide,发现 Pod IP 全是 0.0.0.0 或者根本不显示 IP,又或者 IP 段冲突了。这通常意味着 CNI 插件没有在节点上正确分配网桥或隧道。
1.2 Pod 之间无需 NAT 即可通信
正如上面所说,跨节点 Pod 通信不需要 Network Address Translation (NAT)。这听起来很美好,但代价是——你的节点网络必须能够路由这些 Pod IP。
如果你的物理机或虚拟机网络本身无法路由 10.244.x.x 这个网段,那 Calico 的 BGP 模式或者 Flannel 的 VXLAN 模式就必须起作用了。这里有个关键概念:Underlay(底层物理网络)vs Overlay(覆盖网络)。
- Underlay 模型(如 Calico BGP):直接利用物理交换机的路由能力。性能最好,但对物理网络设备有要求(支持 BGP)。
- Overlay 模型(如 Flannel VXLAN, Weave):在物理网络之上封装一层虚拟网络。兼容性好,但有一点性能损耗。
1.3 每个容器共享同一个网络命名空间
一个 Pod 里的多个容器(Init Container, Sidecar 等)共享网络栈。它们共用一个 IP 和一个端口空间。这意味着容器间通信可以用 localhost,但这同时也意味着端口冲突。两个容器不能同时监听 8080,除非使用 hostPort(不推荐)。
实战小贴士:
当你调试多容器 Pod 时,进入主容器 (kubectl exec -it <pod> -c <container> -- sh),用 netstat -tuln 或 ss -tuln 查看端口,你会发现所有容器看到的端口列表是一模一样的。这是验证“是否共享网络栈”的最快方法。
1.4 Pod 与 Node 网络直接互通
Pod 不需要 NAT 就能和 Node 网络互通。Node 网络指的是节点本身的网络接口(比如 eth0)所在的网段。
这带来了两个重要推论:
- Node 可以直接访问 Pod IP:你在节点 A 上
curl 10.244.1.5是可以通的(前提是路由正确)。 - Pod 可以直接访问 Node IP:Pod 内的服务如果需要访问节点上的其他服务(比如本地数据库),也是可以直接通的。
常见误区: 很多用户以为 Pod IP 和 Node IP 在同一个网段,其实不是。它们通常是不同的子网。Pod IP 来自 CNI 配置的池子(Pool),Node IP 是物理网卡 IP。通过路由表,这两个子网是互通的。
1.5 容器端口定义 vs 主机端口
在 Pod Spec 中定义的 containerPort 只是告诉 Kubernetes 和 Service 这个容器监听哪个端口,它不会在主机上开放任何端口。这与 Docker 的 -p 8080:80 完全不同。
如果你想让外部流量直接进入 Pod 的特定端口(绕过 Service),你必须使用 hostPort。但强烈不推荐这么做,因为它破坏了 Pod 的便携性,且容易引发端口冲突。
2. CNI 插件:黑盒背后的真相
CNI (Container Network Interface) 是 K8s 网络的插件化接口。K8s 本身不负责网络实现,它只负责调用 CNI 插件来“配置”或“卸载”网络。
目前主流的 CNI 插件有:
- Flannel:最简单,适合小规模集群,基于 VXLAN。
- Calico:功能强大,支持 BGP,策略灵活,适合大规模生产环境。
- Cilium:基于 eBPF,性能极致,安全策略强大,是未来的趋势。
- Weave Net:自动加密,跨云友好,但性能稍弱。
2.1 CNI 插件的工作流程
当一个 Pod 被调度到某个节点时,Kubelet 会调用 CNI 插件:
- ADD:插件为该 Pod 创建一个网络命名空间(或复用已有的),分配一个 IP 地址,创建 veth pair(虚拟网线),一端放入 Pod 网络命名空间,另一端放入节点网络命名空间(通常桥接到一个网桥,如
cni0或cali*)。 - DEL:当 Pod 删除时,插件回收 IP,移除网络配置。
让我们看看真实环境中发生了什么。
假设你用的是 Calico,进入一个运行 Pod 的节点,执行:
# 查看节点上的网络接口
ip addr show
# 你应该能看到类似这样的接口
# eth0: 节点物理网卡
# cali1234abcd: 连接到某个 Pod 的 veth 接口
# calib+ 开头的网桥
在 Pod 内部执行:
# 进入 Pod
kubectl exec -it <pod-name> -- ip addr
# 你会看到一个 eth0 接口,IP 是 Pod IP
# 注意:Pod 内部的网关通常是 cali 接口的 IP 或网桥 IP
2.2 Flannel 的典型配置(以 VXLAN 为例)
Flannel 是新手最常接触到的 CNI,因为它部署简单。它的工作原理是在每个节点上创建一个 flannel.1 虚拟网卡,并通过 VXLAN 隧道将不同节点的 Pod 网络连接起来。
配置示例(通常由 kube-flannel.yml 提供):
apiVersion: policy/v1
kind: NetworkPolicy
metadata:
name: flannel
namespace: kube-flannel
spec:
podSelector:
matchLabels:
app: flannel
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-flannel-cfg
namespace: kube-flannel
data:
cni-conf.json: |
{
"name": "cni0",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
关键点解析:
Network: 10.244.0.0/16:这是 Pod IP 的池子。每个节点会从这个池子里切分一个小段(Subnet),比如节点 1 分到10.244.1.0/24,节点 2 分到10.244.2.0/24。Type: vxlan:使用 VXLAN 封装。Pod 发出的数据包会被封装在 UDP 包里,通过节点的物理网卡发送,到达目标节点后解封装。
故障排查: 如果跨节点 Pod 不通,先在节点上检查 VXLAN 隧道是否建立:
# 查看 VXLAN 接口
ip link show flannel.1
# 查看 ARP 表,确认是否学习到对端节点的 MAC 地址
ip neigh show dev flannel.1
# 如果 ARP 表是空的,说明 VXLAN 隧道没有建立成功,检查 kube-flannel-ds 是否在所有节点上都正常运行
kubectl get pods -n kube-flannel
2.3 Calico 的典型配置(以 IPIP 或 BGP 为例)
Calico 比 Flannel 复杂,但更强大。它支持多种模式:
- IPIP:类似 VXLAN,封装 IP 包。兼容性好,但性能略低。
- BGP:直接利用节点上的 BGP 协议向物理路由器宣告 Pod 路由。性能最好,但需要物理网络支持 BGP。
- VXLAN:Calico 也支持 VXLAN,作为 BGP 的备选方案。
配置示例(Calico DaemonSet 中的关键配置):
env:
- name: CALICO_IPV4POOL_IPIP
value: "Always" # 或 "Never" (使用 BGP)
- name: FELIX_IPINIP_ENABLED
value: "True"
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
BGP 模式下的网络拓扑:
在 BGP 模式下,每个节点上的 bird 进程会与物理交换机或其他节点的 bird 进程建立 BGP 邻居关系,宣告该节点上的 Pod 网段。这样,物理交换机就知道如何路由到 Pod IP 了。
故障排查:
# 检查 bird 进程是否运行
ps aux | grep bird
# 查看 BGP 邻居状态
birdc show protocol all
# 如果邻居状态是 "Established",说明 BGP 连通性正常
# 如果不通,检查防火墙是否放行了 BGP 端口 (TCP 179)
3. Pod IP 生命周期与分配机制
理解 Pod IP 是如何产生和消亡的,对于排查 IP 冲突、网络重启后连接中断等问题至关重要。
3.1 CNI 调用链
当 Pod 进入 Running 状态时,Kubelet 会调用 CNI 插件:
- Kubelet 读取
/etc/cni/net.d/目录下的 CNI 配置文件(如10-flannel.conflist)。 - Kubelet 调用 CNI 插件的二进制文件(如
/opt/cni/bin/flannel)。 - CNI 插件 执行脚本,调用内核命令(
ip link add,ip addr add等)来配置网络。 - Pod 的 network namespace 被创建,veth pair 被挂载。
- Pod 开始启动,此时它已经有 IP 了。
注意:Pod IP 是在容器启动之前就分配好的。如果 CNI 插件故障,Pod 可能无法启动,或者启动后没有 IP。
3.2 IP 地址冲突怎么办?
在分布式系统中,IP 冲突是噩梦。K8s 本身不校验 IP 冲突,它依赖 CNI 插件来保证唯一性。
- Flannel:通过 etcd 或 kube-apiserver 记录每个节点已分配的子网,确保不重复。
- Calico:通过 etcd 记录每个 Pod 的 IP 绑定,确保全局唯一。
如果遇到 IP 冲突: 通常表现为某个 Pod 网络不通,或者 ARP 表异常。解决方法是重启 CNI 插件,让它重新从存储后端(etcd/kube-apiserver)同步数据,重新分配 IP。
# 重启所有节点的 Calico 节点代理
kubectl rollout restart daemonset calico-node -n calico-system
3.3 Pod 重启后 IP 会变吗?
会的。每次 Pod 被删除并重新创建,它会获得一个新的 IP。这是 K8s 的设计理念:Pod 是无状态的,IP 是 ephemeral(临时的)。
这也意味着,不要直接依赖 Pod IP 做长期通信,应该使用 Service 名称。如果依赖 Pod IP,Pod 重启后,所有指向它的连接都会断开。
4. 常见故障排查指南:Connection Timed Out
这是新手最常遇到的错误。curl: (28) Connection timed out 或 telnet: connect to address ...: Operation timed out。
4.1 排查步骤:从内到外,分层定位
我建议你采用 OSI 模型的思路,从最底层(网络层)开始向上排查。
第一步:确认 Pod 是否有 IP
kubectl get pods -o wide
如果 IP 列是空的,或者状态是 ContainerCreating 很久,说明 CNI 插件没工作。检查 CNI Pod 日志:
kubectl logs -n kube-system <cni-pod-name>
# 例如
kubectl logs -n kube-system calico-node-xxxxx
第二步:确认 Pod 内部网络是否配置正确
进入 Pod,检查 IP 和路由:
kubectl exec -it <pod-name> -- ip addr
kubectl exec -it <pod-name> -- ip route
你应该看到 eth0 有 IP,并且默认网关指向 CNI 网桥或 veth 接口。如果没有路由,Ping 外部会失败。
第三步:同节点 Pod 互通测试
先排除跨节点问题。在同一个节点上的两个 Pod 之间 ping:
kubectl exec -it pod-a -- ping <pod-b-ip>
如果同节点不通,问题出在 CNI 插件的本地配置(网桥、veth、iptables 规则)。
第四步:跨节点 Pod 互通测试
kubectl exec -it pod-a -- ping <pod-b-ip> # pod-b 在另一个节点
如果同节点通,跨节点不通,问题出在 Overlay 隧道(VXLAN/IPIP)或 BGP 路由。
检查隧道状态:
# 在节点 A 上
ip link show flannel.1 # 或 ipip.1
ip neigh show # 查看是否学到节点 B 的 MAC
# 在节点 B 上
# 同样检查
如果 ARP 表没有对端节点的条目,检查防火墙是否阻断了 VXLAN (UDP 8472) 或 IPIP (协议 4) 流量。
第五步:节点到 Pod 的连通性
在节点上直接 ping Pod IP:
# 在节点 A 上
ping <pod-a-ip>
如果节点 ping 不通自己的 Pod,说明节点到 Pod 的路由有问题,或者 iptables 规则拦截了流量。
检查 iptables:
iptables -L -n -v
# 特别关注 OUTPUT 和 FORWARD 链,看是否有 DROP 或 REJECT 规则
第六步:Service 和 DNS 测试
如果 Pod 间互通正常,但通过 Service 名称访问不通,问题出在 kube-proxy 或 CoreDNS。
# 检查 kube-proxy 日志
kubectl logs -n kube-system <kube-proxy-pod>
# 检查 DNS 解析
kubectl exec -it <pod-name> -- nslookup <service-name>
kubectl exec -it <pod-name> -- dig <service-name>
如果 DNS 解析失败,检查 CoreDNS Pod 是否运行,以及 DNS 服务 IP 是否在 kube-system 命名空间中配置正确。
4.2 实战案例:跨节点 Pod 不通的排查
场景: 集群有两个节点 Node1 和 Node2,使用 Flannel VXLAN 模式。Pod A 在 Node1 (IP: 10.244.1.5),Pod B 在 Node2 (IP: 10.244.2.5)。Pod A ping Pod B 超时。
排查过程:
检查 CNI Pod 状态:
kubectl get pods -n kube-flannel # 确保所有节点的 kube-flannel-ds 都是 Running检查 Node2 上的 VXLAN 接口:
# SSH 到 Node2 ip link show flannel.1 # 应该看到状态是 UP检查 ARP 表: “`bash ip neigh show dev flannel.1
应该看到 Node1 的 IP 对应的 MAC 地址
如果是 FAILED 或空白,说明 VX
