<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>CrashLoopBackOff on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/crashloopbackoff/</link>
        <description>Recent content in CrashLoopBackOff on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sat, 11 Jul 2026 07:00:00 +0000</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/crashloopbackoff/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>记一次 Kubernetes 因 liveness 探针配置过严导致 Pod 陷入 CrashLoopBackOff 的排查</title>
            <link>https://blog.5772447.xyz/posts/21e8a43e/</link>
            <pubDate>Sat, 11 Jul 2026 07:00:00 +0000</pubDate>
            <guid>https://blog.5772447.xyz/posts/21e8a43e/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;业务核心交易服务完成容器化改造，整体迁移到自建 Kubernetes 集群（v1.24，kubelet 运行时 containerd）。某次版本发布后，运维在发布系统里看到该服务的 Deployment 长时间处于 &lt;code&gt;Progressing&lt;/code&gt; 状态，副本数始终为 0/3。发布流水线反复重试仍无法拉起实例，上游网关已开始零星返回 502。由于当天还有其它变更在并行进行，最初的怀疑点被分散到了网络策略、镜像仓库和节点资源上，排查一度走偏。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;kubectl get pod -n trade&lt;/code&gt; 显示 3 个 Pod 全部处于 &lt;code&gt;CrashLoopBackOff&lt;/code&gt; 状态，&lt;code&gt;RESTARTS&lt;/code&gt; 列以肉眼可见的速度从 3、5、8 一路上涨，最短的十几秒就被重启一次。&lt;code&gt;kubectl describe pod&lt;/code&gt; 的 Events 里反复出现：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Liveness probe failed: HTTP probe failed with statusCode: 503&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Back-off restarting failed container&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Pod 的 Conditions 中 &lt;code&gt;Ready&lt;/code&gt; 一直是 &lt;code&gt;False&lt;/code&gt;。有意思的是，用 &lt;code&gt;kubectl logs&lt;/code&gt; 看容器标准输出，发现应用进程其实在跑、日志里还在打印 &lt;code&gt;loading config / connecting to mysql&lt;/code&gt; 这类启动日志，并没有真正崩溃退出——它只是还没&amp;quot;活&amp;quot;过来就被杀掉了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;第一步，区分&amp;quot;进程崩溃&amp;quot;和&amp;quot;被探针杀掉&amp;quot;。因为 &lt;code&gt;RESTARTS&lt;/code&gt; 持续上涨但日志显示进程停在启动中途，基本可以排除 OOMKilled（OOM 会有 &lt;code&gt;OOMKilled&lt;/code&gt; 事件且通常不是稳定周期）。&lt;code&gt;kubectl describe&lt;/code&gt; 明确指向 &lt;code&gt;Liveness probe failed&lt;/code&gt;，方向锁定在探针。&lt;/p&gt;&#xA;&lt;p&gt;第二步，看探针配置。该 Deployment 的容器定义了 livenessProbe：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;7&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;livenessProbe&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;httpGet&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;path&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/healthz&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;port&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;initialDelaySeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;periodSeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;failureThreshold&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;第三步，理解 &lt;code&gt;/healthz&lt;/code&gt; 的语义。这个端点并不是&amp;quot;进程还活着&amp;quot;的简单探测，而是开发在代码里把它实现成了&amp;quot;依赖全部就绪才算健康&amp;quot;——它内部会去连 MySQL、Redis 和配置中心 Nacos，任何一项没连上就返回 503。也就是说，&lt;code&gt;/healthz&lt;/code&gt; 实际上是一个 readiness 语义的端点被错误地用作了 liveness。&lt;/p&gt;&#xA;&lt;p&gt;第四步，核对应用真实启动耗时。应用是 Spring Boot 单体，冷启动到连上全部中间件、&lt;code&gt;/healthz&lt;/code&gt; 返回 200，平均需要 90 秒左右；而首次发布那天因为配置中心刚扩容、连接更慢，实测接近 120 秒。探针在容器启动后仅 10 秒就开始探活，连续 3 次（30 秒）失败就触发 kubelet 发 SIGTERM 杀容器。应用永远停在被 kill 在启动到一半的状态，下一轮重启又从 0 开始，陷入死循环。&lt;/p&gt;&#xA;&lt;p&gt;第五步，确认没有 readinessProbe。该容器只配了 liveness，没有 readinessProbe。即使某次侥幸启动完成，在 &lt;code&gt;/healthz&lt;/code&gt; 真正就绪前流量也会被 Service 转发进去，加剧不稳定。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;纠正探针语义：livenessProbe 改成只探&amp;quot;进程是否还活着、能否自救&amp;quot;，使用极轻量的端点（如独立的 &lt;code&gt;/health&lt;/code&gt;，仅返回 200，不依赖任何外部依赖），并把 &lt;code&gt;initialDelaySeconds&lt;/code&gt; 提高到 60，&lt;code&gt;failureThreshold&lt;/code&gt; 提到 5，给足启动宽容度。&lt;/li&gt;&#xA;&lt;li&gt;补上 readinessProbe：使用 &lt;code&gt;/healthz&lt;/code&gt;（依赖就绪才返回 200），&lt;code&gt;periodSeconds&lt;/code&gt; 10、&lt;code&gt;failureThreshold&lt;/code&gt; 3，确保只有真正就绪才接流量。&lt;/li&gt;&#xA;&lt;li&gt;引入 startupProbe 兜底慢启动：K8s 1.16+ 支持 startupProbe，在它探测成功之前，liveness 和 readiness 都不会生效。配置 &lt;code&gt;startupProbe&lt;/code&gt; &lt;code&gt;httpGet /healthz&lt;/code&gt;，&lt;code&gt;failureThreshold&lt;/code&gt; 30、&lt;code&gt;periodSeconds&lt;/code&gt; 5，相当于给应用最多 150 秒的启动窗口，期间不会被 liveness 误杀。&lt;/li&gt;&#xA;&lt;li&gt;开发侧拆分端点：明确 &lt;code&gt;/health&lt;/code&gt;（存活）、&lt;code&gt;/ready&lt;/code&gt;（就绪）两个语义不同的端点，避免把&amp;quot;就绪&amp;quot;逻辑塞进&amp;quot;存活&amp;quot;端点。&lt;/li&gt;&#xA;&lt;li&gt;滚动更新策略配合：把 &lt;code&gt;maxSurge&lt;/code&gt;/&lt;code&gt;maxUnavailable&lt;/code&gt; 调成 &lt;code&gt;1/0&lt;/code&gt;，避免新 Pod 没起来就把旧 Pod 摘掉造成容量空窗。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;改完重新发布，Pod 在约 100 秒后进入 &lt;code&gt;Running&lt;/code&gt;/&lt;code&gt;Ready&lt;/code&gt;，&lt;code&gt;RESTARTS&lt;/code&gt; 不再上涨，服务恢复。&lt;/p&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;根因是&lt;strong&gt;探针语义错位 + 初始延迟远小于真实启动时间&lt;/strong&gt;。livenessProbe 的职责是&amp;quot;发现进程已死且无法自愈时重启它&amp;quot;，应该用最宽松的存活判断；而本例把&amp;quot;依赖全部就绪&amp;quot;这种 readiness 语义放进了 liveness，又只给 10 秒 &lt;code&gt;initialDelay&lt;/code&gt;，导致应用在依赖就绪前就被反复处决。本质是把&amp;quot;是否准备好接流量&amp;quot;和&amp;quot;进程是否还活着&amp;quot;两件事混为一谈。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;给所有对外提供服务的容器同时配置 liveness / readiness / startup 三件套，语义严格区分。&lt;/li&gt;&#xA;&lt;li&gt;liveness 端点保持&amp;quot;零依赖&amp;quot;，只证明进程在跑；依赖就绪交给 readiness。&lt;/li&gt;&#xA;&lt;li&gt;启动慢的应用（连多数据源、拉配置中心）一律用 startupProbe 兜启动窗口，而不是无脑调大 &lt;code&gt;initialDelay&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;把探针配置纳入上线 review 清单和 Chart 模板默认值，避免每个团队各写各的。&lt;/li&gt;&#xA;&lt;li&gt;在监控里对 Pod 的 &lt;code&gt;RESTARTS&lt;/code&gt; 速率、&lt;code&gt;CrashLoopBackOff&lt;/code&gt; 状态做告警，比等用户投诉早发现。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;CrashLoopBackOff 不一定意味着代码崩了，很多时候是探针在&amp;quot;帮倒忙&amp;quot;。liveness 要宽松、readiness 管流量、startup 管慢启动，三者各司其职，容器才稳。把&amp;quot;存活&amp;quot;和&amp;quot;就绪&amp;quot;混为一谈，是容器化迁移里最高频、也最隐蔽的坑之一。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
