Kubernetes网络不通怎么办从Pod通信到Service原理手把手教你解决网络连接故障
先别慌,我们一步一步来
昨天有个开发兄弟找我,说他们的K8s集群里Pod之间就是不通,急得像热锅上的蚂蚁。我问他:”你具体说说,是哪两个Pod?怎么个不通法?”他说:”就是一个前端Pod访问后端Pod,用curl连不上。”
这种场景我太熟悉了,Kubernetes的网络问题就像是一个黑盒,里面机制复杂,一旦出问题排查起来确实让人头疼。但别担心,今天我就把这套东西掰开揉碎了讲给你听,保证你看完以后遇到网络问题不再抓瞎。
先搞清楚K8s网络模型的基本假设
在深入排查之前,你首先需要理解Kubernetes官方定义的网络模型,这是所有排查的根基。K8s网络模型有四个核心假设,你必须牢牢记住:
每个Pod都有自己的IP地址
这意味着,不管你有多少个容器在一个Pod里,整个Pod对外只有一个IP,这个IP在Pod内所有容器间是共享的。
Pod之间可以直接通信,不需要NAT
不管这两个Pod是在同一台Node上,还是跨节点,跨网络,甚至跨集群(理论上),它们都能直接用IP互相访问。
每个Pod的IP在整个集群里都是唯一的
你不会遇到两个Pod使用相同IP的情况,这是K8s网络管理的基础。
Pod的IP和Node的IP是不同的
Pod的IP是虚拟网络空间里的IP,不是宿主机物理网卡的IP。
理解了这四个假设,你才能明白为什么网络会”不通”——因为有些东西没有按照这个模型在工作。
Pod到Pod的通信:最基础的连通性验证
先做一个简单的连通性测试
假设你的集群里有两个Pod,一个是nginx-frontend,一个是api-backend。你想从frontend访问backend,第一步应该是:
# 查看两个Pod的IP地址
kubectl get pods -o wide
# 输出示例
NAME READY STATUS RESTARTS AGE IP NODE
nginx-frontend-1 1/1 Running 0 5m 10.244.1.10 node-1
api-backend-2 1/1 Running 0 3m 10.244.2.20 node-2
拿到IP之后,从frontend Pod里curl backend的IP:
# 进入frontend Pod
kubectl exec -it nginx-frontend-1 -- /bin/sh
# 在Pod内部执行curl
curl -v http://10.244.2.20:80
如果这里能通,恭喜你,Pod到Pod的基本网络是好的。如果这里就堵了,那问题就在最底层——CNI插件或者网络策略层面。
如果Pod到Pod不通,该怎么排查
这种情况我一般按照这个顺序来查:
第一层:确认IP是否正确分配
# 查看Pod的详细信息,确认IP
kubectl describe pod api-backend-2
# 重点看这个部分
# Addresses:
# IP: 10.244.2.20
# Host IP: 192.168.1.100
# Pod IP: 10.244.2.20
如果IP字段是空的,说明Pod根本没有拿到IP,这时候问题很可能出在CNI插件上。
第二层:检查CNI插件状态
# 查看CNI相关Pod是否正常运行
kubectl get pods -n kube-system | grep -E 'calico|flannel|canal|cilium'
# 查看CNI Pod的日志,比如calico
kubectl logs calico-node-xxxx -n kube-system
第三层:检查Node网络配置
# 在Node上查看网络接口
ip addr show
# 应该能看到类似cni0或者cali*这样的接口
# 如果没有,说明CNI插件没有正确配置网络
# 检查路由表
ip route show
# 应该能看到通过cni网桥的路由
第四层:检查网络策略
# 查看是否有NetworkPolicy在限制流量
kubectl get networkpolicy -A
# 如果有,查看具体规则
kubectl describe networkpolicy <policy-name> -n <namespace>
网络策略是K8s里一个很容易被忽视但又极易导致问题的东西。一个默认拒绝所有入流量的NetworkPolicy,能让你的整个集群的网络”静默死亡”——没有报错,只是流量就是到不了目的地。
Service:K8s网络的精华所在
Service到底是什么
很多初学者(甚至一些工作几年的老手)对Service的理解很模糊。我用一个最直白的比喻来说:Service就是一个智能的负载均衡器,它背后挂着一组Pod,当你访问Service的IP时,它会把流量转发到后面健康的Pod上。
# 一个典型的Service定义
apiVersion: v1
kind: Service
metadata:
name: api-backend-service
spec:
selector:
app: api-backend
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
这里有个关键点:selector。Service通过selector来找到背后的Pod。如果你的Pod标签和selector对不上,Service就找不到任何后端,流量就会丢失。
ClusterIP、NodePort、LoadBalancer:三种Service类型的区别
ClusterIP(默认类型)
这是最常用的一种,它给你的Service分配一个集群内部的虚拟IP(ClusterIP),只能在集群内部访问。
# 创建ClusterIP Service
kubectl expose deployment api-backend --port=80 --target-port=8080 --name=api-backend-svc
# 查看Service信息
kubectl get svc api-backend-svc
# 输出示例
NAME TYPE CLUSTER-IP PORT(S) AGE
api-backend-svc ClusterIP 10.96.0.50 80/TCP 2m
注意这个10.96.0.50,这就是Service的虚拟IP。你在集群内任何Pod里都可以访问这个IP。
NodePort
如果你需要从集群外部访问服务,就需要NodePort类型。它会在每个Node上开放一个端口,外部可以通过NodeIP:NodePort来访问。
apiVersion: v1
kind: Service
metadata:
name: api-backend-nodeport
spec:
selector:
app: api-backend
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30080
type: NodePort
# 查看NodePort
kubectl get svc api-backend-nodeport
# 输出
NAME TYPE CLUSTER-IP PORT(S) AGE
api-backend-nodeport NodePort 10.96.0.51 80:30080/TCP 1m
现在你可以通过http://NodeIP:30080从外部访问了。
LoadBalancer
如果你用的是公有云(AWS、阿里云、腾讯云等),LoadBalancer类型会自动帮你创建一个云厂商的负载均衡器。
apiVersion: v1
kind: Service
metadata:
name: api-backend-lb
spec:
selector:
app: api-backend
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
kubectl get svc api-backend-lb
# 输出
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-backend-lb LoadBalancer 10.96.0.52 203.0.113.50 80:32080/TCP 1m
那个EXTERNAL-IP就是云厂商给你的负载均衡器地址。
iptables和ipvs:Service流量转发机制
这是Kubernetes Service实现的核心,也是很多网络问题的高发区。
iptables模式
这是默认模式。每个Node上的kube-proxy会监听Service和Endpoint的变化,然后自动生成iptables规则来转发流量。
# 查看kube-proxy是否在用iptables模式
kubectl get configmap -n kube-system kube-proxy -o yaml | grep mode
# 输出
mode: "iptables"
iptables模式下,流量转发是这样工作的:
Pod A (10.244.1.10) → 访问 Service ClusterIP (10.96.0.50:80)
↓
经过iptables规则 DNAT
↓
转发到某个后端Pod (10.244.2.20:8080)
ipvs模式
ipvs性能更好,支持更多调度算法,但需要内核支持。
# 切换到ipvs模式
kubectl edit configmap kube-proxy -n kube-system
# 修改这一行
mode: "ipvs"
# 然后重启所有kube-proxy Pod
kubectl delete pods -n kube-system -l app=kube-proxy
ipvs模式下的性能优势在大规模集群里非常明显,如果你集群里Service数量很多,强烈建议切换到ipvs。
Endpoint:Service和Pod之间的桥梁
Service本身不产生流量,它只是一个”名字”,真正干活的是它后面的Pod。那Service是怎么知道有哪些Pod的呢?答案就是Endpoint。
# 查看Service对应的Endpoint
kubectl get endpoints api-backend-svc
# 输出示例
NAME ENDPOINTS AGE
api-backend-svc 10.244.2.20:8080 5m
如果你看到ENDPOINTS列是<none>,那就说明Service找不到任何后端Pod。这通常是selector配置错误导致的。
# 详细查看Endpoint信息
kubectl describe endpoints api-backend-svc
# 重点看这部分
# Addresses: 10.244.2.20
# Ports: 8080 (http)
#
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal SubsetsAdded 5m endpoint-controller Added endpoints to object
常见问题:Endpoint为空
这种情况我见过太多了,原因通常是:
- 标签不匹配:Service的selector和Pod的labels对不上
- Pod未就绪:Pod还在启动中,或者探针失败
- 命名空间错误:Service和Pod不在同一个namespace
# 检查Pod的标签
kubectl get pods -l app=api-backend --show-labels
# 检查Service的selector
kubectl get svc api-backend-svc -o yaml | grep -A5 selector
# 确保两边一致!
常见网络故障排查实战
故障一:Pod能ping通Service IP,但curl不通
这种情况很经典,说明网络层是通的,但应用层有问题。
# 先确认能ping通
kubectl exec -it nginx-frontend-1 -- ping -c 3 10.96.0.50
# 如果能ping通,但curl不通
kubectl exec -it nginx-frontend-1 -- curl -v http://10.96.0.50:80
# 如果timeout,检查端口是否正确
# Service定义的port和Pod实际监听的targetPort要对得上
可能的原因:
端口不匹配
# Service里定义的端口
spec:
ports:
- port: 80 # Service暴露的端口
targetPort: 8080 # Pod实际监听的端口
如果你的后端Pod实际上监听的是9090端口,但targetPort配置的是8080,那流量就会被转发到一个没有进程监听的端口,自然不通。
Pod没有Ready
# 检查Pod状态
kubectl get pods
# 如果STATUS是ContainerCreating或者Error,说明Pod还没好
# 查看事件
kubectl describe pod api-backend-2
# 重点看Events部分
防火墙规则
有时候Node上的防火墙会拦截流量:
# 在Node上检查iptables规则
iptables -L -n -v
# 检查是否有DROP或REJECT规则
# 如果有,需要调整
# 检查firewalld
systemctl status firewalld
故障二:跨节点Pod通信不通
这是最让人头疼的问题之一,因为涉及到节点间的网络连通性。
# 确认两个Pod在不同节点上
kubectl get pods -o wide
# 输出示例
NAME IP NODE
nginx-frontend-1 10.244.1.10 node-1
api-backend-2 10.244.2.20 node-2
跨节点通信需要保证:
- Node之间网络互通
- Pod网络Overlay正确配置
# 在Node-1上测试到Node-2的连通性
ping 192.168.1.101 # Node-2的IP
# 在Pod里测试跨节点连通性
kubectl exec -it nginx-frontend-1 -- curl -v http://10.244.2.20:8080
如果Node之间不通,那就是底层网络的问题了。检查内容:
# 检查Node间的网络
ping <other-node-ip>
# 检查MTU是否一致
ip link show
# 不同网络插件对MTU要求不同
# Calico默认要求MTU至少1440
# Flannel默认MTU是1450
MTU问题是一个经常被忽视的坑。如果你的底层网络MTU是1500,而Pod网络Overlay也需要一些头部空间,那就会产生 fragmentation,导致大包传输失败。
# 在Pod里测试大包传输
kubectl exec -it nginx-frontend-1 -- ping -s 1400 -M do 10.244.2.20
# 如果ping大包不通,小包通,那就是MTU问题
# 解决方案:调整网络插件的MTU设置
以Calico为例:
# calico.yaml配置
apiVersion: install.crd.projectcalico.org/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
bgp: Disabled
nodeAddressAutodetectionV4:
interface: eth0
mtu: 1440 # 根据底层网络调整
故障三:Service访问超时
# 测试Service ClusterIP是否可达
kubectl exec -it nginx-frontend-1 -- curl -v --max-time 5 http://10.96.0.50:80
# 如果超时,检查Endpoint是否存在
kubectl get endpoints api-backend-svc
# 如果Endpoint为空,检查selector
kubectl describe svc api-backend-svc
检查kube-proxy是否在正常工作
# 查看kube-proxy Pod状态
kubectl get pods -n kube-system | grep kube-proxy
# 查看kube-proxy日志
kubectl logs -n kube-system kube-proxy-xxxx
# 正常的日志应该定期输出信息
# 如果一直报错,可能是配置问题
检查iptables规则是否正确生成
# 在Node上查看iptables规则
iptables -t nat -L KUBE-SERVICES -n -v
# 应该能看到类似这样的规则
# Chain KUBE-SERVICES (1 references)
# pkts bytes target prot opt in out source destination
# 100 6000 KUBE-SVC-XXX tcp -- * * 0.0.0.0/0 10.96.0.50 /* default/api-backend-svc cluster IP */ tcp dpt:80
如果看不到对应的规则,说明kube-proxy没有正确同步。
故障四:DNS解析失败
Kubernetes里服务发现主要靠DNS,如果DNS不通,Service名称就无法解析。
# 测试DNS解析
kubectl exec -it nginx-frontend-1 -- nslookup api-backend-svc
# 或者用dig
kubectl exec -it nginx-frontend-1 -- dig api-backend-svc.default.svc.cluster.local
# 输出示例
# ;; ANSWER SECTION:
# api-backend-svc.default.svc.cluster.local. 30 IN A 10.96.0.50
检查CoreDNS状态
# 查看CoreDNS Pod
kubectl get pods -n kube-system | grep coredns
# 查看CoreDNS日志
kubectl logs -n kube-system <coredns-pod-name>
# 查看CoreDNS配置
kubectl get configmap coredns -n kube-system -o yaml
常见DNS问题排查
# 检查Pod的resolv.conf
kubectl exec -it nginx-frontend-1 -- cat /etc/resolv.conf
# 应该包含集群DNS服务器
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# 测试从Pod到CoreDNS的连通性
kubectl exec -it nginx-frontend-1 -- ping -c 3 10.96.0.10
# 检查CoreDNS Service
kubectl get svc -n kube-system kube-dns
CoreDNS配置问题
# 典型的CoreDNS配置
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
hosts /etc/coredns/NodeHosts {
fallthrough
}
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
故障五:外部无法访问Service
这通常涉及到Ingress或者LoadBalancer。
检查Ingress控制器
# 查看Ingress
kubectl get ingress
# 查看Ingress详情
kubectl describe ingress <ingress-name>
# 查看Ingress控制器Pod
kubectl get pods -n ingress-nginx
# 查看控制器日志
kubectl logs -n ingress-nginx <controller-pod-name>
检查LoadBalancer Service
# 查看LoadBalancer状态
kubectl get svc <lb-service-name>
# 如果EXTERNAL-IP一直是<pending>
# 说明云厂商的负载均衡器没有创建成功
# 查看事件
kubectl describe svc <lb-service-name>
一套完整的排查工具集
必备命令速查
# 1. 查看Pod网络信息
kubectl get pods -o wide
# 2. 查看Service和Endpoint
kubectl get svc
kubectl get endpoints
# 3. 查看NetworkPolicy
kubectl get networkpolicy -A
# 4. 测试连通性
kubectl exec -it <pod> -- curl -v <target>
kubectl exec -it <pod> -- ping <target-ip>
kubectl exec -it <pod> -- nslookup <service-name>
# 5. 查看kube-proxy状态
kubectl get pods -n kube-system | grep kube-proxy
kubectl logs -n kube-system <kube-proxy-pod>
# 6. 查看CNI插件状态
kubectl get pods -n kube-system | grep -E 'calico|flannel|cilium'
# 7. 查看DNS状态
kubectl get pods -n kube-system | grep coredns
kubectl logs -n kube-system <coredns-pod>
自动化排查脚本
我平时做故障排查会用这个脚本,帮你快速定位问题:
#!/bin/bash
# k8s-network-check.sh - Kubernetes网络健康检查脚本
set -e
NAMESPACE="${1:-default}"
TARGET_POD="${2:-}"
echo "======================================"
echo "Kubernetes Network Health Check"
echo "Namespace: $NAMESPACE"
echo "Time: $(date)"
echo "======================================"
# 检查Pod状态
echo ""
echo "[1/6] Checking Pod Status..."
kubectl get pods -n "$NAMESPACE" -o wide
# 检查Service和Endpoint对应关系
echo ""
echo "[2/6] Checking Service-Endpoint Mapping..."
for svc in $(kubectl get svc -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}'); do
endpoints=$(kubectl get endpoints "$svc" -n "$NAMESPACE" -o jsonpath='{.subsets[*].addresses[*].ip}')
if [ -z "$endpoints" ]; then
echo " WARNING: Service $svc has no endpoints!"
else
echo " OK: Service $svc -> $endpoints"
fi
done
# 检查NetworkPolicy
echo ""
echo "[3/6] Checking Network Policies..."
policies=$(kubectl get networkpolicy -n "$NAMESPACE" --no-headers 2>/dev/null | wc -l)
if [ "$policies" -gt 0 ]; then
echo " Found $policies NetworkPolicy(s):"
kubectl get networkpolicy -n "$NAMESPACE"
else
echo " No NetworkPolicy found in namespace $NAMESPACE"
fi
# 检查CoreDNS
echo ""
echo "[4/6] Checking CoreDNS..."
coredns_pod=$(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -n "$coredns_pod" ]; then
coreDNSReady=$(kubectl get pod "$coredns_pod" -n kube-system -o jsonpath='{.status.containerStatuses[0].ready}')
if [ "$coreDNSReady" = "true" ]; then
echo " OK: CoreDNS is running"
else
echo " WARNING: CoreDNS is not ready!"
fi
else
echo " ERROR: CoreDNS pod not found!"
fi
# 检查kube-proxy
echo ""
echo "[5/6] Checking kube-proxy..."
proxy_count=$(kubectl get pods -n kube-system -l app=kube-proxy --no-headers | wc -l)
echo " Found $proxy_count kube-proxy pod(s)"
kubectl get pods -n kube-system -l app=kube-proxy
# 检查CNI插件
echo ""
echo "[6/6] Checking CNI Plugin..."
cni_pods=$(kubectl get pods -n kube-system -o jsonpath='{.items[*].metadata.labels.k8s-app}' | grep -oE 'calico|flannel|canal|cilium' | sort -u)
if [ -n "$cni_pods" ]; then
echo " Detected CNI: $cni_pods"
else
echo " WARNING: Could not detect CNI plugin"
fi
# 如果指定了Target Pod,做连通性测试
if [ -n "$TARGET_POD" ]; then
echo ""
echo "[BONUS] Connectivity Test from $TARGET_POD..."
target_ip=$(kubectl get pod "$TARGET_POD" -n "$NAMESPACE" -o jsonpath='{.status.podIP}')
echo " Target Pod IP: $target_ip"
# 测试DNS解析
echo " Testing DNS resolution..."
kubectl exec "$TARGET_POD" -n "$NAMESPACE" -- nslookup kubernetes.default.svc.cluster.local || echo " DNS resolution failed!"
# 测试API Server连通性
echo " Testing API Server connectivity..."
kubectl exec "$TARGET_POD" -n "$NAMESPACE" -- curl -s --max-time 5 https://kubernetes.default.svc.cluster.local/api/ > /dev/null && echo " OK: Can reach API Server" || echo " WARNING: Cannot reach API Server"
fi
echo ""
echo "======================================"
echo "Check completed at $(date)"
echo "======================================"
使用方法:
# 对整个namespace做检查
./k8s-network-check.sh default
# 指定namespace和目标Pod
./k8s-network-check.sh production my-frontend-pod
网络问题排查的思维框架
排查网络问题最忌讳的就是乱试命令。我总结了一个思维框架,你遇到任何问题都可以按照这个思路来:
1. 明确现象
- 谁能访问谁?
- 什么协议?(TCP/UDP/HTTP/DNS)
- 什么错误?(timeout/refused/DNS failure)
2. 分层排查
- 物理层:Node之间网络通吗?
- 网络层:Pod IP能ping通吗?
- 传输层:端口通吗?
- 应用层:服务正常吗?
3. 定位组件
- CNI插件状态?
- kube-proxy状态?
- DNS状态?
- NetworkPolicy?
4. 验证假设
- 每一步都验证,不要跳步
- 用排除法定位问题
最后说两句
Kubernetes的网络确实复杂,但只要你理解了Pod网络、Service、Endpoint、kube-proxy、CNI这些核心组件是怎么协作的,大多数问题都能迎刃而解。
记住,能复现的问题就解决了一半。当你遇到网络问题时,先写出一个最小可复现的测试用例,比如最简单的两个Pod互相访问,然后一步步添加复杂性,看哪一步开始出问题。
如果你在实际操作中遇到什么奇怪的问题,欢迎随时来找我聊聊。网络问题虽然讨厌,但解决后的成就感也是真的很爽。
