<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Reliability · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/reliability/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/reliability/index.xml" rel="self" type="application/rss+xml"/><item><title>重启策略需要一个速率限制</title><link>https://hk.xbcatm.com/zh/notes/restart-policies-need-a-rate-limit/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/restart-policies-need-a-rate-limit/</guid><description>让 systemd 恢复瞬时故障，同时不掩盖永久性的配置错误。</description><content:encoded>&lt;p&gt;对长驻服务来说，自动重启很有用，但无界的循环会把一个清晰的失败变成一个嘈杂的失败。单元文件应同时描述正常进程生命周期与可接受的恢复量。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/restart-loop.detail.zh.svg" alt="程序执行、应用就绪和响应健康是三项独立检查。图中示例配置允许 60 秒内启动五次，快速失败后延迟五秒。再次尝试达到限制时，自动重启停止。较慢失败可能始终低于速率限制。单纯等到间隔结束不会排定新尝试；修复原因后应有意识地启动，或先清除计数器再启动。保存第一次失败日志，并核验就绪状态和健康响应。" width="720" height="3410"&gt;&lt;figcaption&gt;按配置窗口统计启动次数。启动速率限制可以停止快速反复重启，但不能证明健康，也不限制生命周期重试总数。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="捕获启动失败"&gt;捕获启动失败&lt;/h2&gt;
&lt;p&gt;对长驻进程，&lt;code&gt;Type=exec&lt;/code&gt; 能在启动时暴露配置程序执行失败，而不是把一次 fork 当成功。把它与显式的 &lt;code&gt;ExecStart&lt;/code&gt; 以及能完成工作所需最小权限的 &lt;code&gt;User&lt;/code&gt; 搭配使用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Example foreground site service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;StartLimitIntervalSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;60s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;StartLimitBurst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/srv/site/bin/server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Type=exec&lt;/code&gt; 确认程序已执行，并不确认应用已就绪。程序应保持前台运行；应用支持显式就绪通知时可用 &lt;code&gt;Type=notify&lt;/code&gt;。&lt;code&gt;Restart=on-failure&lt;/code&gt; 是恢复策略，不是健康检查。如果进程在提供错误响应的同时保持存活，systemd 不会知道站点坏了；外部冒烟测试仍有其职责。&lt;/p&gt;
&lt;h2 id="限制循环"&gt;限制循环&lt;/h2&gt;
&lt;p&gt;Systemd 通过 &lt;code&gt;StartLimitIntervalSec=&lt;/code&gt; 与 &lt;code&gt;StartLimitBurst=&lt;/code&gt; 应用启动速率限制。这两个指令放在 &lt;code&gt;[Unit]&lt;/code&gt;，&lt;code&gt;RestartSec&lt;/code&gt; 放在 &lt;code&gt;[Service]&lt;/code&gt;。示例允许 60 秒内启动五次；快速失败之间只有五秒重启延迟，因此可能达到限制。更长延迟可能让循环一直低于该速率：这并不是整个生命周期的重试次数预算。应按预期故障模式选择限制。当达到限制时，日志里应包含区分&amp;quot;重启成功&amp;quot;与&amp;quot;重复失败后放弃&amp;quot;所需的证据。&lt;/p&gt;
&lt;p&gt;在获准使用的测试主机上安装或修改单元后，先验证文件、重载管理器并检查有效配置，再重启。下列命令会改变服务状态，可能中断流量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemd-analyze verify /etc/systemd/system/example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl cat example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl show example.service -p Restart -p RestartUSec &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -p StartLimitIntervalUSec -p StartLimitBurst
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl restart example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status --no-pager example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl -u example.service -n &lt;span class="m"&gt;80&lt;/span&gt; --no-pager
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不要用重启策略掩盖缺失文件、无效权限或糟糕的环境。先修复第一次失败，再测试恢复路径。&lt;/p&gt;
&lt;p&gt;启动限制停止自动重启后，单纯等到间隔结束不会自动排定下一次尝试。修复原因后，可等待间隔结束再启动，或有意识地用 &lt;code&gt;systemctl reset-failed example.service&lt;/code&gt; 清除计数器后启动。在隔离服务中测试可执行文件缺失与快速崩溃，并保存第一次失败日志，不要只留下最后的“达到启动限制”消息。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/systemd.service.5.html"&gt;systemd.service&lt;/a&gt;，服务类型、重启与启动速率限制器，已核验 2026-08-03。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/systemd.unit.5.html"&gt;systemd.unit&lt;/a&gt;，通用单元段与 drop-in 语义，已核验 2026-08-03。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/systemd/systemd/v257/man/systemd.service.xml"&gt;systemd.service upstream manual (v257)&lt;/a&gt;，Type=exec/oneshot、就绪、激活超时与重启语义，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/systemd/systemd/v257/man/systemd.unit.xml"&gt;systemd.unit upstream manual (v257)&lt;/a&gt;，启动限制所属配置段、速率窗口与 reset-failed 行为，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>可靠的默认行为胜过聪明的恢复</title><link>https://hk.xbcatm.com/zh/notes/reliable-defaults-beat-clever-recovery/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/notes/reliable-defaults-beat-clever-recovery/</guid><description>一条可预测的首要路径比复杂的兜底更能预防事故。</description><content:encoded>&lt;p&gt;小服务往往在正常路径被充分理解之前，就先累积了恢复逻辑。每一次重试、备用端点与兼容分支，都增加了必须被观察和测试的一个状态。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/reliable-path.detail.zh.svg" alt="常见路径使用固定工具构建，检查完整目录，发布并验证公共响应，保留上一版目录。配置、运行时与文档一致指向一个选中发布。故障后先判断是否瞬时且可安全重复，仅符合条件的操作可使用次数与耗时预算重试，否则停止并修正。权限错误不进入重试；响应丢失时首次操作可能已成功。恢复显式回到保留的已知良好目录并记录结果。" width="720" height="3620"&gt;&lt;figcaption&gt;让常见路径可见。先判断并限制重试，再恢复到保留的已知良好状态，避免选择半成品副本。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="让常见路径变得无聊"&gt;让常见路径变得无聊&lt;/h2&gt;
&lt;p&gt;一个有用的默认行为是明确的、易于检视的、且可安全重复的。对静态站点，可以简单到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建自固定的工具链。&lt;/li&gt;
&lt;li&gt;检查生成的 HTML、资源与完整的待发布目录。&lt;/li&gt;
&lt;li&gt;发布该目录，再把公共响应与它核对。&lt;/li&gt;
&lt;li&gt;保留上一个目录以便回滚。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这段序列不如多阶段部署系统炫目，但它的状态可见。当某处失败时，运维者能回答：问题在源码、构建、复制还是 Web 服务器。&lt;/p&gt;
&lt;h2 id="只对可限定范围的操作重试"&gt;只对可限定范围的操作重试&lt;/h2&gt;
&lt;p&gt;当故障可能是瞬时的、且重复操作不会改变其预期效果时，重试才有用。幂等不等于每次响应或附带日志都相同；响应丢失后，第一次尝试可能早已成功。从源站下载可能可安全重试；数据库迁移则未必。把每个错误都看作可重试的，会掩盖需要关注的边界。&lt;/p&gt;
&lt;p&gt;当重试适当时，记录尝试次数与最终原因。类似&amp;quot;发布失败：校验和不匹配&amp;quot;的日志条目，比一串静默重试后跟一个笼统超时更有可操作性。&lt;/p&gt;
&lt;h2 id="偏好单一权威状态"&gt;偏好单一权威状态&lt;/h2&gt;
&lt;p&gt;配置、运行时状态与文档应一致地说明哪个目录是活动的、哪条命令产生它。如果一个发布可以被不同的人复制到三条不同路径，恢复就变成了猜谜。&lt;/p&gt;
&lt;p&gt;对静态部署，一份简短的发布记录就够了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;release: 2026-07-20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;source: git commit &amp;lt;commit-id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;builder: Hugo 0.165.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;output: public/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;verification: links + headers + checksums
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;占位符 commit ID 应按设计在发布时解析，而不是在文档里杜撰。&lt;/p&gt;
&lt;h2 id="恢复应回到已知状态"&gt;恢复应回到已知状态&lt;/h2&gt;
&lt;p&gt;当恢复把服务带回最后一个已知良好目录，而不是在几个半成品替代品之间选择时，恢复最容易。保留上一次输出直到新输出通过检查，再按保留策略移除旧副本。&lt;/p&gt;
&lt;p&gt;目标不是消除失败，而是把失败缩小到能解释、把恢复缩小到能信任。&lt;/p&gt;
&lt;p&gt;同时限制重试次数与总耗时。多个客户端可能一起重试时，使用带随机抖动的退避，并避免每一层都重试。权限错误与确定性的无效输入需要修正，而不是更长的重试循环。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，9.2.2 节关于幂等方法与重试，已核验 2026-07-20。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/zh/resources/"&gt;静态网站发布检查清单&lt;/a&gt;，本地配套资源。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/"&gt;AWS Builders’ Library: Timeouts, retries, and backoff with jitter&lt;/a&gt;，重试放大、有界重试预算、退避与随机抖动，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>