
很多人写Pod配置习惯从网上抄一份YAML就往集群里丢表面上跑得挺正常可一遇到发布、故障、流量抖动问题就全冒出来了。这些年我排查过的线上事故里至少有一半的根因都能归结到Pod生命周期管理上——要么Init容器卡住导致主容器迟迟起不来要么探针配置不合理让Pod明明活着却接不到流量要么优雅终止没设计好滚动更新时一路502。这篇文章我把Init容器、探针、优雅终止三块内容彻底讲透不背概念直接给能落地的配置、调优参数和排查思路适合正在维护Kubernetes工作负载的开发者、SRE以及刚接触Pod编排想少踩坑的人。1. 先建立全局观Pod从创建到消失经历了哪些阶段1.1 一个Pod的生命周期路径从Pending到Failed/SucceededKubernetes里Pod的生命周期不是一个笼统的运行中而是一条有明确阶段的路径。从kubectl get pod看到的PHASE到Pod内部的各种Condition再到容器级别的状态每一层都有各自的语义。理解这条路径是管理Pod生命周期的前提。一个Pod被创建后首先进入Pending。此时Pod已经提交给API Server但还没被调度到具体节点。调度器介入后Pod被绑定到某个节点kubelet开始干活拉取镜像、创建沙箱然后按顺序执行Init容器。所有Init容器都成功退出后主容器才会被启动。主容器启动后Pod进入Running但这不代表它已经可用——探针结果决定了它能不能被加进Service的Endpoints。整个生命周期中如果某个容器崩溃或者存活探针失败kubelet会按重启策略处理如果Pod因为不可恢复的错误退出或者被正常删除且不再重启最终进入Succeeded或Failed。这里有一个容易混淆的点Pod的PhasePending/Running/Succeeded/Failed/Unknown和Pod的ConditionsPodScheduled、Initialized、ContainersReady、Ready是两套信号。Phase是宏观状态Conditions是细粒度条件。比如一个Pod可以PhaseRunning但Condition里的ReadyFalse因为它还在做就绪探针的首次探测。生产环境排障时靠kubectl describe pod查看Conditions往往比只盯Phase有用得多。1.2 为什么生命周期管理的核心落在Init容器、探针、优雅终止这三件事上Pod生命周期里可配置的东西不少比如activeDeadlineSeconds、restartPolicy、postStart钩子但实际生产环境中影响最大的就是标题里的三件事它们的职责其实非常清晰Init容器解决的是主容器启动的前置条件问题。数据库还没就绪、配置还没生成、目录权限还没处理好这些事如果丢给主容器自己处理会让业务进程的启动逻辑变得很脏。探针解决的是Kubernetes如何判断容器当前状态的问题。存活探针管要不要重启就绪探针管能不能接流量启动探针管慢启动应用的保护。优雅终止解决的是Pod被删除时如何体面退场的问题。连接怎么排空、在途请求怎么处理完、日志怎么刷干净全在这最后几十秒里。这三件事不是孤立的。Init容器没写好主容器起来就是带病启动探针没配置对Kubernetes对容器的判断就是错的优雅终止没设计好哪怕探针全绿发布时用户照样看到错误。所以这篇文章先分别拆最后用一个真实事故把它们串起来。2. Init容器主容器启动前的预备车间2.1 Init容器到底解决了什么问题没有Init容器之前团队常见的做法是把前置等待逻辑塞进主容器的entrypoint脚本里。比如订单服务要等数据库可用于是启动脚本里写个until nc -z db 3306; do sleep 1; done。短时间内看着能跑但后果是业务进程的启动代码被环境检查逻辑污染日志混在一起脚本挂了没人知道该查哪一层。Init容器把这类启动前准备工作从主容器里剥离出来变成一个独立的、跑完就退出的容器。Init容器和普通容器有几个本质区别串行执行Pod里的多个Init容器按定义顺序一个一个跑前一个成功退出后一个才开始全部成功后才会启动主容器。注意主容器之间是并行启动的但Init容器绝无并行。跑完即退Init容器不是常驻进程它的任务是执行一个命令或脚本然后退出。退出码为0代表成功。失败处理依赖restartPolicy如果Init容器失败kubelet会根据Pod的restartPolicy决定行为。Always会重启整个Pod所有容器重新开始Init容器从头跑OnFailure会重启Init容器直到成功Never直接让Pod进入Failed。没有探针Init容器不支持配置readiness/liveness/startup探针。它本身的成功就等同于退出码为0不需要额外健康检查机制。Init容器的典型场景包括等待依赖服务可用、执行数据库迁移或数据初始化、从配置中心拉取配置并写入共享卷、给共享目录设置权限、下载模型或静态资源。这些任务有一个共同点必须在业务进程启动前全部完成否则主容器起来也干不了活。2.2 一份可以直接套用的Init容器配置示例下面这个例子模拟了一个常见的等待依赖生成配置组合。第一个Init容器等数据库端口通第二个Init容器把模板配置拷贝到共享卷并替换占位符。apiVersion: v1 kind: Pod metadata: name: order-service-pod spec: initContainers: - name: wait-for-db image: busybox:1.36 command: - sh - -c - | until nc -z order-db 3306; do echo waiting for order-db:3306... sleep 3 done echo order-db is ready - name: generate-config image: busybox:1.36 env: - name: APP_ENV value: prod command: - sh - -c - | cp /config-tpl/app.yml.tpl /work/app.yml sed -i s/__APP_ENV__/${APP_ENV}/g /work/app.yml echo config generated volumeMounts: - name: workdir mountPath: /work - name: config-tpl mountPath: /config-tpl readOnly: true containers: - name: app image: registry.example.com/order-service:2.3.1 volumeMounts: - name: workdir mountPath: /app/config这里面有两个容易忽略的细节。第一generate-config容器里用了env注入环境变量再用sed替换模板文件里的占位符这比在镜像里硬编码配置干净得多。第二共享卷workdir被Init容器和主容器同时挂载了这就是Init容器向主容器传递成果的标准方式。调试Init容器时最常用的命令是这几个kubectl describe pod pod看Init容器的状态和事件kubectl logs pod -c wait-for-db看某个Init容器的输出。如果Init容器一直不结束describe里会显示Init:0/2这种状态配合kubectl get events能很快定位。2.3 Init容器的资源、重启策略与调度细节Init容器在资源方面有一个很反直觉的规则调度器在算节点资源够不够时会取所有Init容器资源请求的最大值和所有主容器资源请求总和这两个值中的较大者来评估。也就是说如果某个Init容器requests.cpu写了个很大的值即使主容器都很小整个Pod在这个节点上的调度预留也会被这个大头顶上去。这个规则的本意是保证Init容器执行期间资源不被挤占但实践中经常坑到人。我见过一个团队给Init容器配了limits.cpu: 4主容器反而只用了250m结果Pod因为资源不够一直Pending排查了半天才发现是Init容器的limit拖了后腿。所以给Init容器配资源时要遵循够用就好原则不做业务计算就不要给大资源。关于重启再补充一个容易误解的点restartPolicy: Always时如果Init容器失败kubelet重启的是整个Pod不是只重启这个Init容器。这意味着所有Init容器都要从头再执行一遍。如果你的Init容器里有副作用操作比如写数据库要保证它具备幂等性——重复执行也不会产生脏数据或重复初始化。另外Pod级别的activeDeadlineSeconds同样适用于Init容器阶段。如果所有Init容器加起来的执行时间超过这个值Pod会被判定失败对长时间等待外部服务的场景要格外小心。2.4 什么场景不建议用Init容器Init容器不是万能的有些场景用了反而增加复杂度。主应用本身已有完善的等待重试逻辑比如很多框架自带服务发现和重连机制启动时连不上依赖会自动重试。这时候再加Init容器做前置等待属于重复劳动还会拖慢整个Pod的启动时间。任务可以并行或延后执行Init容器是严格串行的如果两个前置任务互不依赖硬塞进两个Init容器反而让启动时间变成累加值。这时候可以考虑合并进一个Init容器或者用主容器里的异步初始化。对启动时间极其敏感的场景每多一个Init容器Pod从Pending到Ready的时间就多一段串行等待。如果业务对扩容速度要求很高比如秒级弹性尽量把前置条件做成主容器内的快速自检而不是外部等待。说白了Init容器适合的是不做完就不能启动的硬性前置条件。可做可不做的事不要往Init容器里堆。3. 探针让Kubernetes准确判断Pod活着且可用3.1 liveness、readiness、startup三分工探针是kubelet定期对容器执行的健康检查Kubernetes通过探针结果决定怎么对待这个容器。三种探针的职责完全不同很多人把它们混为一谈这是配置事故的第一大来源。livenessProbe存活探针回答进程还活着吗。如果探测失败kubelet会杀掉容器并按restartPolicy重启它。它防的是死锁、OOM后的僵尸状态、线程池耗尽但进程还没退出这类看似活着实际已死的情况。readinessProbe就绪探针回答现在能接收流量吗。如果探测失败容器不会被重启但会被从Service的Endpoints里摘掉流量不再打到它。等探针恢复了Endpoints会自动加回来。它管的是进程活着但不一定能干活的情况比如依赖的Redis断了、连接池满了、正在加载大量数据。startupProbe启动探针它的出现是为了解决慢启动应用被liveness误杀的问题。如果应用启动要两三分钟而liveness探针在启动后10秒就开始探测且很快就失败容器会被反复重启永远起不来。startup探针专门探测进程启动是否完成启动期间由它决定容器状态一旦探测成功后续就交还给liveness和readiness接管。注意startup探针本身失败也会导致容器重启它的保护对象是慢启动的容器不是不健康的容器。记住一个判断口诀liveness管要不要重启readiness管要不要接流量startup管慢启动别被误杀。这三者不是同一个东西的三种写法。3.2 HTTP、TCP、Exec三类探针的配置与参数调优探针有三种常用探测方式加上新版支持的gRPC核心就两类探测进程内状态和探测网络可达性。Exec探针在容器内执行命令靠退出码判断成败适合没有HTTP接口的进程或需要检查本地文件的场景livenessProbe: exec: command: - sh - -c - test -f /tmp/healthy initialDelaySeconds: 5 periodSeconds: 10HTTP探针通过请求容器的HTTP接口判断状态是Web服务最常用的方式。判断标准是响应码在200到399之间视为成功readinessProbe: httpGet: path: /healthz/ready port: 8080 httpHeaders: - name: X-Health-Check value: 1 initialDelaySeconds: 3 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 3TCP探针只检查端口能否建立连接适合Redis、MySQL这类以TCP端口为服务边界的中间件但要注意它只验证端口开放无法验证服务真正可用livenessProbe: tcpSocket: port: 6379 initialDelaySeconds: 10 periodSeconds: 10关于探测参数下面这张表是默认值和推荐起始值实际要根据应用特征调整参数默认值含义生产环境常见调整initialDelaySeconds0容器启动后多久开始第一次探测根据启动时间设置避免首轮探测太早periodSeconds10探测间隔读多写少5~15秒居多timeoutSeconds1单次探测超时有慢查询的应用调到2~5秒successThreshold1连续成功几次才算成功就绪探针可调到2避免抖动failureThreshold3连续失败几次才算失败易抖动应用调到5~6避免误杀把参数换算成实际时间很重要。比如一个liveness探针periodSeconds10、failureThreshold3、timeoutSeconds2那么从开始失败到kubelet决定重启容器最快也要约101010232秒。如果应用只是GC停顿了8秒这个配置就不会误杀如果应用GC停顿40秒就会被杀掉这时要么调大failureThreshold要么换用startup探针掩护启动阶段而不是盲目把liveness放宽到完全无感。3.3 探针配置最常见的坑探针是排障时最容易背锅的配置但很多锅其实是自己埋的。下面几条是我反复踩过的探针地址写错。HTTP探针的host字段如果写了Service域名或ClusterIP探测请求会先经过kube-proxy的转发链一来多一跳二来如果目标Pod网络异常可能把网络抖动当成应用故障。正确做法是不写host默认用Pod IP探测或者写127.0.0.1前提是应用确实监听了本地回环。探针要探测的是这个容器自己不是这个服务。liveness探针调用了重接口。有些团队直接把应用的完整健康检查接口挂到liveness上这个接口内部会查数据库、查缓存、查下游依赖一次探测可能耗时几百毫秒甚至几秒。liveness探针每10秒触发一次等于每10秒让应用做一次全链路体检既浪费CPU又可能因为依赖抖动触发重启。liveness只应该检查进程存活状态重依赖检查交给readiness。健康检查接口分开设计/healthz/live只做本地检查返回200/healthz/ready才检查依赖。exec探针频繁起进程。Exec探针每次探测都会在容器里创建一个新进程如果探测间隔很短容器内僵尸进程会堆积CPU开销也不小。能用HTTP/TCP解决的尽量不用Exec。startup探针失败误当就绪问题。前面说过startup探针失败会重启容器而很多人把它当成启动阶段不接流量的工具来用。如果你的应用只是暂时没准备好但进程本身没死应该增大startup的failureThreshold和periodSeconds而不是以为等它就绪就行。startup探针的本质是启动超时即判死。3.4 探针结果如何影响Service流量接入readiness探针和流量接入是联动关系。每次readiness探测成功kubelet才把Pod标记为ReadyPod的IP才会被加入Service的Endpoints/EndpointSlice一旦readiness失败Pod IP会被摘除新请求不再转发过来但已经建立的连接不会被Kubernetes主动切断这个切断动作必须由应用自己或负载均衡器完成。这就引出和下一章优雅终止的衔接点readiness探针负责在运行期间不要往不健康的Pod转发新流量优雅终止负责在Pod退出时把存量连接处理完。两者配合滚动更新才能做到用户无感。很多人只配了readiness忽略终止阶段的连接排空结果新Pod全绿、旧Pod全被摘发布期间照样报错。4. 优雅终止Pod退场时的最后几秒怎么设计4.1 默认终止流程拆解SIGTERM、宽限期、SIGKILL当你执行kubectl delete pod或滚动更新缩容旧Pod时Kubernetes会启动一个非常明确的终止流程。搞清楚这个流程的每一步才能知道该在哪里插自己的逻辑。默认流程大概是这样的API Server收到删除请求Pod进入Terminating状态。Service的Endpoints开始移除这个Pod。这个动作是异步的不是瞬间完成通常需要一两秒甚至更久。kubelet开始执行终止流程如果配置了preStop钩子先执行钩子钩子执行完或超时后给容器主进程发送SIGTERM信号。从删除请求发出开始算有一个宽限期默认30秒由terminationGracePeriodSeconds控制。如果宽限期内容器没有退出宽限期结束后kubelet发送SIGKILL强制杀掉容器。这里最关键的概念是宽限期从API Server接受删除请求那一刻就开始计时。所以如果preStop钩子要跑10秒应用收到SIGTERM后还要排空15秒总共25秒那terminationGracePeriodSeconds至少要设到30秒以上否则应用还没处理完在途请求就被SIGKILL了。容器主进程收到SIGTERM后做了什么决定了优雅终止成不成立。很多基础镜像的主进程并不是应用本身而是/bin/sh或者tini这类进程管理器。如果应用是shell脚本启动的子进程信号传递链要格外小心否则SIGTERM发给了shellshell没转发给子进程应用根本感知不到终止事件。4.2 preStop钩子的作用与边界preStop钩子和postStart一样属于生命周期钩子它的执行时机在发送SIGTERM之前。支持的两种方式exec执行命令或httpGet发起HTTP请求。实际生产里exec用得最多。preStop最常见的用途是延迟退出。为什么需要延迟因为终止流程第2步的Endpoints摘除是异步的。如果你在Endpoints还没摘干净的时候就给应用发SIGTERM应用随即关闭监听、排空连接而此时负载均衡器或客户端还在往这个Pod转发新请求就会产生连接被拒、502、超时之类的问题。所以一个经典做法是lifecycle: preStop: exec: command: - sh - -c - sleep 10这10秒的含意是给Service和Ingress留出摘除Endpoints的时间让新流量先停止进入然后再让应用优雅退出。这个数字不是固定的取决于你的集群组件从删除Pod到Endpoints摘除完全生效的平均耗时测试后确定通常5~15秒足够。preStop还可以做主动注销。比如服务注册到了注册中心在preStop里调用注册中心的API把实例下线比等上游发现实例不健康要快得多。但这种场景要注意preStop的执行时间同样算进宽限期如果注销接口下游慢可能拖到SIGKILL强行触发。preStop的边界也很明确它执行在容器内容器里的工具链决定了你能干什么。一个只有基础工具的精简镜像写复杂脚本受限时宁可把逻辑放到外部控制器里也别在sh -c里硬凹。4.3 应用侧如何配合优雅终止以Go为例Kubernetes只能负责发信号和给宽限期真正优雅不优雅取决于应用怎么处理SIGTERM。很多应用收到SIGTERM后默认直接退出在途请求当场被掐断这就是发布时一堆报错的直接原因。以Go的HTTP服务为例标准做法是用http.Server.Shutdown实现排空func main() { srv : http.Server{ Addr: :8080, } go func() { if err : srv.ListenAndServe(); err ! nil err ! http.ErrServerClosed { log.Fatalf(listen failed: %v, err) } }() quit : make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT) -quit log.Println(shutting down server...) ctx, cancel : context.WithTimeout(context.Background(), 20*time.Second) defer cancel() if err : srv.Shutdown(ctx); err ! nil { log.Printf(graceful shutdown failed: %v, err) } log.Println(server exited gracefully) }这段代码的逻辑是主协程跑HTTP服务等SIGTERM后调用Shutdown它会停止接收新连接、等待在途请求处理完最多等20秒。20秒对应的是terminationGracePeriodSeconds的一部分。Java Spring Boot类似开启server.shutdowngraceful并配好spring.lifecycle.timeout-per-shutdown-phase。Node.js则要在process.on(SIGTERM)里关闭server.close()。有一个细节值得记住信号处理要放在进程1或能被信号正确传递的进程上。如果容器入口指令是CMD [sh, -c, go run main.go]SIGTERM发给Shell后Shell可能不转发给go run优雅终止形同虚设。生产环境建议直接用应用二进制作为入口或者用tini这类init进程负责信号转发。4.4 滚动更新时优雅终止最容易被忽视的问题滚动更新是优雅终止出问题最多的场景。RollingUpdate策略下新Pod创建、变Ready旧Pod开始终止。表面上是先增后减但因为Endpoints摘除是异步的旧的这条链路上每个环节都可能出错。最常见的故障组合是这样的旧Pod收到删除请求但Endpoints摘除有延迟。旧Pod的preStop没配直接收到SIGTERM。应用不处理SIGTERM进程秒退监听端口关闭。此时Ingress/Nginx这边还有新请求按旧Endpoints转发过来连接被拒用户看到502。这个链路里preStop延迟、应用信号处理、宽限期长度三个参数必须对齐。我的建议是先把应用侧排空逻辑写好并测试出实际需要的排空时间然后preStop留出Endpoints摘除的时间最后把terminationGracePeriodSeconds设成preStop耗时应用排空耗时一点余量。另外maxUnavailable: 0和maxSurge的选择也会影响终止压力。如果一次滚动要同时杀掉多个副本且每个副本都要花二三十秒优雅退出整个发布窗口会被拉得很长。此时可以考虑增大maxSurge、或调小maxUnavailable来控制并发终止的数量让优雅终止有时间完成而不是因为发布超时被强杀。5. 一次502事故的完整排查链路三者共同失效的案例5.1 现象滚动更新期间出现502前文讲的都是理论拆解现在说一个我参与过的真实排障案例细节已做脱敏处理。某套订单服务Deployment 20个副本某天做常规镜像升级时监控里出现约3%的502错误持续了大约40秒等滚动更新结束后恢复正常。排查时所有Pod都显示Running且Ready没有任何CrashLoopBackOff看起来一切正常。502出现在滚动更新期间而且Pod状态全绿这本身就是重要线索要么流量被转发到了正在退出的Pod要么新Pod其实没准备好但readiness误报成功。5.2 逐层排查从事件、日志到Endpoints第一层先看Pod事件。kubectl get events --sort-by.metadata.creationTimestamp里能看到滚动更新期间每个旧Pod的Killing事件。时间戳显示502集中出现在旧Pod被Kill之后的几秒内。第二层看旧Pod的日志。被终止的Pod里应用日志最后一行是received signal, exiting now后面没有任何draining connections、finished requests之类的记录。这说明应用收到SIGTERM后走的是默认退出路径没有排空在途请求。第三层验证Endpoints摘除时序。用一个脚本在滚动更新期间高频查询kubectl get endpoints service -o json对比Pod删除事件和Endpoints里IP消失的时间点。实测发现旧Pod的IP从Endpoints里消失通常要1~3秒而应用在收到SIGTERM后不到1秒就关闭了监听。这个时间差就是502的来源Ingress在Endpoint还没摘掉时把新请求转发到了正在退出的Pod连接直接被拒。第四层回头看探针配置。旧Pod的readiness探针指向/healthz这个接口只在启动和运行期间返回200终止过程中不会主动返回503。也就是说终止流程开始后Pod依然显示Ready没有主动停止接收新流量。这不是探针的错误而是探针与终止逻辑缺少联动。5.3 根因与修复根因归结为三个叠加因素应用不处理SIGTERM不做连接排空在途请求被硬掐断。没有配置preStop延迟Endpoints摘除的异步窗口期里旧Pod已经关闭了监听。探针是通用的/healthz终止过程中没有主动摘除自身流量的机制。修复方案分三步走应用侧实现SIGTERM信号处理调用框架的graceful shutdown能力排空时间预算20秒。Pod侧给容器加lifecycle.preStop.exec.command内容为sleep 10给Ingress和Endpoints摘除留时间。配置侧把terminationGracePeriodSeconds从默认30秒调到45秒保证preStop 10秒 排空20秒 余量不被SIGKILL打断。另外把readiness探针的path从/healthz拆成/healthz/live和/healthz/ready两个接口liveness只做本地进程检查readiness检查依赖项。改完后重新执行一次滚动更新502消失观察新老Pod滚动期间Endpoints变化也平滑了。这个案例最有价值的地方在于三个环节单独看都没问题合在一起就出事故。Init容器决定了Pod能不能好好开始探针决定了Pod能不能好好服务优雅终止决定了Pod能不能好好结束。排障时如果只盯其中一个永远找不到答案。6. 可直接落地的Deployment配置模板与验证方法论6.1 完整配置模板把前面各章的内容整合成一份可直接参考的Deployment配置。这个模板覆盖了Init容器、三类探针、优雅终止和资源限制适合一个中等规模的Web服务apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: terminationGracePeriodSeconds: 45 initContainers: - name: wait-for-db image: busybox:1.36 command: - sh - -c - until nc -z order-db 3306; do echo wait db; sleep 2; done containers: - name: app image: registry.example.com/order-service:2.3.1 ports: - containerPort: 8080 name: http resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1 memory: 1Gi startupProbe: httpGet: path: /healthz/live port: 8080 periodSeconds: 5 failureThreshold: 30 readinessProbe: httpGet: path: /healthz/ready port: 8080 periodSeconds: 5 timeoutSeconds: 2 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /healthz/live port: 8080 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 lifecycle: preStop: exec: command: - sh - -c - sleep 10注意几个联动关系startup探针给了最多150秒30次×5秒的启动时间期间不跑livenessliveness的探测间隔10秒失败3次才会重启readiness隔离了/healthz/ready这样的依赖检查preStop的10秒和terminationGracePeriodSeconds: 45是配套的应用侧排空20秒的预算正好落在45秒窗口内。6.2 上线前必做的验证动作配置写完不要直接上生产我建议至少做以下四类验证验证Init容器逻辑用kubectl apply创建副本数1的临时Deployment观察kubectl get pod -w里Init容器状态是否从Init:0/1走到Init:1/1然后进入Running。如果Init容器卡住用kubectl logs -c wait-for-db看具体输出。验证探针阈值模拟故障比如kubectl exec进去往健康检查文件里写错误内容或者直接停掉健康检查监听观察Pod多久被摘除、多久被重启。用kubectl describe pod里的Events核对耗时是否符合参数预期。验证优雅终止手动kubectl delete pod --grace-period45同时观察应用日志里的received SIGTERM、shutting down、draining等关键节点确认在途请求能处理完且进程在宽限期内真正退出。验证滚动更新kubectl rollout restart deployment/order-service用压测工具同时打流量观察502率、滚动耗时、Pod启动和终止的节奏。重复两三轮结果稳定才算过关。6.3 我的一点经验这三块必须当成一个整体调最后说点个人体会。很多人把Init容器、探针、优雅终止当成三个独立的配置项分别抄文档、分别调参结果就是开头说的那种每块都看着正常合起来就出问题。我的经验是这几块东西本质上是一条链Init容器决定Pod能不能开始探针决定Kubernetes能不能正确认识Pod优雅终止决定Pod能不能干净退出。调参时要以一次完整的Pod生命周期为单位去审视而不是以单个字段为单位。比如调整terminationGracePeriodSeconds时要同步看preStop耗时和应用排空时间调整readiness的failureThreshold时要想想终止阶段它还能不能帮你提前摘流量。还有一个每回排障都会用的小技巧遇到Pod相关问题先别急着改配置用kubectl describe pod把整个生命周期的事件和时间线拉出来往往一眼就能看到卡点在哪。探针失败会有Unhealthy事件Init容器失败会有BackOff事件终断会有Killing事件。这些事件本身就是最诚实的日志。在生产环境里Pod生命周期管理从来不是配了就行而是要反复验证、持续打磨的。希望这篇文章能帮你少踩几个我当年踩过的坑。