☰
K8s Service访问失败的五层分步排查法
2026/10/21 4:10:20 网站建设 项目流程

1. 项目概述:为什么一个Service访问不了,会让人连续盯屏三小时?

K8s常见问题:Service 不能访问排查流程——这标题不是技术文档的冷冰冰条目,而是某天凌晨两点我合上笔记本前,刚在生产环境里亲手走完的第七遍完整链路。它背后站着的,是真实世界里最常被低估的“连接失效”困境:Pod明明Running,日志一切正常,curl localhost:8080也返回200,但外部一调用Service ClusterIP就超时;Ingress配置没动过,却突然503;NodePort在节点上能通,从集群外却连不上。这些不是报错,是沉默的失败,比崩溃更难定位。

核心关键词就三个:K8s Service、网络连通性、分层排查。它们共同指向一个本质问题:Kubernetes的Service抽象层,本质上是一套由iptables/ipvs规则+Endpoint动态同步+DNS解析+网络插件协同构成的“隐形管道系统”。任何一层出现微小偏移——比如Endpoint未同步、kube-proxy未生效、CNI插件路由表缺失、甚至CoreDNS缓存污染——都会导致流量在某个环节无声蒸发。这不是代码bug,是系统级状态不一致。

这个排查流程适合三类人:刚通过CKA认证但还没在真实集群里“流过血”的新人;正在值班、被告警电话叫醒、需要30分钟内定位根因的SRE;还有那些正准备把老旧单体应用迁入K8s、提前想摸清网络底细的架构师。它不教你怎么写YAML,而是给你一套可逐层敲命令、看输出、做验证的“听诊器”。你不需要背熟所有参数,只要记住:每一步验证,都必须有明确的预期结果和对应的失败含义。比如kubectl get endpoints my-svc返回空列表,那问题一定出在Selector匹配或Pod标签上,后面所有网络检查都是徒劳。这种确定性,才是快速止血的关键。

我试过把整个流程压缩成一张脑图贴在显示器边框,也整理过几十个case的真实日志片段。你会发现,90%的Service不可访问问题,其实只集中在四个断点:Pod是否真就绪且被选中、Endpoints是否实时同步、ClusterIP规则是否加载、DNS解析是否准确指向该IP。剩下的10%,才是CNI底层、kube-proxy模式切换、甚至宿主机防火墙这种“深水区”。所以这篇内容,我们不堆概念,不讲源码,就按你实际坐在终端前的顺序,一行命令、一个判断、一次验证地往下推。你现在打开终端,我来告诉你下一步该敲什么。

2. 整体设计思路:为什么必须按“Pod → Endpoint → Service → DNS → 外部”五层推进?

2.1 排查不是搜索,是排除:每一层都必须有“证伪”动作

很多人一遇到Service不通,第一反应是kubectl describe svc,然后盯着Events里那行“no endpoints available”发呆。但这句话本身不解决问题——它只是现象,不是原因。真正的排查,是构建一个逻辑严密的排除链:只有当前层被证实“工作正常”,才能进入下一层;一旦某层失败,就必须在此层深挖,直到找到唯一确定的故障点。这就像修水管,你不会一上来就砸墙找主管道,而是先看水龙头有没有水,再查阀门,再查接头,最后才怀疑主管道破裂。

K8s Service的流量路径天然具备清晰的五层结构:

  1. Pod层:后端容器是否真的在运行、是否通过了readinessProbe、标签是否匹配Service的selector;
  2. Endpoint层:kube-controller-manager是否成功将匹配的Pod IP写入Endpoint对象;
  3. Service层(ClusterIP):kube-proxy是否将Endpoint列表编译为iptables/ipvs规则,并正确加载到节点内核;
  4. DNS层:CoreDNS是否能将my-svc.default.svc.cluster.local解析为正确的ClusterIP;
  5. 外部访问层:NodePort/LoadBalancer/Ingress是否正确转发到ClusterIP,以及客户端网络策略是否放行。

这五层不是并列关系,而是强依赖的流水线。第2层Endpoint为空,第3层规则再完美也无流量可转;第4层DNS解析错误,第5层Ingress配置得再漂亮,客户端拿到的也是个假IP。因此,我们的整体设计就是严格遵循这个数据流向,拒绝跳跃,拒绝猜测,用最小成本的命令验证每一层的状态。比如验证Endpoint层,只需一条kubectl get endpoints,耗时不到0.2秒,却能直接排除掉50%以上的常见问题。

2.2 工具选型逻辑:为什么只用kubectl + curl + iptables-save,而不用Weave Scope或Lens?

市面上有太多炫酷的K8s可视化工具,Weave Scope能画出漂亮的流量图,Lens能高亮显示异常Pod。但在高压排障场景下,它们反而成了负担:Scope需要额外部署DaemonSet,Lens依赖GUI响应速度,而你真正需要的,可能只是确认10.96.123.45:80这个IP是否在iptables的KUBE-SERVICES链里。这时候,原生命令的确定性和即时性无可替代。

  • kubectl是唯一真相源:所有对象状态(Pod、Endpoint、Service、ConfigMap)都以etcd为最终存储,kubectl get直接读取API Server缓存,延迟低于100ms;
  • curl是协议层探针:它不关心K8s抽象,只认TCP/IP和HTTP状态码。curl -v http://10.96.123.45:80能同时验证三层:网络可达性(TCP握手)、服务端口监听(Connection refused or timeout)、HTTP响应(200/503);
  • iptables-save是内核规则快照:kube-proxy生成的规则最终落地为iptables链,iptables-save | grep KUBE-SVC-能100%确认规则是否存在、目标DNAT是否正确。

我曾在一个金融客户现场,用iptables-save发现kube-proxy的ipvs模式被误切回iptables,但kubectl get pods -n kube-system里所有组件都显示Running——因为健康检查只探活kube-proxy进程,不校验其实际工作模式。这种“表面正常,内里失效”的情况,只有直击内核规则才能暴露。所以本流程彻底放弃任何第三方UI工具,所有判断依据,必须来自这三类命令的原始输出。这不是复古,是回归本质:当系统复杂度飙升时,最可靠的永远是离内核最近的那层命令。

2.3 模式选择原则:iptables vs ipvs,为什么默认按iptables路径设计?

K8s支持两种kube-proxy后端模式:iptables和ipvs。前者基于Linux内核的iptables模块,后者基于更高效的ipvs内核模块。虽然ipvs在大规模集群中性能更好,但它的调试门槛显著更高:ipvsadm -Ln输出格式晦涩,NAT规则追踪需结合conntrack -L,且部分老版本内核不支持ipvs。而iptables规则是明文可读的,-j DNAT --to-destination 10.244.1.3:8080这种写法,连非网络工程师都能看懂。

因此,本流程默认按iptables模式展开。但这不意味着忽略ipvs。在关键步骤(如验证Service规则层)中,我会明确标注“若使用ipvs,请执行ipvsadm -Ln | grep <svc-name>”,并解释其输出字段含义。更重要的是,我会强调一个实操铁律:在开始排查前,必须先确认当前集群使用的kube-proxy模式。方法很简单:kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode。如果输出是mode: ipvs,那么后续所有iptables相关命令都要切换为ipvs对应命令。这个确认动作,能避免你在iptables路径上浪费两小时,而问题根源其实在ipvs的virtual server配置错误。

提示:不要假设模式。我见过太多团队,文档写着“已启用ipvs”,结果kubectl get cm kube-proxy -n kube-system里mode字段还是空的,默认回退到iptables。K8s的“默认行为”永远比文档更真实。

3. 核心细节解析与实操要点:从Pod就绪到DNS解析的逐层验证

3.1 Pod层验证:就绪(Ready)不等于可用(Usable)

kubectl get pods输出里的1/1状态,只代表容器进程启动成功,绝不保证它能处理请求。真正的可用性,由readinessProbe决定。很多故障的起点,就是Pod卡在Running但NotReady状态,而kubectl get pods默认不显示Conditions详情。

必须执行的验证命令:

kubectl get pods -l app=my-app --show-labels # 确认Pod标签是否匹配Service的selector(如selector: {app: my-app}) kubectl get pods -l app=my-app -o wide # 查看Pod IP和所在节点,为后续节点级检查做准备 kubectl describe pod <pod-name> # 重点看Events和Conditions段

在kubectl describe pod输出中,死死盯住这两个区域:

  • Conditions:查找Ready: False,并看Message字段。常见原因有:
    • Readiness probe failed:readinessProbe的HTTP请求返回非2xx/3xx,或TCP端口未监听;
    • ContainersNotReady:某个容器启动失败,但主容器已running(多容器Pod常见);
  • Events:查找FailedCreatePodSandBox(CRI运行时问题)、FailedMount(存储卷挂载失败,导致应用无法启动)等。

实操心得:我习惯在describe输出后,立刻用curl直连Pod IP测试。例如,若kubectl get pods -o wide显示Pod IP是10.244.1.3,端口是8080,则执行:

curl -v http://10.244.1.3:8080/healthz # 注意:这里必须用Pod IP,不是ClusterIP!这是绕过Service层,直击源头。

如果curl返回Connection refused,说明应用根本没监听该端口——可能是应用配置错误,或容器内进程未启动;如果返回timeout,则是网络层面问题(如CNI插件故障);如果返回200,恭喜,Pod层已通关。

注意:curl测试必须在集群内节点执行。如果你在本地机器跑curl,它走的是外部网络,无法验证Pod IP的可达性。正确做法是kubectl exec -it <some-pod> -- sh,进入任意一个同命名空间的Pod,再从那里curl目标Pod IP。这样确保流量完全在集群Overlay网络内流转。

3.2 Endpoint层验证:Service的“血管图谱”是否实时更新?

Endpoint对象是Service与Pod之间的桥梁。kube-controller-manager持续监听Pod事件,一旦发现新Pod匹配Service selector,就立即更新Endpoint对象。如果这个同步链断裂,Service就会变成“空壳”。

核心验证命令:

kubectl get endpoints my-svc # 最简验证:看ENDPOINTS列是否有IP:Port kubectl get endpoints my-svc -o wide # 查看SUBSETS字段,确认addresses和notReadyAddresses分布 kubectl describe endpoints my-svc # 重点看Events:是否有"endpoints for service/my-svc changed"记录

kubectl get endpoints的输出是黄金标准。如果ENDPOINTS列为空(显示<none>),问题100%出在Pod层或Selector匹配上。此时无需往下查,直接回到3.1节重新核对Pod标签和Service selector。

如果ENDPOINTS有值,但数量远少于Pod数量(如kubectl get pods -l app=my-app显示5个Pod,而kubectl get endpoints只显示2个IP),则要检查Pod的readinessProbe状态。kubectl describe pod中Conditions的Ready: False会直接导致该Pod被剔除出Endpoint列表。

一个易被忽略的细节:Endpoint对象本身也有age字段。如果kubectl get endpoints my-svc显示AGE是3d,而你刚刚部署了新Pod,这说明同步严重滞后。此时应检查kube-controller-manager日志:kubectl logs -n kube-system $(kubectl get pods -n kube-system | grep controller-manager | awk '{print $1}') | tail -20,查找error syncing endpoints类报错。

3.3 Service层验证:ClusterIP规则是否真正“活”在内核里?

即使Endpoint列表完美,Service仍可能不可访问——因为kube-proxy生成的iptables/ipvs规则没有加载到节点内核,或者被其他规则拦截。

iptables模式下的关键验证:

# 1. 获取Service的ClusterIP和端口 kubectl get svc my-svc -o jsonpath='{.spec.clusterIP}:{.spec.ports[0].port}' # 假设输出:10.96.123.45:80 # 2. 在任一运行Pod的节点上,检查iptables规则 iptables-save | grep -A 5 "KUBE-SVC-.*10.96.123.45" # 正确输出应包含:-A KUBE-SERVICES -d 10.96.123.45/32 -p tcp --dport 80 -j KUBE-SVC-XXXXXX # 3. 追踪该Service的DNAT链 iptables-save | grep "KUBE-SVC-XXXXXX" | head -10 # 应看到类似:-A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-YYYYYY # 这表示流量被随机分发到后端Endpoint

ipvs模式下的关键验证:

# 1. 获取Service的ClusterIP和端口(同上) # 2. 在节点上检查ipvs virtual server ipvsadm -Ln | grep "10.96.123.45:80" # 正确输出:TCP 10.96.123.45:80 rr # 3. 查看real server(即Endpoint) ipvsadm -Ln | grep -A 5 "10.96.123.45:80" # 应显示后端Pod IP和端口,如:-> 10.244.1.3:8080 Masq 1 0 0

实操心得:规则存在≠规则生效。我曾遇到一个案例:iptables-save里规则齐全,但curl 10.96.123.45:80仍超时。最终发现是节点上的firewalld服务被意外启动,其默认规则链FORWARD被设置为DROP,而kube-proxy的规则在nat表,不经过filter表的FORWARD链。解决方案是systemctl stop firewalld并systemctl disable firewalld。所以,在确认规则存在后,务必用curl在节点上实测:curl -v http://10.96.123.45:80。如果失败,立即检查节点防火墙状态。

3.4 DNS层验证:服务名能否正确翻译成ClusterIP?

K8s中,服务发现高度依赖CoreDNS。my-svc.default.svc.cluster.local这个FQDN,必须被准确解析为Service的ClusterIP。DNS解析失败,是外部访问(如Ingress、curl svc-name)失败的最常见原因之一。

分步验证DNS:

# 1. 在集群内Pod中执行(推荐用busybox镜像) kubectl run dns-test --image=busybox:1.35 --rm -it --restart=Never -- nslookup my-svc.default.svc.cluster.local # 2. 如果失败,检查CoreDNS Pod状态 kubectl get pods -n kube-system -l k8s-app=kube-dns # 3. 检查CoreDNS ConfigMap kubectl get configmap coredns -n kube-system -o yaml # 重点看plugins段,确认forward插件是否指向合法上游DNS(如1.1.1.1)

nslookup输出必须包含Address: 10.96.123.45(即你的Service ClusterIP)。如果返回server can't find my-svc.default.svc.cluster.local: NXDOMAIN,说明CoreDNS根本没加载该Service记录,常见原因:

  • CoreDNS Pod未Running(kubectl get pods -n kube-system查看);
  • Service和Pod不在同一命名空间,而查询时未指定完整FQDN(如只查my-svc,而非my-svc.default.svc.cluster.local);
  • CoreDNS ConfigMap中kubernetes插件的pods insecure参数未开启,导致无法解析Pod IP(此参数影响Service解析,需确认)。

一个硬核技巧:当nslookup失败时,不要急着重启CoreDNS。先用dig获取更详细信息:

kubectl exec -it dns-test -- dig my-svc.default.svc.cluster.local @10.96.0.10 # 10.96.0.10是CoreDNS的ClusterIP,@指定DNS服务器,绕过本地resolv.conf

dig输出的ANSWER SECTION会明确告诉你是否返回了IP,AUTHORITY SECTION会显示SOA记录,帮助判断是权威DNS问题还是递归查询问题。

4. 实操过程与核心环节实现:一次完整的线上故障复现与解决

4.1 故障现场还原:一个真实的“503 Service Unavailable”案例

时间:某周三下午3:15
告警:Prometheus监控显示,ingress-nginx控制器对api-service的健康检查连续5分钟返回503
现象:

  • curl https://api.example.com/v1/status→503 Service Temporarily Unavailable
  • curl http://10.96.123.45:80(Service ClusterIP)→timeout
  • kubectl get pods -l app=api→5/5 Running, 5/5 Ready
  • kubectl get endpoints api-service→10.244.1.3:8080,10.244.1.4:8080,10.244.2.5:8080(3个IP,符合预期)

这是一个典型的“表象正常,实则中断”案例。Pod就绪,Endpoint同步,但ClusterIP不可达。按照五层排查法,我们从第3层(Service层)切入。

4.2 逐层执行与关键输出分析

Step 1:确认kube-proxy模式

kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode # 输出:mode: iptables # 确认走iptables路径

Step 2:提取Service ClusterIP和端口

kubectl get svc api-service -o jsonpath='{.spec.clusterIP}:{.spec.ports[0].port}' # 输出:10.96.123.45:80

Step 3:登录任一运行api Pod的节点(如node-01),检查iptables规则

# 先确认节点上是否有该Service规则 iptables-save | grep "10.96.123.45" | head -3 # 输出:-A KUBE-SERVICES -d 10.96.123.45/32 -p tcp --dport 80 -j KUBE-SVC-ABCD1234 # 规则存在,继续追踪 iptables-save | grep "KUBE-SVC-ABCD1234" | head -5 # 输出: # -A KUBE-SVC-ABCD1234 -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-EFGH5678 # -A KUBE-SVC-ABCD1234 -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-IJKL9012 # -A KUBE-SVC-ABCD1234 -j KUBE-SEP-MNOP3456 # 三条规则,对应3个Endpoint,概率加起来为1,正常

Step 4:在node-01上实测ClusterIP

curl -v http://10.96.123.45:80/v1/status # 输出:* Connection timed out after 30001 milliseconds # 明确超时,问题锁定在iptables规则未生效或被拦截

Step 5:检查节点防火墙

systemctl status firewalld # 输出:firewalld.service - firewalld - dynamic firewall daemon # Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled) # Active: active (running) since Wed 2023-10-11 14:22:11 CST; 1h 23min ago # firewalld正在运行!这就是元凶。

Step 6:临时关闭firewalld验证

systemctl stop firewalld curl -v http://10.96.123.45:80/v1/status # 输出:HTTP/1.1 200 OK # {"status":"ok","timestamp":"2023-10-11T15:45:22Z"} # 立刻恢复!证明问题根因是firewalld的FORWARD链拦截

4.3 根因定位与永久修复方案

为什么firewalld会拦截?
firewalld默认策略是DROP所有FORWARD流量。而kube-proxy的iptables规则位于nat表,负责DNAT(目标地址转换),但流量进入节点后,仍需经过filter表的FORWARD链进行包过滤。由于FORWARD链默认DROP,且firewalld未配置允许K8s网段(10.244.0.0/16)的流量,导致DNAT后的包在FORWARD链被丢弃。

永久修复命令(在所有worker节点执行):

# 1. 添加firewalld rich rule,允许K8s Pod网段 firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.244.0.0/16" accept' # 2. 添加rich rule,允许Service ClusterIP网段(通常是10.96.0.0/12) firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.96.0.0/12" accept' # 3. 重载firewalld配置 firewall-cmd --reload # 4. 验证规则已加载 firewall-cmd --list-all | grep "10.244\|10.96" # 应输出包含上述两个网段的accept规则

验证修复效果:

# 重启firewalld systemctl start firewalld # 再次测试ClusterIP curl -v http://10.96.123.45:80/v1/status # 返回200 OK # 测试外部Ingress curl -k https://api.example.com/v1/status # 返回200 OK,告警解除

实操心得:这个案例教会我一个铁律——在任何K8s网络问题排查中,节点级防火墙永远是最后检查项,但却是最高频的根因。因为它的影响是全局的、静默的,且kubectl命令完全无法感知。所以,我把systemctl status firewalld && firewall-cmd --list-all加入了自己的排障checklist第一条,无论问题看起来多么“高级”。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 “Endpoints为空”但Pod标签完全匹配?检查Service的namespace!

这是新手最常踩的坑。kubectl get svc默认只查default命名空间,而你的Pod可能在prod命名空间。kubectl get svc -n prod才能看到正确的Service。

速查命令:

# 查看Service所在命名空间 kubectl get svc my-svc --all-namespaces # 查看Pod所在命名空间 kubectl get pods --all-namespaces -l app=my-app # 确保两者namespace一致!否则Endpoint永远为空。

避坑技巧:在写Service YAML时,永远显式声明namespace字段,而不是依赖kubectl apply的默认行为。例如:

apiVersion: v1 kind: Service metadata: name: my-svc namespace: prod # 显式声明,杜绝歧义 spec: selector: app: my-app ports: - port: 80

5.2curl ClusterIP成功,但curl svc-name失败?DNS缓存作祟!

CoreDNS会缓存负向查询(NXDOMAIN),如果之前my-svc不存在,后来创建了,但旧的NXDOMAIN缓存未过期,nslookup仍会返回NXDOMAIN。

清理缓存命令:

# 1. 获取CoreDNS Pod名称 COREDNS_POD=$(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}') # 2. 发送SIGUSR1信号强制刷新缓存(需CoreDNS v1.8.0+) kubectl exec -n kube-system $COREDNS_POD -- kill -USR1 1 # 3. 验证缓存已清空(查看日志) kubectl logs -n kube-system $COREDNS_POD | tail -5 # 应看到:[INFO] Reloading complete

替代方案(通用):重启CoreDNS Pod,强制重建缓存:

kubectl delete pod -n kube-system -l k8s-app=kube-dns

5.3 NodePort在节点上能通,但从集群外无法访问?云厂商安全组是隐形墙!

在AWS/Azure/GCP上,NodePort端口(30000-32767)必须在云平台的安全组(Security Group)中显式放行。kubectl get nodes -o wide查到的节点公网IP,其安全组默认是拒绝所有入站流量的。

检查步骤:

  1. 登录云控制台,找到对应节点的实例;
  2. 查看其关联的安全组;
  3. 确认入站规则(Inbound Rules)中,有Type: Custom TCP, Port Range: 30000-32767, Source: 0.0.0.0/0(或你的客户端IP);
  4. 若无,添加该规则并保存。

验证命令(在客户端机器执行):

telnet <node-public-ip> 30001 # 如果连接失败,99%是安全组未放行

5.4 Ingress返回502 Bad Gateway?检查Ingress Controller的Service类型!

ingress-nginx控制器本身是一个Deployment,它需要一个Service暴露给外部。如果这个Service的type被误设为ClusterIP(默认值),那么Ingress Controller就无法接收外部流量,所有Ingress规则都会返回502。

正确配置:

apiVersion: v1 kind: Service metadata: name: ingress-nginx-controller namespace: ingress-nginx spec: type: LoadBalancer # 或 NodePort,绝不能是ClusterIP! ports: - port: 80 targetPort: 80 selector: app.kubernetes.io/name: ingress-nginx

速查命令:

kubectl get svc -n ingress-nginx ingress-nginx-controller # TYPE列必须是LoadBalancer或NodePort

5.5 “所有检查都通过,但就是不通”终极排查表

当以上所有步骤都确认无误,问题仍未解决时,启用这张终极排查表:

检查项命令预期结果失败含义
节点内核路由ip route get 10.244.1.3(任一Endpoint IP)10.244.1.3 via 10.0.2.2 dev eth0 src 10.0.2.15 uid 0节点无法路由到Pod网段,CNI插件未正确配置
Pod网络连通性kubectl exec <pod-a> -- ping -c 3 10.244.1.364 bytes from 10.244.1.3: icmp_seq=1 ttl=63 time=0.234 msCNI网络平面故障,Pod间二层不通
kube-proxy日志kubectl logs -n kube-system <kube-proxy-pod> | grep -i "error|fail"无error/fail输出kube-proxy自身异常,如无法连接API Server
API Server压力kubectl get --raw='/metrics' | grep 'apiserver_request_total{verb="LIST",resource="endpoints"}'数值稳定增长API Server过载,导致Endpoint同步延迟

我个人在实际操作中的体会是:90%的“疑难杂症”,根源都在前三层(Pod、Endpoint、Service)的某个细节被忽略。比如readinessProbe的initialDelaySeconds设为30秒,而应用启动只需5秒,导致Pod就绪前就被加入Endpoint;又比如Service的port和targetPort写反,ClusterIP规则指向了错误的Pod端口。所以,永远不要跳过kubectl describe的完整输出,尤其是Events和Conditions。那些看似冗余的字段,往往是破案的关键线索。这个排查流程,我用了三年,从第一次花四小时定位一个标签拼写错误,到现在平均15分钟闭环,靠的不是天赋,是把每一步验证都刻进肌肉记忆。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询