Kubernetes故障排查手册
一、概述
1.1 背景介绍
K8s集群出故障是常态。Pod起不来、Service访问不通、节点NotReady、证书过期、etcd磁盘满——每一个问题都可能导致业务中断。和传统运维不同,K8s的故障链路更长:一个请求从Ingress进来,经过Service、Endpoint、kube-proxy的iptables/IPVS规则,到达Pod,中间任何一环出问题都会导致故障。
这篇手册整理了生产环境中最常见的K8s故障场景和排查方法,按照"现象→定位→解决"的思路组织。不讲理论,直接给排查命令和解决方案。
1.2 技术特点
分层排查思路:K8s故障排查遵循"节点层→控制平面层→工作负载层→网络层"的分层模型,从底层往上排查效率最高
声明式系统的排查特点:K8s是声明式系统,故障往往表现为"期望状态和实际状态不一致"。排查的核心是找到哪个控制器没有正常工作
事件驱动排查:kubectl describe 和 kubectl get events 是最有价值的排查入口,90%的问题能从Events中找到线索
1.3 适用场景
Pod状态异常:CrashLoopBackOff、ImagePullBackOff、Pending、OOMKilled、Evicted
网络故障:Service无法访问、Pod间通信不通、DNS解析失败、Ingress 502/504
节点故障:NotReady、磁盘压力、内存压力、PID压力
控制平面故障:API Server无响应、etcd故障、调度器异常、证书过期
存储故障:PVC Pending、挂载失败、存储性能问题
1.4 环境要求
| 组件 | 版本要求 | 说明 |
|---|---|---|
| kubectl | 与集群版本匹配 | 排查的核心工具,版本差异不超过1个小版本 |
| crictl | 与容器运行时匹配 | 节点级容器排查工具,替代docker命令 |
| nsenter | 系统自带 | 进入容器网络命名空间排查网络问题 |
| tcpdump | 需要安装 | 抓包分析网络故障 |
| etcdctl | 与etcd版本匹配 | etcd故障排查和数据恢复 |
二、详细步骤
2.1 准备工作
2.1.1 排查工具准备
# 确认kubectl版本和集群连接 kubectl version --short kubectl cluster-info # 安装kubectl插件(可选但推荐) # krew是kubectl插件管理器 kubectl krew install ctx # 快速切换context kubectl krew install ns # 快速切换namespace kubectl krew install neat # 清理kubectl输出中的冗余字段 kubectl krew install tree # 以树形展示资源关系 kubectl krew install node-shell # SSH到节点 # 准备debug镜像(用于网络排查) kubectl run debug-pod --image=nicolaka/netshoot --restart=Never -- sleep 3600
2.1.2 快速定位故障范围
# 第一步:集群整体健康检查 kubectl get nodes kubectl get cs # 控制平面组件状态(1.19+已废弃,用下面的命令) kubectl get --raw='/readyz?verbose' # 第二步:检查所有namespace中异常的Pod kubectl get pods -A --field-selector status.phase!=Running,status.phase!=Succeeded | head -30 # 第三步:检查最近的集群事件 kubectl get events -A --sort-by='.lastTimestamp' | tail -30 # 第四步:检查节点资源压力 kubectl describe nodes | grep -A 5 "Conditions:"
2.2 核心配置
2.2.1 Pod故障排查
场景一:Pod状态 CrashLoopBackOff
Pod启动后立即退出,kubelet不断重启,重启间隔指数增长(10s→20s→40s→...→5min)。
# 查看Pod状态和重启次数 kubectl get pod-o wide # 查看Pod事件 kubectl describe pod # 关注Events部分和容器的Last State # 查看容器退出日志(最关键的一步) kubectl logs --previous # --previous 查看上一次崩溃的日志,不加只能看到当前(可能还没输出就崩了) # 如果容器启动太快来不及看日志,用debug容器 kubectl debug -it --copy-to=debug-pod --container=app -- sh
常见原因和解决方案:
| 退出码 | 含义 | 常见原因 | 解决方案 |
|---|---|---|---|
| 0 | 正常退出 | 应用执行完就退出了,不是常驻进程 | 检查Dockerfile的CMD/ENTRYPOINT |
| 1 | 应用错误 | 配置文件错误、依赖服务不可用 | 查看 --previous 日志定位具体错误 |
| 137 | SIGKILL(OOMKilled) | 内存超过limits被杀 | 调大memory limits或优化应用内存使用 |
| 139 | SIGSEGV | 段错误,程序bug | 检查应用代码,特别是C/C++/CGO |
| 143 | SIGTERM | 被正常终止 | 检查是否被liveness probe杀掉 |
# 确认是否OOMKilled kubectl get pod-o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' # 如果输出OOMKilled,查看内存使用 kubectl top pod
场景二:Pod状态 ImagePullBackOff
# 查看具体拉取错误 kubectl describe pod| grep -A 10 "Events:" # 常见错误: # ErrImagePull: 镜像不存在或tag错误 # ImagePullBackOff: 拉取失败后的退避状态 # unauthorized: 需要认证但没配置imagePullSecrets # 检查镜像是否存在 crictl pull # 检查imagePullSecrets配置 kubectl get pod -o jsonpath='{.spec.imagePullSecrets}' kubectl get secret -o jsonpath='{.data..dockerconfigjson}' | base64 -d
解决方案:
镜像名或tag写错 → 修正镜像地址
私有仓库未配置认证 → 创建docker-registry类型的Secret并关联到Pod
网络不通无法拉取 → 检查节点到镜像仓库的网络连通性
镜像仓库限流(Docker Hub免费账户100次/6小时)→ 配置镜像代理或使用私有仓库
# 创建镜像拉取Secret kubectl create secret docker-registry regcred --docker-server=your-registry.com --docker-username=user --docker-password=pass -n
场景三:Pod状态 Pending
# Pending说明Pod还没被调度到节点上 kubectl describe pod# 关注Events中的调度失败原因 # 常见调度失败原因: # 0/3 nodes are available: 3 Insufficient cpu # → 所有节点CPU不够 # 0/3 nodes are available: 3 node(s) had taint {key: NoSchedule} # → 所有节点有污点,Pod没配置容忍 # 0/3 nodes are available: 1 node(s) didn't match Pod's node affinity # → 节点亲和性不匹配
# 检查集群可用资源 kubectl describe nodes | grep -A 8 "Allocated resources:" # 检查节点污点 kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints # 检查PVC是否绑定(如果Pod挂载了PVC) kubectl get pvc -n# Pending的PVC会导致Pod也Pending
2.2.2 网络故障排查
场景一:Service无法访问
# 第一步:确认Service和Endpoint存在 kubectl get svc-n kubectl get endpoints -n # 如果Endpoints为空( ),说明没有Pod匹配Service的selector # 第二步:确认Service selector和Pod label匹配 kubectl get svc -o jsonpath='{.spec.selector}' kubectl get pods -l = -n # 第三步:确认Pod端口和Service targetPort一致 kubectl get svc -o jsonpath='{.spec.ports}' kubectl get pod -o jsonpath='{.spec.containers[0].ports}' # 第四步:从集群内部测试连通性 kubectl run test-curl --rm -it --image=curlimages/curl -- curl -v http:// . .svc:port/path
场景二:DNS解析失败
# 测试DNS解析 kubectl run dns-test --rm -it --image=busybox:1.36 -- nslookup kubernetes.default kubectl run dns-test --rm -it --image=busybox:1.36 -- nslookup. .svc.cluster.local # 检查CoreDNS是否正常运行 kubectl get pods -n kube-system -l k8s-app=kube-dns kubectl logs -n kube-system -l k8s-app=kube-dns --tail=30 # 检查CoreDNS的ConfigMap kubectl get configmap coredns -n kube-system -o yaml # 检查Pod的DNS配置 kubectl exec -- cat /etc/resolv.conf # nameserver应该指向CoreDNS的ClusterIP(通常是10.96.0.10)
常见DNS问题:
CoreDNS Pod挂了 → 重启CoreDNS:kubectl rollout restart deployment coredns -n kube-system
节点上的resolv.conf有问题导致CoreDNS上游解析失败 → 检查CoreDNS的forward配置
ndots配置导致解析慢 → Pod的resolv.conf默认ndots:5,短域名会尝试5次搜索域拼接后才查外部DNS。可以在Pod spec中设置dnsConfig减小ndots
场景三:Pod间通信不通
# 从源Pod ping目标Pod IP kubectl exec-- ping -c 3 # 如果ping不通,检查CNI插件状态 kubectl get pods -n kube-system | grep -E "calico|flannel|cilium|weave" # 检查节点间网络 # 在源Pod所在节点上抓包 kubectl debug node/ -it --image=nicolaka/netshoot -- tcpdump -i any host -c 10 # 检查NetworkPolicy是否阻止了通信 kubectl get networkpolicy -n kubectl describe networkpolicy -n
场景四:Ingress返回502/504
# 502通常是后端Pod不健康或不存在 # 504通常是后端Pod响应超时 # 检查Ingress配置 kubectl describe ingress-n # 检查Ingress Controller日志 kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=50 # 检查后端Service的Endpoints kubectl get endpoints -n # 检查后端Pod是否Ready kubectl get pods -l -n # 如果Pod Running但不Ready,说明readinessProbe失败 kubectl describe pod | grep -A 5 "Readiness"
2.2.3 节点故障排查
场景一:节点状态NotReady
# 查看节点状态和条件 kubectl describe node| grep -A 20 "Conditions:" # 常见Condition: # Ready=False: kubelet停止上报心跳 # MemoryPressure=True: 内存不足 # DiskPressure=True: 磁盘不足 # PIDPressure=True: PID耗尽 # NetworkUnavailable=True: 网络插件未就绪 # SSH到节点检查kubelet状态 ssh systemctl status kubelet journalctl -u kubelet --since "10 minutes ago" | tail -50 # 检查节点资源 free -h df -h ps aux | wc -l
常见原因和解决方案:
| 原因 | 排查命令 | 解决方案 |
|---|---|---|
| kubelet进程挂了 | systemctl status kubelet | systemctl restart kubelet |
| 证书过期 | openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates | kubeadm certs renew all |
| 磁盘满 | df -h | 清理docker/containerd镜像缓存:crictl rmi --prune |
| 内存不足 | free -h | 驱逐低优先级Pod或扩容节点 |
| 容器运行时挂了 | systemctl status containerd | systemctl restart containerd |
场景二:节点磁盘压力(DiskPressure)
# 检查磁盘使用 df -h /var/lib/kubelet df -h /var/lib/containerd # 查看占用空间最大的目录 du -sh /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/* | sort -rh | head -10 # 清理未使用的容器镜像 crictl rmi --prune # 清理已退出的容器 crictl rm $(crictl ps -a --state exited -q) # 清理kubelet日志 journalctl --vacuum-size=500M
2.2.4 控制平面故障排查
场景一:API Server无响应
# 检查API Server Pod状态 kubectl get pods -n kube-system -l component=kube-apiserver # 如果kubectl完全无法连接,直接到Master节点操作 sshcrictl ps | grep kube-apiserver crictl logs --tail 50 # 检查API Server端口是否监听 ss -tlnp | grep 6443 # 检查etcd连接 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health
场景二:证书过期
# 检查所有证书过期时间 kubeadm certs check-expiration # 输出示例: # CERTIFICATE EXPIRES RESIDUAL TIME # admin.conf Feb 08, 2027 00:00 UTC 364d # apiserver Feb 08, 2027 00:00 UTC 364d # apiserver-etcd-client Feb 08, 2027 00:00 UTC 364d # ... # 续期所有证书 kubeadm certs renew all # 重启控制平面组件使新证书生效 # kubeadm部署的集群,静态Pod会自动重启 # 手动检查: crictl ps | grep -E "kube-apiserver|kube-controller|kube-scheduler"
注意:证书续期后需要更新kubeconfig文件。kubeadm certs renew all 会自动更新 /etc/kubernetes/*.conf,但如果你把kubeconfig复制到了其他地方(比如~/.kube/config),需要手动更新。
2.3 启动和验证
2.3.1 故障恢复后验证清单
# 1. 集群整体状态
kubectl get nodes
kubectl get pods -A --field-selector status.phase!=Running,status.phase!=Succeeded
# 2. 控制平面组件
kubectl get pods -n kube-system
# 3. 核心服务可用性
kubectl run verify --rm -it --image=busybox:1.36 -- wget -qO- http://kubernetes.default.svc/healthz
# 4. DNS解析
kubectl run verify --rm -it --image=busybox:1.36 -- nslookup kubernetes.default
# 5. 业务服务健康检查
kubectl get deployments -A | awk '$3 != $4 {print $0}'
# 输出READY数不等于期望数的Deployment
2.3.2 验证网络连通性
# 创建测试Pod验证跨节点通信
kubectl run net-test-1 --image=nicolaka/netshoot --restart=Never
--overrides='{"spec":{"nodeName":"node-1"}}' -- sleep 3600
kubectl run net-test-2 --image=nicolaka/netshoot --restart=Never
--overrides='{"spec":{"nodeName":"node-2"}}' -- sleep 3600
# 获取Pod IP
POD1_IP=$(kubectl get pod net-test-1 -o jsonpath='{.status.podIP}')
POD2_IP=$(kubectl get pod net-test-2 -o jsonpath='{.status.podIP}')
# 跨节点ping测试
kubectl exec net-test-1 -- ping -c 3 $POD2_IP
kubectl exec net-test-2 -- ping -c 3 $POD1_IP
# 清理
kubectl delete pod net-test-1 net-test-2
三、示例代码和配置
3.1 完整配置示例
3.1.1 一键集群健康检查脚本
#!/bin/bash
# 文件名:k8s-health-check.sh
# 功能:全面检查K8s集群健康状态,输出诊断报告
# 用法:./k8s-health-check.sh
RED='�33[0;31m'
YELLOW='�33[1;33m'
GREEN='�33[0;32m'
NC='�33[0m'
echo"============================================"
echo" Kubernetes集群健康检查报告"
echo" 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo"============================================"
echo""
# 1. 节点状态
echo"=== 1. 节点状态 ==="
NOT_READY=$(kubectl get nodes --no-headers | grep -v " Ready " | wc -l)
TOTAL_NODES=$(kubectl get nodes --no-headers | wc -l)
if [ "$NOT_READY" -gt 0 ]; then
echo -e "${RED}[异常] $NOT_READY/$TOTAL_NODES 个节点不健康${NC}"
kubectl get nodes | grep -v " Ready "
else
echo -e "${GREEN}[正常] $TOTAL_NODES/$TOTAL_NODES 个节点健康${NC}"
fi
echo""
# 2. 节点资源压力
echo"=== 2. 节点资源压力 ==="
kubectl get nodes -o json | jq -r '
.items[] |
.metadata.name as $name |
.status.conditions[] |
select(.type != "Ready" and .status == "True") |
"($name): (.type) = (.status) - (.message)"
' | whileread -r line; do
echo -e "${RED}[压力] $line${NC}"
done
echo""
# 3. 异常Pod
echo"=== 3. 异常Pod ==="
ABNORMAL=$(kubectl get pods -A --no-headers --field-selector status.phase!=Running,status.phase!=Succeeded 2>/dev/null | wc -l)
if [ "$ABNORMAL" -gt 0 ]; then
echo -e "${YELLOW}[警告] $ABNORMAL 个异常Pod${NC}"
kubectl get pods -A --field-selector status.phase!=Running,status.phase!=Succeeded 2>/dev/null | head -20
else
echo -e "${GREEN}[正常] 无异常Pod${NC}"
fi
echo""
# 4. CrashLoopBackOff的Pod
echo"=== 4. CrashLoopBackOff Pod ==="
kubectl get pods -A -o json | jq -r '
.items[] |
select(.status.containerStatuses[]? | select(.state.waiting.reason == "CrashLoopBackOff")) |
[.metadata.namespace, .metadata.name,
(.status.containerStatuses[0].restartCount | tostring)] | @tsv
' | while IFS=$' 'read -r ns name restarts; do
echo -e "${RED}[崩溃] $ns/$name (重启${restarts}次)${NC}"
done
echo""
# 5. 高重启次数Pod(>10次)
echo"=== 5. 高重启次数Pod(>10次)==="
kubectl get pods -A -o json | jq -r '
.items[] |
select(.status.containerStatuses[]? | select(.restartCount > 10)) |
[.metadata.namespace, .metadata.name,
(.status.containerStatuses[0].restartCount | tostring)] | @tsv
' | sort -t$' ' -k3 -rn | head -10 | while IFS=$' 'read -r ns name restarts; do
echo -e "${YELLOW}[警告] $ns/$name: ${restarts}次重启${NC}"
done
echo""
# 6. Deployment副本数不匹配
echo"=== 6. Deployment副本数异常 ==="
kubectl get deployments -A -o json | jq -r '
.items[] |
select(.status.readyReplicas != .spec.replicas) |
[.metadata.namespace, .metadata.name,
(.status.readyReplicas // 0 | tostring),
(.spec.replicas | tostring)] | @tsv
' | while IFS=$' 'read -r ns name ready desired; do
echo -e "${YELLOW}[异常] $ns/$name: Ready $ready/$desired${NC}"
done
echo""
# 7. PVC状态
echo"=== 7. 未绑定的PVC ==="
kubectl get pvc -A --no-headers | grep -v "Bound" | whileread -r line; do
echo -e "${YELLOW}[未绑定] $line${NC}"
done
echo""
# 8. 最近的Warning事件
echo"=== 8. 最近Warning事件(最近30分钟)==="
kubectl get events -A --field-selector type=Warning
--sort-by='.lastTimestamp' 2>/dev/null | tail -10
echo""
echo"============================================"
echo" 检查完成"
echo"============================================"
3.1.2 etcd健康检查和备份脚本
#!/bin/bash
# 文件名:etcd-check-backup.sh
# 功能:检查etcd健康状态并执行快照备份
ETCD_ENDPOINTS="https://127.0.0.1:2379"
ETCD_CACERT="/etc/kubernetes/pki/etcd/ca.crt"
ETCD_CERT="/etc/kubernetes/pki/etcd/server.crt"
ETCD_KEY="/etc/kubernetes/pki/etcd/server.key"
BACKUP_DIR="/opt/etcd-backup"
ETCDCTL="etcdctl --endpoints=$ETCD_ENDPOINTS
--cacert=$ETCD_CACERT --cert=$ETCD_CERT --key=$ETCD_KEY"
echo"=== etcd健康检查 ==="
# 端点健康状态
$ETCDCTL endpoint health
echo""
# 端点状态(包含数据库大小)
$ETCDCTL endpoint status --write-out=table
echo""
# 成员列表
$ETCDCTL member list --write-out=table
echo""
# 检查数据库大小(超过4GB需要告警)
DB_SIZE=$($ETCDCTL endpoint status --write-out=json | jq '.[0].Status.dbSize')
DB_SIZE_MB=$((DB_SIZE / 1024 / 1024))
echo"数据库大小: ${DB_SIZE_MB}MB"
if [ "$DB_SIZE_MB" -gt 4000 ]; then
echo"[警告] etcd数据库超过4GB,需要压缩和碎片整理"
# 压缩历史版本
LATEST_REV=$($ETCDCTL endpoint status --write-out=json | jq '.[0].Status.header.revision')
$ETCDCTL compact "$LATEST_REV"
# 碎片整理
$ETCDCTL defrag
fi
# 执行快照备份
mkdir -p "$BACKUP_DIR"
SNAPSHOT_FILE="$BACKUP_DIR/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db"
$ETCDCTL snapshot save "$SNAPSHOT_FILE"
$ETCDCTL snapshot status "$SNAPSHOT_FILE" --write-out=table
echo"备份完成: $SNAPSHOT_FILE"
3.2 辅助脚本
3.2.1 Pod故障快速诊断脚本
#!/bin/bash # 文件名:pod-diagnose.sh # 功能:快速诊断指定Pod的问题 # 用法:./pod-diagnose.shNS=${1:?"用法: $0 "} POD=${2:?"请指定Pod名称"} echo"========== Pod诊断: $NS/$POD ==========" # 基本信息 echo"--- 基本状态 ---" kubectl get pod "$POD" -n "$NS" -o wide echo"" # 容器状态 echo"--- 容器状态 ---" kubectl get pod "$POD" -n "$NS" -o json | jq -r ' .status.containerStatuses[]? | "容器: (.name) | Ready: (.ready) | 重启: (.restartCount) | 状态: (.state | keys[0])" ' echo"" # 事件 echo"--- 相关事件 ---" kubectl get events -n "$NS" --field-selector "involvedObject.name=$POD" --sort-by='.lastTimestamp' | tail -10 echo"" # 如果有上次崩溃日志 RESTART_COUNT=$(kubectl get pod "$POD" -n "$NS" -o jsonpath='{.status.containerStatuses[0].restartCount}') if [ "$RESTART_COUNT" -gt 0 ]; then echo"--- 上次崩溃日志(最后30行)---" kubectl logs "$POD" -n "$NS" --previous --tail=30 2>/dev/null || echo"无法获取上次日志" echo"" fi # 资源使用 echo"--- 资源使用 ---" kubectl top pod "$POD" -n "$NS" 2>/dev/null || echo"Metrics不可用" echo"" # 资源配置 echo"--- 资源配置 ---" kubectl get pod "$POD" -n "$NS" -o json | jq '.spec.containers[] | {name, resources}' echo"" echo"========== 诊断完成 =========="
3.2.2 节点资源排查脚本
#!/bin/bash
# 文件名:node-resource-check.sh
# 功能:检查节点资源分配和使用情况
# 用法:./node-resource-check.sh [node-name]
NODE=${1:-""}
if [ -z "$NODE" ]; then
NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
else
NODES="$NODE"
fi
for n in$NODES; do
echo"========== 节点: $n =========="
# 节点状态
STATUS=$(kubectl get node "$n" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
echo"状态: Ready=$STATUS"
# 资源容量和已分配
echo""
echo"--- 资源分配 ---"
kubectl describe node "$n" | sed -n '/Allocated resources/,/Events/p' | head -15
# 该节点上的Pod数量
POD_COUNT=$(kubectl get pods -A --field-selector "spec.nodeName=$n" --no-headers | wc -l)
MAX_PODS=$(kubectl get node "$n" -o jsonpath='{.status.capacity.pods}')
echo""
echo"Pod数量: $POD_COUNT / $MAX_PODS"
# 该节点上资源使用Top 5的Pod
echo""
echo"--- CPU使用Top 5 ---"
kubectl top pods -A --sort-by=cpu --no-headers |
whileread -r ns name cpu mem; do
pod_node=$(kubectl get pod "$name" -n "$ns" -o jsonpath='{.spec.nodeName}' 2>/dev/null)
if [ "$pod_node" = "$n" ]; then
echo" $ns/$name: CPU=$cpu MEM=$mem"
fi
done | head -5
echo""
done
3.3 实际应用案例
案例一:生产环境大规模Pod驱逐事件排查
场景描述:周一早上收到告警,production namespace中30%的Pod被驱逐(Evicted),业务出现大面积503。
排查过程:
# 1. 确认驱逐规模 kubectl get pods -n production --field-selector status.phase=Failed | grep Evicted | wc -l # 输出:47 # 2. 查看驱逐原因 kubectl get pods -n production --field-selector status.phase=Failed -o json | jq -r '.items[] | select(.status.reason=="Evicted") | .status.message' | sort | uniq -c # 输出:47 The node was low on resource: ephemeral-storage. # 3. 定位问题节点 kubectl get pods -n production --field-selector status.phase=Failed -o json | jq -r '.items[] | select(.status.reason=="Evicted") | .spec.nodeName' | sort | uniq -c # 输出:47 node-3 # 4. 检查node-3的磁盘 ssh node-3 df -h # /dev/sda1 100G 97G 3G 97% / # 5. 找到磁盘占用元凶 ssh node-3 du -sh /var/log/* | sort -rh | head -5 # 78G /var/log/pods/production_data-processor-xxx/app/0.log
根因:data-processor应用没有配置日志轮转,单个容器日志文件涨到78G,撑满了节点磁盘。kubelet检测到ephemeral-storage压力,开始驱逐该节点上的Pod。
解决方案:
# 1. 清理Evicted Pod kubectl delete pods -n production --field-selector status.phase=Failed # 2. 配置kubelet日志轮转(在每个节点上) # /var/lib/kubelet/config.yaml 中添加: # containerLogMaxSize: "100Mi" # containerLogMaxFiles: 5 # 3. 给应用Pod配置ephemeral-storage限制 # 防止单个Pod占满节点磁盘
resources: requests: ephemeral-storage: 1Gi limits: ephemeral-storage: 5Gi
案例二:间歇性DNS解析失败排查
场景描述:业务反馈部分请求报"Name or service not known"错误,但不是每次都失败,大约5%的请求会DNS解析失败。
排查过程:
# 1. 确认CoreDNS运行状态 kubectl get pods -n kube-system -l k8s-app=kube-dns # 2个Pod都Running # 2. 检查CoreDNS日志 kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100 | grep -i "error|fail" # 没有明显错误 # 3. 检查CoreDNS的资源使用 kubectl top pods -n kube-system -l k8s-app=kube-dns # NAME CPU MEMORY # coredns-xxx-aaa 980m 178Mi # coredns-xxx-bbb 950m 175Mi # CPU接近limits(1核),说明CoreDNS过载 # 4. 检查DNS查询量 kubectl exec -n kube-system coredns-xxx-aaa -- wget -qO- http://localhost:9153/metrics | grep coredns_dns_requests_total # 每秒超过5000次查询
根因:集群规模扩大后DNS查询量超过CoreDNS的处理能力,2个Pod各1核CPU已经跑满。
解决方案:
# 1. 扩容CoreDNS kubectl scale deployment coredns -n kube-system --replicas=5 # 2. 调大CoreDNS资源 kubectl edit deployment coredns -n kube-system # 将CPU limits从1调到2 # 3. 启用NodeLocal DNSCache减少CoreDNS压力 kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml # NodeLocal DNSCache在每个节点运行DNS缓存,大部分查询在本地命中,不需要走CoreDNS
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 性能优化
配置合理的资源requests和limits:requests设太低会导致Pod被调度到资源不足的节点,运行时性能差;limits设太低会导致CPU被throttle或内存OOMKill。生产环境建议requests设为实际使用量的80%,limits设为requests的1.5-2倍。
# 查看Pod实际资源使用,作为设置requests的参考 kubectl top pods -n production --sort-by=cpu kubectl top pods -n production --sort-by=memory
配置PodDisruptionBudget:防止节点维护或集群升级时同时驱逐太多Pod导致服务中断。
apiVersion: policy/v1 kind:PodDisruptionBudget metadata: name:web-app-pdb namespace:production spec: minAvailable:"60%" selector: matchLabels: app:web-app
配置优雅终止时间:默认terminationGracePeriodSeconds是30秒。Java应用可能需要更长时间完成请求处理和资源释放,建议设为60-120秒。应用代码中要正确处理SIGTERM信号。
4.1.2 安全加固
定期检查证书过期时间:kubeadm签发的证书默认1年有效期,过期后API Server直接不可用。配置定时任务每周检查一次。
# 加入crontab,每周一检查证书 0 9 * * 1 kubeadm certs check-expiration | mail -s "K8s证书检查" sre@company.com
etcd定期备份:etcd存储了集群所有状态数据,丢了就全完了。每天至少备份一次,保留最近7天的快照。
# crontab每天凌晨2点备份 0 2 * * * /opt/scripts/etcd-check-backup.sh >> /var/log/etcd-backup.log 2>&1 # 清理7天前的备份 0 3 * * * find /opt/etcd-backup -name "*.db" -mtime +7 -delete
限制kubectl权限:不要给所有人cluster-admin权限。按团队和职责分配RBAC角色,开发只能查看自己namespace的资源,SRE有更高权限但也要审计。
4.1.3 高可用配置
控制平面多节点:生产环境至少3个Master节点,API Server、etcd、Controller Manager、Scheduler都是多副本。单Master挂了整个集群不可管理。
跨可用区部署:Worker节点分布在至少2个可用区,配合Pod Topology Spread Constraints确保Pod分散在不同AZ。
关键组件监控:etcd磁盘延迟、API Server请求延迟、Controller Manager队列深度——这三个指标异常通常是集群故障的前兆。
4.2 注意事项
4.2.1 配置注意事项
警告:以下操作在生产环境中执行前务必确认影响范围。
不要随意删除kube-system中的Pod:kube-proxy、CoreDNS、CNI插件等核心组件在kube-system中,误删会导致集群网络中断。
不要在高峰期做节点维护:kubectl drain 会驱逐节点上所有Pod,高峰期驱逐可能导致其他节点资源不足,引发连锁故障。
kubectl delete pod和kubectl delete deployment的区别:delete pod只删一个Pod(Deployment会自动重建),delete deployment会删除所有Pod且不会重建。紧急情况下重启服务用 kubectl rollout restart deployment,不要delete。
4.2.2 常见错误
| 错误现象 | 原因分析 | 解决方案 |
|---|---|---|
| kubectl超时无响应 | API Server过载或网络不通 | 检查API Server Pod状态和节点网络 |
| Pod一直Terminating | finalizer阻塞或容器进程不响应SIGTERM | kubectl delete pod --force --grace-period=0 强制删除 |
| 节点NotReady后Pod不迁移 | 默认等待5分钟(pod-eviction-timeout)才开始迁移 | 检查kube-controller-manager的配置 |
| HPA不工作 | Metrics Server未安装或Pod没设requests | 安装Metrics Server,配置resources.requests |
| PVC一直Pending | StorageClass不存在或存储后端故障 | kubectl describe pvc 查看事件 |
| Service ClusterIP不通 | kube-proxy未运行或iptables规则异常 | 检查kube-proxy Pod和iptables规则 |
4.2.3 兼容性问题
kubectl版本:kubectl和集群版本差异不能超过1个小版本。用1.25的kubectl管理1.28的集群可能出现API不兼容。
容器运行时:K8s 1.24+移除了dockershim,必须用containerd或CRI-O。升级到1.24前确认运行时已切换。
API废弃:每个K8s版本都会废弃一些API(如extensions/v1beta1 Ingress)。升级前用 kubectl convert 或 pluto 工具检查废弃API。
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
# kubelet日志(节点级别) journalctl -u kubelet --since "30 minutes ago" | tail -100 # API Server日志 kubectl logs -n kube-system -l component=kube-apiserver --tail=50 # Controller Manager日志 kubectl logs -n kube-system -l component=kube-controller-manager --tail=50 # Scheduler日志 kubectl logs -n kube-system -l component=kube-scheduler --tail=50 # etcd日志 kubectl logs -n kube-system -l component=etcd --tail=50 # CoreDNS日志 kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50 # kube-proxy日志 kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=50
5.1.2 常见问题排查
问题一:API Server响应慢,kubectl操作延迟高
# 检查API Server请求延迟 kubectl get --raw /metrics | grep apiserver_request_duration_seconds # 检查etcd延迟(API Server依赖etcd) kubectl exec -n kube-system etcd-master01 -- etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint status --write-out=table # 检查API Server审计日志中的慢请求 grep "AUDIT" /var/log/kubernetes/audit.log | jq 'select(.stageTimestamp > .requestReceivedTimestamp + "5s")'
解决方案:
etcd磁盘IO慢 → 将etcd数据目录迁移到SSD
API Server过载 → 检查是否有大量LIST请求(通常是监控组件配置不当)
大量Webhook调用 → 检查MutatingWebhookConfiguration和ValidatingWebhookConfiguration
问题二:Pod被OOMKilled反复重启
# 确认OOMKill kubectl get pod-o jsonpath='{.status.containerStatuses[0].lastState.terminated}' # 查看Pod内存使用趋势 kubectl top pod --containers # 查看节点上的OOM事件 ssh dmesg | grep -i "oom|killed" | tail -10
解决方案:
调大memory limits(临时方案)
排查内存泄漏(根本方案):Java应用用jmap dump堆内存分析,Go应用用pprof
如果是JVM应用,确认 -Xmx 设置小于容器memory limits的80%,留20%给非堆内存
问题三:Service的Endpoints频繁变化导致流量抖动
症状:服务间调用间歇性失败,Endpoints列表频繁增减
排查:
# 监控Endpoints变化 kubectl get endpoints-w # 检查Pod的readinessProbe是否频繁失败 kubectl describe pod | grep -A 10 "Readiness" kubectl get events --field-selector reason=Unhealthy -n
解决:调整readinessProbe的参数,增大failureThreshold和periodSeconds,避免短暂的响应慢就被摘除
5.1.3 调试模式
# 使用ephemeral container调试运行中的Pod(不重启Pod) kubectl debug-it --image=nicolaka/netshoot --target= # 复制Pod进行调试(不影响原Pod) kubectl debug -it --copy-to=debug-copy --container=app -- sh # 调试节点(在节点上启动特权Pod) kubectl debug node/ -it --image=ubuntu # 查看Pod的完整YAML(包含status) kubectl get pod -o yaml # 查看资源的所有事件 kubectl get events --field-selector involvedObject.name= --sort-by='.lastTimestamp'
5.2 性能监控
5.2.1 关键指标监控
# 集群整体资源使用率 kubectl top nodes # 各namespace资源使用汇总 kubectl top pods -A --sort-by=cpu | head -20 kubectl top pods -A --sort-by=memory | head -20 # API Server请求延迟 kubectl get --raw /metrics | grep apiserver_request_duration_seconds_bucket # etcd数据库大小 kubectl exec -n kube-system etcd-master01 -- etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint status --write-out=json | jq '.[0].Status.dbSize / 1024 / 1024'
5.2.2 监控指标说明
| 指标名称 | 正常范围 | 告警阈值 | 说明 |
|---|---|---|---|
| 节点CPU使用率 | < 70% | > 85% 持续5分钟 | 过高会导致Pod被throttle |
| 节点内存使用率 | < 80% | > 90% | 接近100%会触发OOM Killer |
| etcd数据库大小 | < 2GB | > 4GB | 超过8GB etcd会拒绝写入 |
| etcd磁盘fsync延迟 | < 10ms | > 25ms | 延迟过高影响集群所有写操作 |
| API Server请求延迟P99 | < 1s | > 5s | 延迟过高kubectl操作会超时 |
| Pod重启次数 | 0 | > 5次/小时 | 频繁重启说明应用有问题 |
| CoreDNS请求延迟 | < 5ms | > 50ms | DNS慢会影响所有服务间调用 |
5.2.3 监控告警配置
# Prometheus告警规则:k8s-cluster-alerts.yaml
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:k8s-cluster-alerts
namespace:monitoring
spec:
groups:
-name:k8s-node.rules
rules:
-alert:NodeNotReady
expr:kube_node_status_condition{condition="Ready",status="true"}==0
for:5m
labels:
severity:critical
annotations:
summary:"节点 {{ $labels.node }} NotReady超过5分钟"
-alert:NodeHighCPU
expr:|
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for:5m
labels:
severity:warning
annotations:
summary:"节点 {{ $labels.instance }} CPU使用率超过85%"
-alert:NodeHighMemory
expr:|
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
for:5m
labels:
severity:critical
annotations:
summary:"节点 {{ $labels.instance }} 内存使用率超过90%"
-alert:NodeDiskPressure
expr:|
(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 > 85
for:5m
labels:
severity:warning
annotations:
summary:"节点 {{ $labels.instance }} 磁盘使用率超过85%"
-name:k8s-pod.rules
rules:
-alert:PodCrashLooping
expr:|
rate(kube_pod_container_status_restarts_total[15m]) * 60 * 15 > 0
for:5m
labels:
severity:critical
annotations:
summary:"Pod {{ $labels.namespace }}/{{ $labels.pod }} 频繁重启"
-alert:PodOOMKilled
expr:|
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
for:0m
labels:
severity:warning
annotations:
summary:"Pod {{ $labels.namespace }}/{{ $labels.pod }} 被OOMKill"
-name:k8s-etcd.rules
rules:
-alert:EtcdHighDiskLatency
expr:|
histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) > 0.025
for:5m
labels:
severity:critical
annotations:
summary:"etcd磁盘fsync P99延迟超过25ms,集群写入性能受影响"
-alert:EtcdDatabaseSizeLarge
expr:etcd_mvcc_db_total_size_in_bytes>4294967296
for:5m
labels:
severity:warning
annotations:
summary:"etcd数据库大小超过4GB,需要压缩和碎片整理"
5.3 备份与恢复
5.3.1 备份策略
#!/bin/bash
# 文件名:etcd-daily-backup.sh
# 功能:etcd每日快照备份,保留7天
BACKUP_DIR="/opt/etcd-backup"
ETCD_ENDPOINTS="https://127.0.0.1:2379"
ETCD_CACERT="/etc/kubernetes/pki/etcd/ca.crt"
ETCD_CERT="/etc/kubernetes/pki/etcd/server.crt"
ETCD_KEY="/etc/kubernetes/pki/etcd/server.key"
mkdir -p "$BACKUP_DIR"
SNAPSHOT="$BACKUP_DIR/etcd-$(date +%Y%m%d-%H%M%S).db"
etcdctl snapshot save "$SNAPSHOT"
--endpoints="$ETCD_ENDPOINTS"
--cacert="$ETCD_CACERT"
--cert="$ETCD_CERT"
--key="$ETCD_KEY"
if [ $? -eq 0 ]; then
echo"备份成功: $SNAPSHOT ($(du -sh "$SNAPSHOT" | awk '{print $1}'))"
# 清理7天前的备份
find "$BACKUP_DIR" -name "etcd-*.db" -mtime +7 -delete
else
echo"备份失败!" >&2
exit 1
fi
5.3.2 恢复流程
# etcd从快照恢复(集群完全不可用时的最后手段) # 警告:恢复会丢失快照之后的所有变更 # 1. 停止所有Master节点的API Server # 移走静态Pod manifest mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/ # 2. 停止etcd mv /etc/kubernetes/manifests/etcd.yaml /tmp/ # 3. 备份当前etcd数据目录 mv /var/lib/etcd /var/lib/etcd.bak # 4. 从快照恢复 etcdctl snapshot restore /opt/etcd-backup/etcd-20260208.db --data-dir=/var/lib/etcd --name=master01 --initial-cluster=master01=https://10.0.0.1:2380 --initial-advertise-peer-urls=https://10.0.0.1:2380 # 5. 恢复etcd和API Server mv /tmp/etcd.yaml /etc/kubernetes/manifests/ mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/ # 6. 等待集群恢复 sleep 30 kubectl get nodes kubectl get pods -A
六、总结
6.1 技术要点回顾
排查思路分层:节点层(kubelet、磁盘、内存)→ 控制平面层(API Server、etcd、Scheduler)→ 工作负载层(Pod状态、容器日志)→ 网络层(Service、DNS、CNI),从底层往上排查效率最高
kubectl describe是第一步:90%的问题能从Events中找到线索。kubectl describe pod/node/svc 是排查的起点
--previous查看崩溃日志:CrashLoopBackOff的Pod用 kubectl logs --previous 查看上一次崩溃的日志,不加这个参数看到的可能是空的
etcd是集群的命脉:etcd数据丢失等于集群报废。每天备份,定期验证恢复流程
6.2 进阶学习方向
kubectl debug深入使用:Ephemeral Container可以在不重启Pod的情况下注入调试工具,K8s 1.25+ GA
文档:https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/
实践建议:准备一个包含常用排查工具的debug镜像(netshoot、busybox等)
混沌工程:用Chaos Mesh或Litmus在测试环境主动注入故障,提前发现系统弱点
项目地址:https://chaos-mesh.org/
实践建议:从简单的Pod Kill开始,逐步增加网络延迟、磁盘故障等场景
可观测性体系建设:Prometheus + Grafana + Loki + Tempo,构建指标、日志、链路追踪三位一体的可观测性平台
实践建议:先搞定指标监控和告警,再补充日志和链路追踪
6.3 参考资料
Kubernetes官方排查文档 - 官方故障排查指南
kubectl Cheat Sheet - kubectl命令速查
learnk8s故障排查流程图 - 可视化排查决策树
etcd运维文档 - etcd备份恢复和调优
附录
A. 命令速查表
# Pod排查 kubectl get pods -A --field-selector status.phase!=Running,status.phase!=Succeeded # 异常Pod kubectl describe pod# Pod详情和事件 kubectl logs --previous --tail=50 # 上次崩溃日志 kubectl logs -c # 指定容器日志 kubectl top pod --containers # Pod资源使用 kubectl debug -it --image=netshoot # 调试Pod kubectl delete pod --force --grace-period=0 # 强制删除卡住的Pod # 节点排查 kubectl describe node # 节点详情 kubectl top nodes # 节点资源使用 kubectl get nodes -o wide # 节点IP和版本 kubectl cordon # 标记节点不可调度 kubectl drain --ignore-daemonsets # 驱逐节点上的Pod kubectl uncordon # 恢复节点调度 # 网络排查 kubectl get svc,endpoints -n # Service和Endpoint kubectl run test --rm -it --image=busybox -- nslookup # DNS测试 kubectl run test --rm -it --image=curlimages/curl -- curl # HTTP测试 # 事件和日志 kubectl get events -A --sort-by='.lastTimestamp' | tail -20 # 最近事件 kubectl get events --field-selector type=Warning -A # Warning事件 journalctl -u kubelet --since "30m ago" # kubelet日志 # etcd etcdctl endpoint health # 健康检查 etcdctl endpoint status --write-out=table # 状态详情 etcdctl snapshot save # 快照备份 etcdctl snapshot restore # 快照恢复
B. 配置参数详解
Pod状态含义:
| 状态 | 含义 | 排查方向 |
|---|---|---|
| Pending | 等待调度 | 资源不足、污点、亲和性、PVC未绑定 |
| ContainerCreating | 容器创建中 | 镜像拉取、Volume挂载、Init容器 |
| Running | 运行中 | 正常状态,但可能不Ready |
| CrashLoopBackOff | 反复崩溃 | 查看 --previous 日志,检查退出码 |
| ImagePullBackOff | 镜像拉取失败 | 镜像地址、认证、网络 |
| OOMKilled | 内存超限被杀 | 调大limits或排查内存泄漏 |
| Evicted | 被驱逐 | 节点资源压力(磁盘、内存) |
| Terminating | 终止中 | finalizer阻塞或进程不响应SIGTERM |
| Unknown | 状态未知 | 节点失联,kubelet无法上报 |
容器退出码含义:
| 退出码 | 信号 | 含义 |
|---|---|---|
| 0 | - | 正常退出 |
| 1 | - | 应用错误 |
| 126 | - | 命令无法执行(权限问题) |
| 127 | - | 命令未找到 |
| 128+N | Signal N | 被信号N终止 |
| 137 | SIGKILL(9) | 被强制杀死(OOMKill或kill -9) |
| 139 | SIGSEGV(11) | 段错误 |
| 143 | SIGTERM(15) | 被正常终止 |
C. 术语表
| 术语 | 英文 | 解释 |
|---|---|---|
| 控制平面 | Control Plane | K8s集群的管理层,包含API Server、etcd、Scheduler、Controller Manager |
| 数据平面 | Data Plane | K8s集群的工作层,即Worker节点上运行的kubelet、kube-proxy和业务Pod |
| 驱逐 | Eviction | kubelet在节点资源压力下主动终止Pod的行为 |
| 污点 | Taint | 节点上的标记,阻止不容忍该污点的Pod被调度到该节点 |
| 容忍 | Toleration | Pod上的配置,允许Pod被调度到有特定污点的节点 |
| 亲和性 | Affinity | Pod调度时对节点或其他Pod的偏好或要求 |
| 探针 | Probe | kubelet用于检测容器健康状态的机制:liveness、readiness、startup |
| 临时容器 | Ephemeral Container | 用于调试的临时容器,可以注入到运行中的Pod而不重启 |
全部0条评论
快来发表一下你的评论吧 !