Kubernetes 生产环境运维与排障实战那些年踩过的坑与填平的路一、集群又挂了K8s 运维的真实战场Kubernetes 在生产环境中跑起来不难难的是稳定运行。Pod 无限重启、节点 NotReady、Service 端点丢失、PVC 无法挂载……这些问题在测试环境可能永远不会出现一到生产环境就频繁上演。原因很简单生产环境的规模、负载和复杂度是测试环境无法模拟的。K8s 运维的难点在于故障表象和根因往往不在同一层。Pod 启动失败可能是镜像拉不下来也可能是节点资源不足还可能是网络策略拦截。排查时需要逐层剥开从应用层到容器运行时再到内核和网络每一层都可能是故障点。本文不谈概念只谈实战。从最常见的故障场景出发给出系统化的排查思路和可复用的诊断脚本。二、故障传播链K8s 问题的分层定位模型K8s 的故障排查需要建立分层思维。不同层的问题有不同的排查工具和方法论。graph TD subgraph 应用层 A1[Pod CrashLoopBackOff] A2[应用 OOMKilled] A3[健康检查失败] end subgraph 调度层 B1[Pod Pending] B2[节点资源不足] B3[亲和性/污点约束] end subgraph 网络层 C1[Service 无端点] C2[DNS 解析失败] C3[网络策略拦截] end subgraph 存储层 D1[PVC Pending] D2[PV 绑定失败] D3[IO 性能劣化] end subgraph 节点层 E1[节点 NotReady] E2[Kubelet 异常] E3[容器运行时故障] end A1 -- B2 A3 -- C1 B1 -- B2 B1 -- B3 C1 -- C2 C1 -- C3 D1 -- D2 E1 -- E2 E2 -- E3 style A1 fill:#fce4ec style E1 fill:#fce4ec style C2 fill:#fff3e0自上而下排查法是最高效的策略。先确认 Pod 状态再检查事件和日志最后深入节点和网络。大多数问题在前两层就能定位不需要 SSH 到节点上排查。事件Events是排查的金钥匙。K8s 的 Event 对象记录了集群中所有状态变更的详细信息包括调度决策、镜像拉取、健康检查等。kubectl describe输出末尾的 Events 部分往往直接告诉你问题出在哪里。三、排障工具箱K8s 常见故障的诊断脚本以下脚本覆盖了 K8s 运维中最常见的五类故障场景每个脚本都包含详细的排查逻辑和输出解读。#!/bin/bash # k8s-troubleshoot.sh — K8s 生产环境一键排障工具箱 # 使用方式: ./k8s-troubleshoot.sh namespace pod-name set -euo pipefail NAMESPACE${1:-default} POD_NAME${2:-} # 颜色定义便于区分输出级别 RED\033[0;31m YELLOW\033[1;33m GREEN\033[0;32m NC\033[0m # No Color log_info() { echo -e ${GREEN}[INFO]${NC} $1; } log_warn() { echo -e ${YELLOW}[WARN]${NC} $1; } log_error() { echo -e ${RED}[ERROR]${NC} $1; } # # 模块一集群健康总览 # 快速判断问题范围是单个 Pod 还是整个集群 # check_cluster_health() { log_info 集群健康总览 # 检查节点状态NotReady 的节点是很多问题的根源 local not_ready_nodes not_ready_nodes$(kubectl get nodes --no-headers | grep -v Ready || true) if [[ -n $not_ready_nodes ]]; then log_error 发现 NotReady 节点: echo $not_ready_nodes # 进一步检查 NotReady 节点的原因 for node in $(echo $not_ready_nodes | awk {print $1}); do log_warn 节点 $node 的 Conditions: kubectl get node $node -o jsonpath{range .status.conditions[*]}{.type}{.status} ({.reason}: {.message}){\n}{end} done else log_info 所有节点状态正常 fi # 检查关键组件的 Pod 状态 log_info 核心组件状态: kubectl get pods -n kube-system -o wide --no-headers | \ awk {if ($3 ! Running $3 ! Completed) print} | \ while read -r line; do log_error $line done # 统计各命名空间的异常 Pod 数量 log_info 各命名空间异常 Pod 统计: kubectl get pods -A --no-headers | \ awk {if ($4 ! Running $4 ! Completed $4 ! Succeeded) print $1} | \ sort | uniq -c | sort -rn | \ while read -r count ns; do log_warn 命名空间 $ns: $count 个异常 Pod done } # # 模块二Pod 故障诊断 # 针对 CrashLoopBackOff、ImagePullBackOff 等常见状态 # diagnose_pod() { local ns$1 local pod$2 log_info Pod 诊断: $ns/$pod # 获取 Pod 状态和容器状态 local pod_status pod_status$(kubectl get pod $pod -n $ns -o jsonpath{.status.phase}) log_info Pod 状态: $pod_status # 检查容器状态区分 Waiting/Running/Terminated local container_statuses container_statuses$(kubectl get pod $pod -n $ns -o jsonpath{.status.containerStatuses[*]} 2/dev/null || true) if [[ -z $container_statuses ]]; then log_warn 无法获取容器状态可能是 Init 容器阶段 # 检查 Init 容器状态 kubectl get pod $pod -n $ns -o jsonpath{range .status.initContainerStatuses[*]}{.name}: {.state}{\n}{end} 2/dev/null return fi # 提取 Waiting 状态的容器及原因 kubectl get pod $pod -n $ns -o jsonpath{range .status.containerStatuses[*]}{.name}{\t}{.state.waiting.reason}{\t}{.state.waiting.message}{\n}{end} 2/dev/null | \ while IFS$\t read -r name reason message; do if [[ -n $reason ]]; then log_error 容器 $name 处于 Waiting 状态: $reason [[ -n $message ]] log_error 原因: $message # 针对常见 Waiting 原因给出具体建议 case $reason in CrashLoopBackOff) log_warn 建议: kubectl logs -n $ns $pod -c $name --previous log_warn 建议: kubectl describe pod -n $ns $pod | tail -20 ;; ImagePullBackOff|ErrImagePull) log_warn 建议: 检查镜像地址和拉取凭据 log_warn kubectl get secret -n $ns ;; OOMKilled) log_warn 建议: 增加内存限制或排查内存泄漏 log_warn 当前限制: kubectl get pod $pod -n $ns -o jsonpath{.spec.containers[*].resources.limits} ;; esac fi done # 检查重启次数频繁重启意味着持续故障 local restart_count restart_count$(kubectl get pod $pod -n $ns -o jsonpath{.status.containerStatuses[0].restartCount} 2/dev/null || echo 0) if [[ $restart_count -gt 3 ]]; then log_warn 容器已重启 $restart_count 次建议查看历史日志 fi # 输出最近的 Events这是排障最关键的信息 log_info 最近事件: kubectl get events -n $ns --field-selector involvedObject.name$pod --sort-by.lastTimestamp 2/dev/null | tail -10 } # # 模块三网络连通性诊断 # 排查 Service 无端点、DNS 解析失败等问题 # diagnose_network() { local ns$1 local pod$2 log_info 网络诊断: $ns/$pod # 获取 Pod IP local pod_ip pod_ip$(kubectl get pod $pod -n $ns -o jsonpath{.status.podIP} 2/dev/null || true) if [[ -z $pod_ip ]]; then log_error Pod 无 IP 地址可能尚未调度或网络插件异常 return fi log_info Pod IP: $pod_ip # 检查 Pod 所在 Service 的端点 local labels labels$(kubectl get pod $pod -n $ns -o jsonpath{.metadata.labels} 2/dev/null || true) if [[ -n $labels ]]; then log_info 查找匹配的 Service 端点... kubectl get svc -n $ns -o name | while read -r svc; do local endpoints endpoints$(kubectl get $svc -n $ns -o jsonpath{.spec.selector} 2/dev/null || true) if [[ -n $endpoints ]]; then local svc_name svc_name$(echo $svc | cut -d/ -f2) local ep_count ep_count$(kubectl get endpoints $svc_name -n $ns -o jsonpath{.subsets[*].addresses[*].ip} 2/dev/null | wc -w || echo 0) if [[ $ep_count -eq 0 ]]; then log_warn Service $svc_name 无可用端点 else log_info Service $svc_name 有 $ep_count 个端点 fi fi done fi # DNS 解析测试 log_info DNS 解析测试: kubectl exec -n $ns $pod -- nslookup kubernetes.default.svc.cluster.local 2/dev/null \ log_info 集群内部 DNS 正常 || \ log_error 集群内部 DNS 解析失败检查 CoreDNS } # # 模块四资源压力诊断 # 检查节点资源使用和 Pod 资源限制 # diagnose_resources() { local ns$1 local pod$2 log_info 资源诊断: $ns/$pod # 获取 Pod 所在节点 local node_name node_name$(kubectl get pod $pod -n $ns -o jsonpath{.spec.nodeName} 2/dev/null || true) if [[ -z $node_name ]]; then log_error Pod 未调度到任何节点 return fi log_info 所在节点: $node_name # 检查节点资源压力条件 log_info 节点资源压力: kubectl get node $node_name -o jsonpath{range .status.conditions[*]}{.type}{.status}{\n}{end} | \ grep -E MemoryPressure|DiskPressure|PIDPressure | \ while read -r line; do if echo $line | grep -q True; then log_error $line else log_info $line fi done # 检查 Pod 的资源请求和限制 log_info Pod 资源配置: kubectl get pod $pod -n $ns -o jsonpath{range .spec.containers[*]}{.name}{\n Requests: }{.resources.requests}{\n Limits: }{.resources.limits}{\n}{end} 2/dev/null # 检查实际资源使用 log_info 实际资源使用: kubectl top pod $pod -n $ns --containers 2/dev/null || \ log_warn metrics-server 未安装无法获取资源使用数据 } # # 模块五存储诊断 # 排查 PVC 挂载失败和存储类问题 # diagnose_storage() { local ns$1 local pod$2 log_info 存储诊断: $ns/$pod # 获取 Pod 使用的 PVC local pvcs pvcs$(kubectl get pod $pod -n $ns -o jsonpath{range .spec.volumes[*]}{.persistentVolumeClaim.claimName}{\n}{end} 2/dev/null | grep -v ^$ || true) if [[ -z $pvcs ]]; then log_info Pod 未使用 PVC return fi for pvc in $pvcs; do log_info 检查 PVC: $pvc local pvc_status pvc_status$(kubectl get pvc $pvc -n $ns -o jsonpath{.status.phase} 2/dev/null || true) case $pvc_status in Bound) log_info PVC $pvc 状态: Bound正常 # 检查 PV 的回收策略避免数据意外丢失 local pv_name pv_name$(kubectl get pvc $pvc -n $ns -o jsonpath{.spec.volumeName} 2/dev/null || true) local reclaim_policy reclaim_policy$(kubectl get pv $pv_name -o jsonpath{.spec.persistentVolumeReclaimPolicy} 2/dev/null || true) if [[ $reclaim_policy Delete ]]; then log_warn PV $pv_name 回收策略为 DeletePVC 删除后数据将丢失 fi ;; Pending) log_error PVC $pvc 状态: Pending未绑定 # 检查 PVC 事件找出绑定失败的原因 kubectl get events -n $ns --field-selector involvedObject.name$pvc --sort-by.lastTimestamp 2/dev/null | tail -5 ;; Lost) log_error PVC $pvc 状态: Lost底层 PV 丢失 ;; *) log_warn PVC $pvc 状态: $pvc_status ;; esac done } # # 主流程 # main() { log_info K8s 排障工具箱启动 log_info 命名空间: $NAMESPACE check_cluster_health if [[ -n $POD_NAME ]]; then diagnose_pod $NAMESPACE $POD_NAME diagnose_network $NAMESPACE $POD_NAME diagnose_resources $NAMESPACE $POD_NAME diagnose_storage $NAMESPACE $POD_NAME else log_info 未指定 Pod 名称仅执行集群级别检查 log_info 使用方式: $0 namespace pod-name 进行详细诊断 fi log_info 诊断完成 } main脚本设计思路五个模块覆盖了 K8s 故障的五个核心维度——集群健康、Pod 状态、网络连通、资源压力和存储挂载。每个模块独立运行可以单独调用也可以一键全量扫描。输出使用颜色区分级别便于快速定位关键信息。四、K8s 运维的架构权衡没有放之四海皆准的方案资源限制的设置困境。Requests 设低了Pod 可能被调度到资源不足的节点设高了集群资源利用率低成本浪费。Limits 设低了容易被 OOMKilled设高了一个失控的 Pod 可能吃掉整个节点的资源。生产环境建议 Requests 按 P50 实际使用设置Limits 按 P95 设置并配合 LimitRange 和 ResourceQuota 做全局管控。节点维护的停机策略。节点需要重启内核补丁时直接重启会导致 Pod 中断。正确的做法是先kubectl cordon阻止新 Pod 调度再kubectl drain驱逐现有 Pod确认所有 Pod 迁移完成后再维护。但 drain 操作本身可能导致有状态服务的数据不一致需要根据业务特性评估。多集群管理的复杂度。单集群规模超过 5000 节点后etcd 和 API Server 的性能会明显下降。多集群是必然选择但跨集群的服务发现、流量管理和统一监控又引入了新的复杂度。联邦集群方案目前还不够成熟多数团队选择自建多集群管理平台。版本升级的风险。K8s 每年发布三个小版本每个版本只维护 14 个月。升级是必须的但每次升级都可能引入 API 变更和行为差异。建议在预发环境完整验证后再升级生产环境并保留回滚方案。五、总结K8s 生产环境运维的核心能力不是记住所有命令而是建立系统化的排查思维。从集群到节点从 Pod 到容器从网络到存储逐层定位、逐层排除。事件和日志是排障的指南针比任何经验都可靠。排障工具箱的价值在于标准化——将运维人员的排查经验固化为可复用的脚本减少人为遗漏提高排障效率。但工具只是手段理解 K8s 的架构原理和故障传播机制才是快速定位问题的根本。守护系统稳定运行需要的不仅是技术还有耐心和系统化的思维。就像在山野中辨认方向地图和指南针是工具但真正让你走出困境的是对地形的理解和冷静的判断。