你是不是也遇到过这种场景:大半夜被告警震醒,打开监控一看------服务响应时间从 50ms 飙到了 5s,用户反馈"转圈圈转不动"。你登录集群,kubectl get pods 一看,全是 Running,一脸懵:进程没挂,但就是慢。
这种"假活"状态最折磨人。我干了 10 年运维,踩过无数坑,今天把 Kubernetes 环境下服务变慢的排查思路整理出来,希望能帮你少熬几个大夜。
核心思路:4 层排查法
不要一上来就查代码、猜原因。从外向里、从底层到上层,按这个顺序来:
资源层 → 应用层 → 网络层 → 存储/依赖层
📋 前置条件
- 有 kubectl 权限访问目标集群
- Metrics Server 已部署------
kubectl top依赖它(kubectl get deployment metrics-server -n kube-system) - 如果你用 Istio/Linkerd,准备好
istioctl或linkerd命令 - 生产环境慎用破坏性操作(strace 可能卡住进程),优先用临时 Pod + ephemeral containers
🚨 第一层:资源层
这一层先快速判断 Pod 或节点是不是"吃不消了"。
1. 找出可疑 Pod
# 看节点资源
kubectl top nodes
# 按 CPU 排序看 Pod
kubectl top pods --all-namespaces --sort-by=cpu
输出示例:
NAMESPACE NAME CPU(cores) CPU%
default api-server-7d5b6c8-h8 800m 80%
default db-postgres-0 2500m 250% <-- 超标了
我的判断规则:
- Pod CPU 超过 limit 的 80% → 可能被限流,用
kubectl describe pod看Throttled指标 - Pod 内存持续逼近 limit → OOM 风险。
kubectl top是实时快照,趋势要看 Prometheus - 节点资源不足且驱逐事件多 → 查
kubectl get events -n default --sort-by='.lastTimestamp'
2. 进 Pod 内部抓内鬼
kubectl exec -it <pod-name> -- /bin/sh
# 进去后
top -b -n 1 | head -20
# 或
ps aux --sort=-%cpu | head -10
⚠️ 警告: Distroless 镜像(比如谷歌的 gcr.io/distroless)没有 bash 和 top,不要慌------用 kubectl debug 创建一个临时容器挂进去。
kubectl debug -it <pod-name> --image=busybox --target=<container-name>
3. CPU 100% 怎么办
strace 是定位高 CPU 的神器:
# 进 Pod 找到高 CPU 的 PID
kubectl exec -it <pod-name> -- top -b -n 1
# 追踪系统调用
strace -c -p <PID> # 汇总哪个系统调用占用最高
strace -p <PID> -T # 实时输出,带耗时
(顺便说一句,strace 会略微拉慢进程,别在生产高峰用。用几分钟立刻停。)
4. 内存问题
看 RSS(常驻内存)和 cache 占用:
kubectl exec -it <pod-name> -- cat /proc/<PID>/status | grep -E "VmRSS|VmSwap"
如果 Pod 不断重启(CrashLoopBackOff),kubectl describe pod 里通常能看到 OOMKilled。
💡 彩蛋: 如果节点 CPU 正常但 Pod 很慢,检查是否有 CPU 限流:
kubectl get pod <pod-name> -o yaml | grep -A 5 "limits:"
CPU limit 设得过低会导致 CPU Throttling ------CPU 没用满但请求被强行排队。相关指标在 Prometheus:container_cpu_cfs_throttled_seconds_total。监控要是没配,看 kubectl describe pod 的 Events。
🚨 第二层:应用层
资源没问题?进应用内部看看。
1. 看日志------但别瞎看
# 查慢请求日志
kubectl logs --tail=500 <pod-name> | grep -E "slow|timeout|error"
# 实时追踪
kubectl logs -f <pod-name> --since=10m
我踩过的坑: 日志写太多本身就会拖慢服务。日志级别设成 DEBUG?生产环境赶紧改回 INFO/WARN。
2. 链路追踪(如果你还没用,真的该上了)
说实话,分布式追踪是排查微服务慢请求的救命稻草。Jaeger 配合 Istio 或 OpenTelemetry,能清清楚楚看到每个请求在哪个环节卡住------是 A 服务处理慢,还是调用 B 服务的网络慢。
访问 Jaeger UI(通常 ingress 或 port-forward),按 Trace ID 搜一个慢请求,看火焰图就一目了然。
还没上 tracing? 有个笨办法但有效:在 Pod 里用 curl -w "@curl-format.txt" -o /dev/null -s "http://downstream-service" 手工测下游依赖。格式文件大概长这样:
# curl-format.txt
time_namelookup: %{time_namelookup}s\n
time_connect: %{time_connect}s\n
time_starttransfer: %{time_starttransfer}s\n
3. pprof(如果你用 Go)
# 前提:应用开启了 /debug/pprof
kubectl port-forward <pod-name> 8080:8080
go tool pprof -http=:8081 http://localhost:8080/debug/pprof/profile?seconds=30
浏览器打开 localhost:8081,火焰图直接告诉你哪个函数在烧 CPU。
💡 彩蛋: Java 服务?jstack。进 Pod 执行 jstack <PID> 导出线程栈。如果一个 Pod 里装了 JDK,镜像很大,用 kubectl debug --image=openjdk:11-jre-slim 挂进去。
🚨 第三层:网络层
这一层坑最多,我分成三类讲。
类型一:Service Mesh/Ingress 层面的延迟
# Istio 环境看代理延迟
istioctl dashboard kiali
# 或
kubectl exec -it <pod-name> -c istio-proxy -- pilot-agent request GET stats
坑爹的是,有时候 Istio 配置没毛病,但 Pod 重启时 sidecar 先加载了旧配置缓存------一个过时的 ServiceEntry 可能把请求发到无效 IP。测试方法:临时停用 sidecar 注入(给命名空间去掉 istio-injection=enabled 标签),看延迟是否消失。
类型二:DNS 解析慢
CoreDNS 是无声的性能杀手。
# 检查 CoreDNS Pod 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 看 CoreDNS 日志
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100
如果 CoreDNS 大量报 i/o timeout,别光盯着 DNS 服务器------检查 CNI 网络插件的性能和 Pod 与 CoreDNS 之间的连通性。
DNS 慢的典型特征:服务第一次调用慢,后面快。说明是 DNS 解析没缓存。生产环境强烈推荐启用 NodeLocal DNSCache。
类型三:网络策略冲突
最隐蔽的坑。新节点加入集群,CNI 插件版本不一致,网络策略控制器没同步------服务能通但数据包在某个节点被拦。故障特征:非对称------部分 Pod 正常,部分慢。A 节点上的 Pod 跨节点调用 B 节点时,只有部分包丢了。
测试方法:
# 找个临时 Pod 去 ping 目标 Service IP
kubectl run test-pod --image=busybox -it --rm -- nslookup <service-name>
# 或绕开 Service,直接用 ClusterIP
kubectl run test-pod --image=busybox -it --rm -- wget -O- http://<cluster-ip>:<port>
如果 ClusterIP 直接访问正常,但经过 Service 就慢 → DNS 或 kube-proxy 层面的问题。
💡 彩蛋:conntrack 表被撑爆
NodePort 模式下,请求量大时 conntrack 表会满,导致 TCP 重传和延迟飙升。
# 在节点上检查 conntrack 统计(需要节点访问权限)
conntrack -S
# 看 "insert_failed" 或 "drop" 计数,涨得快就说明 conntrack 满了
# 检查当前连接表大小
sysctl net.netfilter.nf_conntrack_max
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
conntrack 坑的一个真实案例:WHOOP 团队发现 Ubuntu 自动安全更新触发 needrestart,重启网络服务时清空了 conntrack 表,导致 RDS 和 ElastiCache 的返回包被丢弃,响应延迟从 1 秒跳到 20 秒。
🚨 第四层:存储与依赖
1. PVC 存储 I/O 慢
数据库或日志写到持久卷,I/O 慢会拖垮整个 Pod。
kubectl describe pvc <pvc-name>
# 看 Events 有没有 I/O 超时、挂载失败
# 进 Pod 用 dd 和 iostat(需要进节点或 debug 容器)
dd if=/dev/zero of=/mnt/data/test bs=1M count=100 conv=fdatasync
生产环境建议用 Inspektor Gadget 的 top_blockio,它能追踪容器级别的磁盘 I/O 延迟。
2. 外部依赖
应用依赖数据库、Redis、第三方 API,这些慢了也会让服务变慢。看看请求日志里的上下游调用耗时,用 tracing 最直观。
3. 数据库连接池
服务连接池配置太小,在流量高峰时大量请求在等连接。看应用日志有没有 waiting for connection 或 timeout waiting for pool。
快速自查清单(可以贴在工位上)
|---------------|-------------------------------------------------------------|-----------------------|
| 排查维度 | 快速命令 | 看什么 |
| 节点/Pod 资源 | kubectl top node/pod | 超限?被限流? |
| Pod 详细信息 | kubectl describe pod | Events、Restarts、OOM |
| CoreDNS 健康 | kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50 | i/o timeout?SERVFAIL? |
| NetworkPolicy | kubectl get netpol -A | 策略冲突 |
| PVC 状态 | kubectl get pvc | Pending?I/O 超时? |
| 应用日志 | kubectl logs --tail=200 | 慢请求、连接池异常 |
| 链路追踪 | Jaeger UI(Ingress 地址) | 哪一跳最慢 |
常见陷阱(我帮你踩过了)
- "Pod 不是 Running 吗?" ------ 运行中不等于真正常。健康检查配置不对可能导致 Pod "假活"------应用没就绪但存活探针一直返回成功。建议用 startupProbe 替代存活探针的 initialDelaySeconds,给应用足够启动时间。
- "Log 怎么全在报错但排查没进展?" ------ 先看是不是日志级别太高。DEBUG 日志在大流量下本身就耗资源。改成 INFO/WARN,观察几分钟。
- "这个 Deployment 副本数我加到 100 了,怎么还是慢?" ------ 加节点解决不了延迟问题,尤其是 DNS 解析慢、网络策略冲突或服务网格配置不当。先把真正瓶颈定位再扩容。
- "为什么半夜 2-3 点才慢?" ------ 检查操作系统自动更新、定时任务、备份任务。WHOOP 团队的 Ubuntu 自动安全更新就是半夜触发的。
- "这 Pod 里连 strace 都装不上" ------ Distroless 镜像确实没这些工具。用
kubectl debug创建临时容器,或直接用 ephemeral containers 功能(K8s 1.16+ GA,1.25+ 稳定)挂调试工具进去。别想着往生产镜像里塞调试工具------那是反模式。
写在最后
排查服务变慢,忌讳"我知道一定是 XXX 的问题"这种先入为主的猜测。我见过太多人一上来就怀疑"节点太少"、"服务代码有 bug",结果折腾半天,最后发现是 conntrack 表满了或者 DNS 超时。
按这 4 层排查法走一遍:资源层→应用层→网络层→存储/依赖层,10 分钟内大概率能定位根因。如果还不行------
你还有什么更好的排查套路?评论区见 👇