K8s网络不通排查实录从Pod到Service全覆盖网络模型入门到实战配置
嘿,我是Agnes,今天和你聊聊一个让很多K8s运维头疼的问题——网络不通。
说实话,K8s的网络模型设计得挺优雅,但真要排查起来,那个复杂程度能让你怀疑人生。我见过太多人遇到Pod之间连不上、Service访问不到、跨节点通信失败这些问题,然后就开始盲目地重启Pod、重装CNI插件,结果越搞越乱。
今天这篇文章,我想带你从最基础的网络模型开始,一步步深入到实战排查,保证你看完之后,遇到网络问题不会再慌。
先搞懂K8s网络模型的底层逻辑
在开始排查之前,我们得先明白K8s网络到底是怎么设计的。K8s官方给了一组基本要求,理解这些,你就理解了网络设计的初衷。
每个Pod都有一个独立的IP地址,Pod内部的容器共享这个IP。也就是说,Pod A的10.244.1.5和Pod B的10.244.2.3是可以在不经过NAT的情况下直接通信的。这个设计看起来很美好,但前提是CNI插件得正常工作。
每个节点上都会运行一个pause容器,这个容器提供了Pod的网络命名空间。所有的业务容器都会加入这个pause容器的网络命名空间,共享同一个IP、端口和网卡配置。这是K8s网络隔离的基础。
CNI插件负责在节点上创建veth pair,一端连接pause容器的eth0,另一端连接节点的虚拟网桥(比如cni0),然后通过IPAM插件给Pod分配IP地址。整个过程由kubelet在Pod创建时调用CNI插件完成。
我用一个实际的拓扑来帮你理解:
节点1 节点2
┌──────────────────┐ ┌──────────────────┐
│ cni0 (10.244.1.1)│ │ cni0 (10.244.2.1)│
│ │ │ │ │ │
│ │ veth pair │◄──────────────────┤ veth pair │
│ │ │ 物理网络 │ │ │
│ ▼ │ │ ▼ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │pause │10.244.1.5 │10.244.2.8 │pause│ │
│ │容器 │───────┐ │───────┐│容器 │ │
│ └────────┘ │ │ │└────────┘ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │app │10.244.1.5 │ │app │10.244.2.8│
│ │容器 │ │ │ │容器 │ │
│ └────────┘ │ │ └────────┘ │
└──────────────────┘ └──────────────────┘
注意,两个Pod的IP虽然在不同子网,但它们是可以直接ping通的,这就是K8s网络设计的核心——Pod IP跨节点可达。
排查工具库:你必须要会的几招
工欲善其事,必先利其器。在K8s里排查网络问题,这几个工具是你的基本装备。
kubectl exec是最常用的,你可以进入任何Pod内部执行命令。比如进入一个busybox容器:
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
然后你可以在容器内部使用curl、ping、nc这些基础工具。比如测试Pod到Pod的连通性:
# 在Pod A内部
curl -v http://<Pod-B-IP>:<port>
nc -zv <Pod-B-IP> <port>
# 测试DNS解析
nslookup <service-name>.<namespace>.svc.cluster.local
netstat和ss可以看网络连接状态:
ss -tlnp # 查看所有监听端口
ss -tn # 查看所有TCP连接
netstat -rn # 查看路由表
iptables规则在K8s网络里特别重要,因为Service的流量转发很大程度上依赖iptables或IPVS规则:
# 在节点上执行,查看所有iptables规则
sudo iptables -L -n -v
# 只看nat表
sudo iptables -t nat -L -n -v
# 查看mangle表
sudo iptables -t mangle -L -n -v
对于更复杂的网络诊断,我强烈推荐安装netshoot镜像,这是一个网络诊断的瑞士军刀容器:
kubectl run -it --rm netshoot-debug \
--image=nicolaka/netshoot:latest \
--restart=Never \
--namespace=default \
--command -- /bin/bash
进入这个容器后,你可以使用tcpdump抓包、ping、curl、dig、netstat、ss、ip、ifconfig、nslookup等几十种网络诊断工具。
第一阶段:Pod内网络问题排查
先看最简单的情况——Pod内部的网络。
容器内没有网络
如果你在Pod里执行ping或者curl,提示”network is unreachable”,首先要检查Pod的状态:
# 查看Pod状态
kubectl get pod <pod-name> -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
# 重点关注Events部分,看是否有PullImageError、CreateContainerError等
如果Pod状态是ContainerCreating且长时间卡住,可能是镜像拉取失败或者CNI配置问题。查看describe输出的Events,通常会告诉你具体原因。
检查Pod的网络配置是否正确应用:
# 在节点上查看Pod的网络命名空间
POD_PID=$(sudo crictl inspectp <pod-id> | jq -r '.info.runtimeSpec.linux.namespaces[].path' | grep pid | xargs basename | sed 's/pid//' | xargs)
ls -la /proc/<pod-pid>/ns/net
# 查看Pod的IP地址
sudo nsenter -t <pod-pid> -n ip addr
DNS解析失败
DNS问题在K8s里非常常见。首先确认CoreDNS或者kube-dns是否正常运行:
# 查看DNS Pod状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 查看DNS Pod的日志
kubectl logs -n kube-system <coredns-pod-name>
# 在Pod内测试DNS解析
kubectl exec -it <pod-name> -n <namespace> -- nslookup kubernetes.default.svc.cluster.local
kubectl exec -it <pod-name> -n <namespace> -- nslookup baidu.com
如果DNS解析失败,检查kubelet的配置,确保–cluster-dns参数正确:
# 查看kubelet配置
sudo cat /var/lib/kubelet/config.yaml | grep clusterDNS
# 查看集群DNS服务
kubectl get svc -n kube-system kube-dns
典型的kubelet配置应该包含:
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
如果域名格式不对,也会导致解析失败。记住,K8s内部的完整域名格式是<service-name>.<namespace>.svc.<domain>。
Pod无法访问外网
这种情况很常见,尤其是私有云环境。首先检查节点的路由表和iptables规则:
# 在节点上检查SNAT规则
sudo iptables -t nat -L POSTROUTING -n -v
# 检查MASQUERADE规则是否存在
sudo iptables -t nat -S | grep MASQUERADE
对于大多数CNI插件(如flannel、calico、canal),它们会自动在节点的iptables中添加MASQUERADE规则,将Pod流量转发到物理网络。如果没有这条规则,Pod就无法访问外网。
手动检查外网连通性:
# 在Pod内测试
ping 8.8.8.8
ping baidu.com
# 如果ping 8.8.8.8通但ping baidu.com不通,是DNS问题
# 如果都不通,是路由或SNAT问题
检查节点的默认路由:
ip route show
正常应该有一条默认路由指向网关。如果没有,需要检查节点的物理网络配置。
第二阶段:Pod到Pod网络问题
这是K8s网络最核心的部分,也是问题最多的地方。
同节点Pod互通性
先检查两个Pod是否在同一个节点上:
kubectl get pod <pod-a> -o wide
kubectl get pod <pod-b> -o wide
如果same node,问题可能出在CNI插件的配置上。检查节点的cni0网桥:
# 在节点上执行
brctl show cni0
ip link show cni0
# 查看该节点上所有Pod的veth接口
ip link show | grep veth
正常情况下,cni0网桥下应该有两个veth接口,分别连接两个Pod的pause容器。如果只有其中一个,说明另一个Pod的CNI配置可能有问题。
尝试在同节点的两个Pod之间ping:
# 在Pod A内部
ping <Pod-B-IP>
如果ping不通,使用tcpdump抓包分析:
# 在Pod A内部抓包
tcpdump -i eth0 host <Pod-B-IP>
如果看到ICMP请求发出但没有回应,检查Pod B那边是否收到请求:
# 在Pod B内部抓包
tcpdump -i eth0 host <Pod-A-IP>
跨节点Pod互通性
跨节点的问题更复杂,涉及到CNI插件的Overlay网络配置。
首先确认两个Pod的IP段是否正确:
# 在节点1上
ip addr show cni0
# 应该看到类似10.244.1.1/24的地址
# 在节点2上
ip addr show cni0
# 应该看到类似10.244.2.1/24的地址
检查节点间的路由是否可达:
# 在节点1上,尝试ping节点2的cni0地址
ping 10.244.2.1
如果ping不通,说明底层的网络连通性有问题。检查节点间的物理网络:
# 在节点1上,ping节点2的物理IP
ping <node2-physical-ip>
# 检查路由表
ip route show
对于Flannel插件,检查 VXLAN 隧道是否正常:
# 在节点1上
ip link show flannel.1
ip route show | grep flannel
# 应该看到类似
# 10.244.0.0/16 via 10.244.0.0 dev flannel.1
# 检查VXLAN设备
ip link show | grep vxlan
对于Calico插件,检查BGP邻居状态:
# 在节点1上
calicoctl node status
# 或者
birdc -p | grep Neighbor
如果BGP邻居Down了,检查Calico的节点配置:
kubectl get node <node-name> -o yaml
Pod网络策略问题
K8s的网络策略(NetworkPolicy)可以限制Pod之间的通信。如果你启用了NetworkPolicy,但某些Pod之间无法通信,很可能是策略配置问题。
查看当前命名空间下的网络策略:
kubectl get networkpolicy -n <namespace>
kubectl describe networkpolicy <policy-name> -n <namespace>
一个简单的网络策略示例:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-nginx
namespace: default
spec:
podSelector:
matchLabels:
app: nginx
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: client
ports:
- protocol: TCP
port: 80
这个策略只允许带有app=client标签的Pod访问带有app=nginx标签的Pod的80端口。其他所有流量都会被拒绝。
如果不确定是哪个策略导致了问题,可以临时删除策略进行测试(生产环境慎用):
kubectl delete networkpolicy <policy-name> -n <namespace>
第三阶段:Service网络问题
Service是K8s中让Pod暴露给外部的核心机制,但它的网络实现也最容易出问题的地方。
Service无法访问
首先确认Service是否存在且配置正确:
kubectl get svc <service-name> -n <namespace>
kubectl describe svc <service-name> -n <namespace>
describe输出中,重点关注以下信息:
Name: my-service
Namespace: default
Labels: app=myapp
Annotations: <none>
Selector: app=myapp
Type: ClusterIP
IP Families: IPv4
IP: 10.96.0.50
IPs: 10.96.0.50
Port: http 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.244.1.5:8080,10.244.2.3:8080
Session Affinity: None
Events: <none>
如果没有Endpoints,说明没有Pod匹配到这个Service的Selector。检查后端Pod的标签:
kubectl get pods -n <namespace> -l app=myapp
kubectl get pods -n <namespace> --show-labels
检查Pod的端口是否正确:
kubectl get pods -n <namespace> -l app=myapp -o wide
kubectl exec -it <pod-name> -n <namespace> -- netstat -tlnp
ClusterIP Service访问测试
在集群内部测试Service访问:
# 使用busybox测试
kubectl run -it --rm test-svc \
--image=busybox:1.28 --restart=Never \
--namespace=default \
--command -- sh
# 在容器内执行
nslookup <service-name>
wget -qO- http://<service-name>:<port>
如果Service的IP无法访问,检查节点的iptables或IPVS规则:
# 使用iptables模式
sudo iptables -t nat -L KUBE-SERVICES -n -v | grep <service-IP>
# 使用IPVS模式
sudo ipvsadm -L -n | grep <service-IP>
对于iptables模式,你应该能看到类似这样的规则:
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SVC-XXXX tcp -- * * 0.0.0.0/0 10.96.0.50 tcp dpt:80
对于IPVS模式,你应该能看到:
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.50:http rr
-> 10.244.1.5:8080 Masq 1 0 0
-> 10.244.2.3:8080 Masq 1 0 0
如果没有看到后端Endpoint,可能是kube-proxy没有正确同步。检查kube-proxy的日志:
kubectl logs -n kube-system <kube-proxy-pod-name>
NodePort Service问题
NodePort类型的Service会在每个节点上开放一个端口,外部可以通过<node-ip>:<nodeport>访问。
首先确认NodePort是否正确分配:
kubectl get svc <service-name> -n <namespace>
输出中应该看到类似:
TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
NodePort 10.96.0.50 <none> 80:30080/TCP 5m
检查节点上的端口是否开放:
# 在每个节点上执行
sudo netstat -tlnp | grep 30080
sudo iptables -t nat -L KUBE-NODEPORTS -n -v
如果端口没有监听,检查kube-proxy是否在所有节点上正常运行:
kubectl get pods -n kube-system -l app=kube-proxy
kubectl logs -n kube-system <kube-proxy-pod-name>
LoadBalancer Service问题
LoadBalancer类型的Service需要云平台提供外部负载均衡器。如果External-IP一直是pending状态,说明云平台没有正确配置负载均衡器。
检查Service状态:
kubectl get svc <service-name> -n <namespace> -o wide
kubectl describe svc <service-name> -n <namespace>
在describe输出中,查看Events部分:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Creating 2m service-controller Creating load balancer
Normal Created 2m service-controller Created load balancer
Warning Failed 1m service-controller Error creating load balancer: <具体错误信息>
根据具体的错误信息排查。常见的问题包括:云平台的API凭证配置错误、安全组规则限制、或者云平台资源配额不足。
第四阶段:实际排查案例
让我分享几个真实的排查案例。
案例一:跨节点Pod无法通信
问题描述: 集群有三个节点,使用Flannel作为CNI插件。节点1和节点2上的Pod可以互相ping通,但节点3上的Pod无法ping通其他节点的Pod。
排查过程:
首先确认节点3的网络配置:
# 在节点3上
ip addr show flannel.1
ip route show
发现flannel.1接口存在,但路由表中没有指向节点3的Flannel子网的路由。
# 在其他节点上检查路由
ip route show | grep 10.244
节点1和节点2都有指向节点3的路由:
10.244.2.0/24 via 10.0.0.3 dev eth0
但节点3上没有指向其他节点的路由。
原因分析:
检查Flannel的etcd配置:
# 在etcd上查看网络配置
etcdctl get /coreos.com/network/config --print-value-only
发现节点3在etcd中的配置缺少了PublicIP字段,导致其他节点无法为节点3建立VXLAN隧道。
解决方案:
# 在节点3上重新配置Flannel
sudo rm -f /etc/flannel/subnet.env
sudo systemctl restart flanneld
重启后,检查路由表:
ip route show | grep flannel
# 应该看到其他节点的Flannel子网路由
验证连通性:
# 在节点3的Pod中
ping <其他节点Pod-IP>
案例二:Service Endpoints经常消失
问题描述: 一个关键的业务Service,其Endpoints会不定期消失,导致服务中断。
排查过程:
首先查看Service的详细信息:
kubectl get ep <service-name> -n <namespace>
kubectl describe svc <service-name> -n <namespace>
发现Endpoints消失时,后端Pod仍然在运行。这说明不是Pod问题,而是Service Selector匹配问题。
检查Pod的标签:
kubectl get pods -n <namespace> --show-labels | grep <pod-name>
发现Pod的标签和Service的Selector不一致。进一步调查发现,是因为Deployment的模板更新时,Pod的标签没有正确更新。
解决方案:
# 检查Deployment配置
kubectl get deploy <deploy-name> -n <namespace> -o yaml
# 确保Selector和Template Labels一致
kubectl patch deploy <deploy-name> -n <namespace> -p '{"spec":{"template":{"metadata":{"labels":{"app":"myapp"}}}}}'
同时,检查是否有其他资源(如HorizontalPodAutoscaler或自定义控制器)修改了Pod的标签。
案例三:外网无法访问K8s集群服务
问题描述: 使用NodePort类型的Service,在集群内部可以访问,但从外网无法访问。
排查过程:
首先在集群内部测试:
kubectl run -it --rm test-external \
--image=busybox:1.28 --restart=Never \
--command -- sh
# 在容器内
wget -qO- http://<service-name>:<port>
内部访问正常,说明Service和后端Pod工作正常。
检查NodePort是否在节点上监听:
# 在每个节点上
sudo netstat -tlnp | grep <nodeport>
sudo iptables -t nat -L KUBE-NODEPORTS -n -v
发现NodePort端口在监听,iptables规则也存在。
检查防火墙规则:
# 在节点上
sudo firewall-cmd --list-ports
sudo iptables -L INPUT -n -v
发现防火墙阻止了NodePort端口的入站流量。
解决方案:
# 开放NodePort端口
sudo firewall-cmd --permanent --add-port=<nodeport>/tcp
sudo firewall-cmd --reload
或者,如果使用了云服务商的安全组,需要在安全组中开放对应的端口范围(默认是30000-32767)。
第五阶段:网络性能优化建议
当网络通畅后,性能问题可能会浮出水面。以下是一些优化建议。
调整MTU值
不同的CNI插件和网络环境可能需要不同的MTU值。MTU不匹配会导致大包传输失败。
检查物理网络的MTU:
ip link show eth0
根据物理网络MTU调整Flannel的MTU:
# 在etcd中配置
etcdctl set /coreos.com/network/config '{"Network":"10.244.0.0/16","Backend":{"Type":"vxlan","VNI":1,"Port":4789,"DirectRouting":false,"MTU":1400}}'
调整kube-proxy模式
kube-proxy支持iptables和IPVS两种模式。IPVS在大规模集群中性能更好。
检查当前模式:
kubectl get configmap -n kube-system kube-proxy -o yaml
切换到IPVS模式:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
strictARP: true
masqueradeAll: false
优化DNS缓存
对于高频访问的服务,可以在Sidecar模式中使用DNS缓存:
apiVersion: v1
kind: Pod
metadata:
name: cached-dns
spec:
containers:
- name: app
image: myapp:latest
- name: dns-cache
image: sameermyles/dnsmasq:latest
ports:
- containerPort: 53
protocol: UDP
- containerPort: 53
protocol: TCP
然后在Pod中使用本地DNS缓存:
dnsPolicy: Default
dnsConfig:
nameservers:
- 127.0.0.1
searches:
- default.svc.cluster.local
- svc.cluster.local
- cluster.local
总结
K8s网络排查是一个系统性的工程,需要从底层网络模型出发,逐步向上排查。记住这几个关键原则:
分层排查:从Pod网络 → Service网络 → 外部网络,逐层定位问题。
工具先行:熟练掌握kubectl、tcpdump、iptables/ipvsadm等工具,能快速定位问题。
日志为王:不要忽略Pod日志、kube-proxy日志、CNI插件日志,它们往往直接给出问题原因。
对比验证:在一个正常工作的集群中,对比配置和网络状态,快速发现差异。
最小化测试:每次只改变一个变量,逐步缩小问题范围。
网络问题最考验耐心和最考验细节,希望这篇文章能帮你建立起一套完整的排查思路。如果你在排查过程中遇到具体的问题,欢迎随时交流。
