就绪探针调优保障 Kubernetes 集群升级时服务流量零中断:K8s 应用健康检查优化策略 就绪探针调优保障 Kubernetes 集群升级时服务流量零中断K8s 应用健康检查优化策略前言涵姐凌晨 Rolling Update 的时候线上报警了用户反馈有几十秒的 502监控上的错误率飙到 8% 了。周二早上我刚到工位Ping 正蹲在我的键盘上舔爪子小周就顶着一对黑眼圈跑过来了。我瞥了一眼他的 Deployment YAML——strategy.rollingUpdate.maxSurge25%, maxUnavailable25%Readiness Probe 的配置是默认值PreStop Hook 也没配。你这是裸更新的啊旧 Pod 还在处理请求的时候就被 Kill 了新 Pod 还没 Ready 就开始接流量不出问题才怪。小周挠了挠头Readiness Probe 我配了啊只是用的默认参数……默认参数就 3 秒 initialDelaySeconds 加 1 秒 periodSeconds你服务启动就要 15 秒探针早挂了。还有Pod 被删的时候 SIGTERM 发完一秒就被强制 Kill 了连接全断了。我把 Ping 从键盘上抱走打开终端今天给你系统讲一下 Readiness Probe 的调优配合 PreStop Hook 和 PDB彻底解决集群升级时的流量中断问题。一、底层原理健康检查机制与集群升级流量中断根因分析1.1 两种探针的职责边界Kubernetes 提供了三种探针Probe但最容易混淆的就是 ReadinessProbe 和 LivenessProbe维度ReadinessProbe就绪探针LivenessProbe存活探针StartupProbe启动探针核心职责判断 Pod是否准备好接收流量判断 Pod是否还活着需不需要重启判断 Pod是否已完成启动v1.16失败后果从 Service Endpoint 中移除不重启Kubelet 重启容器强制拉起重部署屏蔽 Liveness 检测给慢启动应用缓冲期检测时机整个 Pod 生命周期持续检测整个 Pod 生命周期持续检测只在启动阶段检测成功后交给 Liveness典型场景应用启动慢、热加载中、依赖未就绪死锁、OOM、goroutine 泄漏、进程 Hang大型 Java 应用、AI 模型加载、Warmup 期对流量影响直接影响流量路径移除后 Service 不再转发间接影响重启后 Readiness 会重新检测启动期间接管Liveness 判定关键认知滚动更新时流量中断的核心原因不是 Liveness 失败而是Readiness 状态切换与 Pod 终止流程的时序错配。1.2 集群升级时流量中断的完整链路分析集群升级分为控制面升级和节点升级两类场景。节点升级会触发节点上所有 Pod 的重新调度下面以最常见的节点升级为例flowchart TD subgraph 阶段一驱逐开始 A[运维触发节点升级\nkubectl drain node] -- B[Node Controller\n标记 Node 为\nNode.Spec.Unschedulabletrue] B -- C[Eviction API\n创建 Pod 驱逐请求] end subgraph 阶段二Pod 终止流程默认配置 C -- D[kubelet 收到\nPod 删除请求] D -- E{Readiness Probe\n是否已失败?} E --|未配置 PreStop\n直接进入| F[SIGTERM 发送\nterminationGracePeriodSeconds\n默认 30s] E --|配置了 PreStop\n执行 Hook| F F -- G[一旦 PreStop 完成\n或超时默认 30s] G -- H[SIGKILL 强制终止\n容器进程] end subgraph 阶段三流量中断发生问题所在 H -- I[旧 Pod 进程死亡\n正在处理的请求\n被硬中断] I -- J[客户端收到\nConnection Reset / 502] J -- K[新 Pod 启动\nReadiness Probe\n还未通过] K -- L[Service Endpoint\n无可用后端\n流量黑洞] end style E fill:#fff3e0,stroke:#f57c00 style J fill:#fce4ec,stroke:#d32f2f style L fill:#fce4ec,stroke:#d32f2f中断时间线默认配置未优化t0s Pod 收到删除请求开始 Termination t0.1s Endpoint Controller 从 Service 移除该 Pod IP └── 但Endpoint 更新需要 time to propagate kube-proxy 同步 iptables/IPVS 规则需要时间 这期间仍在转发流量到该 Pod t1s Readiness Probeperiod1s可能还未检查 t1~5s PreStop Hook 开始执行 / SIGTERM 发送 t5~30s 应用处理 SIGTERM如果实现了优雅关闭 t30s terminationGracePeriodSeconds 超时 → SIGKILL └── 此时还在处理的请求全部中断 t30~45s 新 Pod 启动Readiness Probe 等待 initialDelaySeconds t45~50s 新 Pod Readiness 通过Endpoint 重新加入 └── 注意45~50s 这段时间 Service 无可用后端二、快速上手Readiness Probe 的三种检测方式与参数配置2.1 三种检测方式Kubernetes 支持三种探针检测方式适用场景各不相同HTTP Get 检测最常用apiVersion: v1 kind: Pod metadata: name: web-app labels: app: web spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 readinessProbe: httpGet: path: /healthz port: 80 httpHeaders: - name: X-Health-Check value: true initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3TCP Socket 检测适合非 HTTP 服务readinessProbe: tcpSocket: port: 3306 initialDelaySeconds: 15 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 2Exec 命令检测最灵活适合复杂逻辑readinessProbe: exec: command: - sh - -c - | # 检查应用内部状态 curl -sf http://localhost:8080/ready test -f /tmp/app_initialized pgrep -f main-process initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 32.2 探针参数详解与调优建议参数默认值含义调优建议服务端场景initialDelaySeconds0容器启动后等待多久开始首次检测设为服务平均启动时间 5s避免启动阶段误判periodSeconds10检测间隔生产建议5~15s间隔太短增加 Kubelet 负载timeoutSeconds1单次检测超时时间3~5s避免网络抖动误报successThreshold1连续成功几次视为就绪通常1即可高可用要求可设为2failureThreshold3连续失败几次视为不就绪2~5配合 periodSeconds 控制总容忍时间总失败判定时间计算总容忍时间 periodSeconds × (failureThreshold - 1) timeoutSeconds例如periodSeconds10, failureThreshold3, timeoutSeconds3第1次失败开始计数第2次失败计数 110s后第3次失败判定为不就绪再过10s总时间 ≈ 20~23s 后 Pod 从 Service 移除2.3 PreStop Hook 优雅关闭Readiness Probe 只是管了新请求别过来但正在处理的请求必须靠 PreStop Hook 和应用的优雅关闭机制来保证不中断apiVersion: apps/v1 kind: Deployment metadata: name: api-server spec: replicas: 3 selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: terminationGracePeriodSeconds: 60 containers: - name: api-server image: api-server:latest ports: - containerPort: 8080 readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 15 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 30 periodSeconds: 15 timeoutSeconds: 5 failureThreshold: 3 lifecycle: preStop: exec: command: - sh - -c - | # 1. 标记自身不再接受新请求应用内 curl -sf -X POST http://localhost:8080/healthz/drain \ # 2. 等待 Readiness Probe 检测到失败 # failureThreshold × periodSeconds 3×515s sleep 15 \ # 3. 等待现有请求处理完毕 sleep 10 # 总耗时 ≈ 25s # terminationGracePeriodSeconds 设为 60s 足够三、核心 API 与深水区探针调优的底层机制3.1 Kubelet 探针执行引擎源码分析Kubelet 中的probeManager是探针的调度和执行引擎。每个 Probe 由worker独立运行循环执行检测逻辑// 源码路径: pkg/kubelet/prober/prober_manager.go // 核心逻辑简化示意 package prober import ( time v1 k8s.io/api/core/v1 k8s.io/apimachinery/pkg/util/wait k8s.io/klog/v2 ) type manager struct { workers map[probeKey]*worker // ... 其他字段 } type worker struct { pod *v1.Pod container v1.Container probeType probeType // readiness / liveness / startup prober *prober results resultsManager // 上次探测结果 lastResult Result // 连续成功/失败计数 consecutiveFailures int consecutiveSuccesses int } func (w *worker) run() { // 等待 initialDelaySeconds time.Sleep(w.getInitialDelay()) // 进入周期性探测循环 wait.Until(func() { w.probeAndRecord() }, w.probePeriod(), w.stopCh) } func (w *worker) probeAndRecord() { result, err : w.prober.probe(w.probeType, w.pod, w.container) if err ! nil { klog.Errorf(Probe error: %v, err) result Unknown } // 更新连续成功/失败计数 switch result { case Success: w.consecutiveSuccesses w.consecutiveFailures 0 case Failure: w.consecutiveFailures w.consecutiveSuccesses 0 case Unknown: w.consecutiveFailures w.consecutiveSuccesses 0 } // 判断是否需要切换状态 // changeDetected 会检查 successThreshold / failureThreshold if w.changeDetected(result) { w.updatePodStatus(result) } }3.2 Endpoint 同步延迟流量中断的隐藏因素很多人以为 Readiness Probe 失败后流量立刻就不来了实际上 Endpoint 传播存在多级延迟flowchart LR A[Pod Readiness\n状态变化] --|延迟1: Kubelet\n探测周期 上报| B[Endpoint Controller\nWatch 到变化] B --|延迟2: EndpointSlice\n更新 写入 etcd| C[kube-proxy\nWatch EndpointSlice] C --|延迟3: kube-proxy\n同步 iptables/IPVS\n规则默认 30s 全量同步| D[节点 iptables/IPVS\n规则生效] style A fill:#e1f5fe,stroke:#0288d1 style D fill:#fce4ec,stroke:#d32f2f各环节延迟估算延迟环节默认值优化方向Kubelet 探测周期periodSeconds默认 10s缩短 periodSeconds 到 5sKubelet 状态上报默认 10s 上报周期调整nodeStatusUpdateFrequency已弃用v1.20 用nodeStatusUpdateFrequencyEndpoint Controller Watch实时毫秒级无需优化kube-proxy 同步周期IPVS 模式增量同步毫秒级iptables 模式全量30s使用 IPVS 模式关键节点 conntrack 更新微秒级确保 conntrack 参数合理核心结论切换到 IPVS 模式 调小periodSeconds是减少 Endpoint 传播延迟最有效的手段。3.3 PodDisruptionBudget 配置PDB 保证在节点升级时指定数量的 Pod 始终可用apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: api-server-pdb spec: minAvailable: 2 # 最少保持 2 个 Pod 可用 # 或者使用 maxUnavailable # maxUnavailable: 1 # 最多允许 1 个不可用 selector: matchLabels: app: api-serverPDB 的工作逻辑kubectl drain执行时Eviction API 会检查 PDB如果驱逐 Pod 会导致可用 Pod 数低于minAvailable驱逐请求被拒绝控制器的kubectl drain会重试等待直到有新的 Pod Ready 才继续驱逐PDB 配合 Rolling Update Strategy 的完整时序flowchart TD Start[触发 Rolling Update\nkubectl set image deploy/api-server] -- PDBCheck{PDB 检查\nminAvailable2\n当前可用 Pod 3?} PDBCheck --|是| NewPod[创建新 Pod v2\n等待 Readiness Probe 通过] NewPod -- ProbeCheck{新 Pod\nReadiness Probe\n通过了吗?} ProbeCheck --|否| Wait[等待探针周期检测\ninitialDelaySeconds \nperiodSeconds × failureThreshold] Wait -- ProbeCheck ProbeCheck --|是| EndpointAdd[新 Pod IP\n加入 Service Endpoint] EndpointAdd -- ScaleDown[开始删除旧 Pod v1\nPDB 保护仍生效] ScaleDown -- DrainCheck{可用 Pod minAvailable\n2个?} DrainCheck --|是| OldPodDelete[删除旧 Pod\n执行 PreStop Hook\n优雅关闭] DrainCheck --|否| WaitScale[等待更多新 Pod Ready\n直到满足 PDB 条件] OldPodDelete -- Continue[继续下一轮\n直到所有旧 Pod 替换完毕] style NewPod fill:#e8f5e9,stroke:#388e3c style OldPodDelete fill:#fff3e0,stroke:#f57c00 style DrainCheck fill:#e1f5fe,stroke:#0288d1四、实战演练从零构建零中断升级方案4.1 场景设定集群3 节点 K8s v1.28kube-proxy 使用 IPVS 模式服务API Server3 副本启动时间约 12s升级方式kubectl drain节点升级 Rolling Update目标升级过程中零 502零连接中断4.2 完整 Production 级配置--- apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: api-server-pdb namespace: production spec: minAvailable: 2 selector: matchLabels: app: api-server --- apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: production spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 每次额外启动 1 个新 Pod maxUnavailable: 0 # 确保 0 个不可用配合 PDB 双保险 selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: terminationGracePeriodSeconds: 70 containers: - name: api-server image: api-server:2.0.0 ports: - containerPort: 8080 protocol: TCP env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 15 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 30 periodSeconds: 15 timeoutSeconds: 5 failureThreshold: 3 lifecycle: preStop: exec: command: - sh - -c - | echo [$(date)] Pod $POD_NAME 开始优雅关闭... # 步骤1标记自身为 Drain 状态 # 应用内 /healthz/ready 返回 503 curl -sf -X POST http://127.0.0.1:8080/healthz/drain || true echo [$(date)] 已触发 Drain 标记 # 步骤2等待 Readiness Probe 检测到不就绪 # failureThreshold3, periodSeconds5 → 最多 15s # 实际可能更快timeoutSeconds3 内返回非 200 echo [$(date)] 等待 Readiness 失效传播... sleep 18 # 步骤3等待已分发请求处理完成 # 假设 max request timeout 10s echo [$(date)] 等待请求处理完成... sleep 12 echo [$(date)] 优雅关闭完成允许 SIGTERM 继续 # 总耗时约 30~35s # terminationGracePeriodSeconds70 充裕 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1000m memory: 1Gi affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - api-server topologyKey: kubernetes.io/hostname4.3 服务端优雅关闭实现Go 示例Readiness Probe 的/healthz/ready端点需要感知到 Drain 状态配合 PreStop Hookpackage main import ( context encoding/json log net/http os os/signal sync syscall time ) type HealthServer struct { mu sync.RWMutex draining bool ready bool startTime time.Time } func NewHealthServer() *HealthServer { return HealthServer{ startTime: time.Now(), } } // Readiness 端点drain 时返回 503 func (h *HealthServer) ReadinessHandler(w http.ResponseWriter, r *http.Request) { h.mu.RLock() defer h.mu.RUnlock() if h.draining { w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]string{ status: draining, message: Pod is being drained, stop sending traffic, }) return } if !h.ready { w.WriteHeader(http.StatusServiceUnavailable) json.NewEncoder(w).Encode(map[string]string{ status: not ready, message: Application not fully initialized, }) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ status: ready, }) } // Liveness 端点只判断进程是否存活 func (h *HealthServer) LivenessHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ status: alive, }) } // Drain 端点由 PreStop Hook 调用标记开始排空 func (h *HealthServer) DrainHandler(w http.ResponseWriter, r *http.Request) { h.mu.Lock() h.draining true h.mu.Unlock() log.Println([Health] Drain signal received, marking as not ready) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ status: draining started, }) } // 初始化完成标记 func (h *HealthServer) MarkReady() { h.mu.Lock() defer h.mu.Unlock() h.ready true } func main() { health : NewHealthServer() mux : http.NewServeMux() mux.HandleFunc(/healthz/ready, health.ReadinessHandler) mux.HandleFunc(/healthz/live, health.LivenessHandler) mux.HandleFunc(/healthz/drain, health.DrainHandler) server : http.Server{ Addr: :8080, Handler: mux, } // 模拟启动耗时 log.Println(Application starting...) time.Sleep(5 * time.Second) health.MarkReady() log.Println(Application ready) // 启动 HTTP 服务 go func() { log.Printf(Health server listening on %s, server.Addr) if err : server.ListenAndServe(); err ! nil err ! http.ErrServerClosed { log.Fatalf(Server error: %v, err) } }() // 等待操作系统信号 quit : make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) -quit log.Println(Shutting down server...) ctx, cancel : context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err : server.Shutdown(ctx); err ! nil { log.Fatalf(Server forced to shutdown: %v, err) } log.Println(Server exited gracefully) }4.4 节点升级脚本配合 PDB 和 Readiness Probe 的节点升级操作流程#!/bin/bash # 零中断节点升级脚本 set -euo pipefail NODE_NAME$1 DRAIN_TIMEOUT5m RETRY_INTERVAL30s echo 开始节点升级: $NODE_NAME # 步骤1先 cordon 节点防止新 Pod 调度 echo [1/5] Cordon node $NODE_NAME... kubectl cordon $NODE_NAME # 步骤2检查节点上运行的重要 Pod 状态 echo [2/5] Checking critical Pods on node... kubectl get pods --all-namespaces --field-selector spec.nodeName$NODE_NAME \ -o json | jq -r .items[] | select(.metadata.labels[app] ! null) | \(.metadata.namespace)/\(.metadata.name) - \(.status.phase) # 步骤3执行 drainPDB 会保护关键服务 echo [3/5] Draining node $NODE_NAME (timeout: $DRAIN_TIMEOUT)... kubectl drain $NODE_NAME \ --ignore-daemonsets \ --delete-emptydir-data \ --grace-period120 \ --timeout$DRAIN_TIMEOUT \ --force # 步骤4确认节点已排空 echo [4/5] Verifying node is fully drained... while true; do POD_COUNT$(kubectl get pods --all-namespaces \ --field-selector spec.nodeName$NODE_NAME \ --no-headers 2/dev/null | wc -l) if [ $POD_COUNT -eq 0 ]; then break fi echo Waiting for $POD_COUNT pods to be evicted... sleep $RETRY_INTERVAL done echo Node $NODE_NAME is fully drained. # 步骤5执行节点升级操作替换为实际升级命令 echo [5/5] Upgrading node $NODE_NAME... # 例如ssh $NODE_NAME sudo apt upgrade -y sudo reboot echo Node $NODE_NAME upgrade completed 4.5 验证升级过程中的流量中断使用持续压测验证升级过程中的零中断效果# 终端1启动持续压测 kubectl run -it load-generator --imagebusybox --rm --restartNever -- sh -c echo Starting load test... i0 while true; do STATUS$(wget -q -O- -S http://api-server.production:8080/healthz/ready 21 | head -1) echo [$i] $STATUS i$((i1)) sleep 0.5 done # 终端2执行 Rolling Update kubectl set image deployment/api-server api-serverapi-server:2.0.1 -n production kubectl rollout status deployment/api-server -n production -w # 终端3观察 Endpoint 变化 watch -n 1 kubectl get endpoints api-server -n production # 终端4观察 Pod 滚动过程 watch -n 1 kubectl get pods -n production -l appapi-server -o wide五、避坑指南问题 1StartupProbe 缺失导致慢启动应用被不断重启现象服务启动需要 20s但 Liveness Probe 的initialDelaySeconds10, failureThreshold2, periodSeconds10在第 20s 时第 2 次探测超时直接判定失败容器被重启陷入启动→被 Kill→重启的死循环。解决方案对于启动慢的应用尤其是 AI 模型加载、Java 应用加 StartupProbestartupProbe: httpGet: path: /healthz/startup port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 30 # 最大等待 5 30×5 155sStartupProbe 期间 LivenessProbe 和 ReadinessProbe 不会执行。问题 2Readiness 与 Liveness 使用同一个端点现象两个探针都指向/healthz且该端点在应用忙碌时返回 503。Liveness 误判导致容器被杀重启。根本原因Readiness 和 Liveness 是不同维度的检查Readiness检查应用是否准备好处理请求——可以返回 503 表示忙碌Liveness检查进程是否还活着——只要进程在运行就应该返回 200解决方案严格区分两个端点// Readiness检查依赖是否就绪数据库、缓存等 mux.HandleFunc(/healthz/ready, func(w http.ResponseWriter, r *http.Request) { if !dbReady || !cacheReady { w.WriteHeader(http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusOK) }) // Liveness只检查进程是否健康 mux.HandleFunc(/healthz/live, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })问题 3PreStop Hook 超时导致 SIGKILL 提前发送现象PreStop Hook 中写了一个复杂的清理逻辑预计执行 45s但terminationGracePeriodSeconds还是默认值 30s。第 30s 时 SIGKILL 直接发送清理逻辑中断。根因terminationGracePeriodSeconds涵盖了 PreStop Hook SIGTERM 后的清理时间。默认 30s 远远不够。解决方案spec: # terminationGracePeriodSeconds 必须 PreStop Hook 总耗时 terminationGracePeriodSeconds: 90 containers: - name: app lifecycle: preStop: exec: command: - sh - -c - | # 总预期待 45s echo Draining connections... sleep 40 # 实际应为 drain 等待逻辑 echo Cleanup done计算方式terminationGracePeriodSeconds ≥ PreStop 预估耗时 × 1.5。问题 4failureThreshold 过大导致流量被转发到已死亡的 Pod现象Pod 内部进程已经 OOM killed 了但 Readiness Probe 要等failureThreshold5, periodSeconds15即 75s 后才能从不就绪。这期间 Service 仍然在转发流量到该 Pod。解决方案Readiness Probe 的failureThreshold不宜过大readinessProbe: httpGet: path: /healthz/ready port: 8080 periodSeconds: 5 failureThreshold: 2 # 10s 内标记不就绪 # 总容忍时间 5 × (2-1) 5s加上 timeoutSeconds 约 8s同时结合 Liveness Probe 兜底确保真正死亡的 Pod 能被更快重启livenessProbe: httpGet: path: /healthz/live port: 8080 periodSeconds: 10 failureThreshold: 3 # 30s 内重启问题 5maxUnavailable 策略设置不当导致 Pod 全部下线现象strategy.rollingUpdate.maxUnavailable: 25%3 副本时有 25% × 3 0.75 → 向下取整 0这没问题。但如果副本数只有 225% × 2 0.5 → 向下取整仍然为 0。但如果设置maxUnavailable: 1且配合MaxSurge: 12 副本滚动更新时K8s 先删一个旧 Pod因为 maxUnavailable1再启动一个新 Pod在替换期间只有 1 个 Pod 可用。但如果没有 PDB 保护且 Readiness Probe 还没通过新 Pod就会导致短暂的服务不可用。解决方案strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 先启动新 Pod maxUnavailable: 0 # 确保旧 Pod 不被删配合 PDB 保护apiVersion: policy/v1 kind: PodDisruptionBudget spec: minAvailable: 2 # 最少 2 个 Pod 运行这样滚动更新时先创建新 Pod → 等 Readiness 通过 → 再删旧 Pod始终保证至少 2 个 Pod 在服务。六、总结回到小周的凌晨故障我们逐一排查后发现了四个问题Readiness Probe 参数不合理initialDelaySeconds3远小于服务启动时间 12s导致新 Pod 刚启动就被标记就绪实际上还没准备好缺少 PreStop HookSIGTERM 发出后应用来不及处理已有请求连接被硬中断未配置 PDB节点升级时 3 个 Pod 同时被驱逐服务完全不可用使用 iptables 模式Endpoint 同步延迟 全量规则刷新加剧了流量漂移问题优化后的配置清单优化项配置建议解决的核心问题Readiness Probe 调优initialDelaySeconds15, periodSeconds5, failureThreshold3避免新 Pod 过早接流量PreStop HookDrain 标记 等待 Readiness 失效 等待请求完成已有连接不断开PDB 保护minAvailable: 2或maxUnavailable: 1防止过多 Pod 同时下线IPVS 模式kube-proxy mode: ipvs减少 Endpoint 规则同步延迟优雅关闭服务端/healthz/drain端点 server.Shutdown()应用层配合 Kubernetes 生命周期RollingUpdate 策略maxSurge: 1, maxUnavailable: 0先建后删零下线写这篇文章的时候Ping 又跳到我的膝盖上蜷成一团睡着了。它睡得很沉偶尔尾巴还会轻轻抽动一下——大概在梦里追老鼠吧。如果集群升级也能像它睡觉这么安稳就好了。不过配置好这些策略后小周凌晨确实再没接到过升级报警了。他说涵姐现在我可以安心睡觉了。我说那你睡得比 Ping 还香了吧下篇文章见