<?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>Supervisor on Tarragon</title><link>https://tarrragon.github.io/blog/tags/supervisor/</link><description>Recent content in Supervisor on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/supervisor/index.xml" rel="self" type="application/rss+xml"/><item><title>Process supervisor 選型</title><link>https://tarrragon.github.io/blog/devops/04-service-health/process-supervisor-selection/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/04-service-health/process-supervisor-selection/</guid><description>&lt;p>選 process supervisor 的判準是這個平台能不能分別表達服務生命週期的四個階段：啟動（startup）、就緒（readiness）、存活（liveness）、收束（drain）。表達力越完整，越能讓平台在對的時機做對的動作；表達力有缺，缺的那部分邏輯就要在應用層自己補，複雜度從平台設定轉移到程式碼裡。選型不是比誰功能多，是比這個服務需要的生命週期粒度，跟平台能表達的粒度對不對得上。&lt;/p>
&lt;p>在動手比較之前，先問服務四個問題：啟動要多久、哪些依賴是就緒條件；失敗時該自己恢復還是交平台重建；停止時有哪些在途請求、連線、背景工作要收束；以及平台能不能把 startup、readiness、liveness、drain 分開表達。這四個問題的答案決定了要往哪個方向選。&lt;/p>
&lt;h2 id="各平台的生命週期表達力">各平台的生命週期表達力&lt;/h2>
&lt;p>各平台對這四階段的支援程度不同，這張對照是選型的骨架：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>平台&lt;/th>
 &lt;th>啟動 gate&lt;/th>
 &lt;th>就緒與存活&lt;/th>
 &lt;th>收束&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>systemd&lt;/td>
 &lt;td>無原生 startup gate&lt;/td>
 &lt;td>&lt;code>sd_notify(READY=1)&lt;/code> 宣告就緒&lt;/td>
 &lt;td>&lt;code>ExecStop&lt;/code> + &lt;code>KillSignal&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>supervisord&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>只有 RUNNING 狀態、不分離就緒與存活&lt;/td>
 &lt;td>&lt;code>stopsignal&lt;/code> + &lt;code>stopwaitsecs&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Docker&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>&lt;code>HEALTHCHECK&lt;/code> 不分離就緒與存活&lt;/td>
 &lt;td>&lt;code>stop_grace_period&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Kubernetes&lt;/td>
 &lt;td>&lt;code>startupProbe&lt;/code>&lt;/td>
 &lt;td>readiness 與 liveness 獨立探針&lt;/td>
 &lt;td>&lt;code>preStop&lt;/code> hook + endpoint 摘除&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ECS&lt;/td>
 &lt;td>startup health check&lt;/td>
 &lt;td>依 health check 設定&lt;/td>
 &lt;td>deregistration delay&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Kubernetes 的表達力最完整——三種探針獨立、收束有 preStop hook 加 endpoint 摘除，能精確表達每個階段。代價是參數最多、也最容易配錯：探針門檻、間隔、grace period 任何一個設歪，行為就跟預期不符。systemd 在單機場景反而直接，&lt;code>sd_notify&lt;/code> 讓服務主動宣告狀態，不必外部反覆探測，但它沒有原生的 startup gate 概念，啟動期的健康要自己用就緒宣告的時機表達。&lt;/p>
&lt;p>supervisord 是單機上的經典應用監管者，比 systemd 更輕、跨發行版一致，適合不想綁 systemd 的環境；限制是它只有一個 RUNNING 狀態，不像 systemd 有 &lt;code>sd_notify&lt;/code> 可以宣告就緒，也就沒辦法區分就緒與存活。這個「不分離就緒與存活」的限制在 Docker 跟 ECS 上一樣存在——&lt;code>HEALTHCHECK&lt;/code> 只有一個健康概念，無法同時回答「可以接流量嗎」跟「還活著嗎」。服務若真的需要把這兩者分開（例如依賴斷線時要摘流量但不要重啟），這段差距就得在應用層補：自己維護就緒狀態、自己在健康端點裡分辨這次探測該回答哪個問題。這一段邏輯搬進程式碼是可行的，代價是本來平台該表達的職責變成應用自己扛。&lt;/p>
&lt;h2 id="restart-policy-是恢復動作的表達">Restart policy 是恢復動作的表達&lt;/h2>
&lt;p>除了生命週期階段，各平台對「進程退出後怎麼辦」也有各自的表達。Docker 的 restart policy 有 &lt;code>no&lt;/code>（不重啟）、&lt;code>on-failure&lt;/code>（非零退出才重啟，可設次數上限）、&lt;code>always&lt;/code>（永遠重啟，含手動停止後 daemon 重啟也拉起）、&lt;code>unless-stopped&lt;/code>（類似 always 但尊重手動停止）。Kubernetes 的 Pod &lt;code>restartPolicy&lt;/code> 有 &lt;code>Always&lt;/code>、&lt;code>OnFailure&lt;/code>、&lt;code>Never&lt;/code>，語意對應到 Pod 層的容器重啟。&lt;/p>
&lt;p>這些選項對應的決策跟 systemd 的 &lt;code>Restart=on-failure&lt;/code> 是同一件事：這個服務退出時，是該無條件拉回、只在異常時拉回、還是不動它交給更上層處理。選 &lt;code>always&lt;/code> 類的策略要搭配重試上限或退避，否則一個永遠起不來的服務會陷入無限重啟迴圈——這條跟 systemd 的 &lt;code>StartLimitBurst&lt;/code> 是同一個問題，&lt;a href="https://tarrragon.github.io/blog/devops/04-service-health/systemd-watchdog-restart/" data-link-title="systemd watchdog 與自動重啟" data-link-desc="在單機 systemd 上做服務自動恢復時，釐清 watchdog 主動報活與 restart policy 被動拉起是兩套機制、以及為什麼要先重啟幾次才放棄">systemd watchdog 與自動重啟&lt;/a> 有單機上的完整設定。&lt;/p>
&lt;h2 id="容器裡的-pid-1-是另一層選型">容器裡的 PID 1 是另一層選型&lt;/h2>
&lt;p>跑在容器裡時，還有一個容易漏掉的選型：誰當 PID 1。容器的 PID 1 是 init process，除了跑服務，還負責接收 &lt;code>SIGTERM&lt;/code>／&lt;code>SIGINT&lt;/code> 並轉發給子進程、以及回收結束的子進程（zombie reaping）。這個責任交給誰，直接影響服務收不收得到關閉信號、以及會不會累積殭屍進程。&lt;/p>
&lt;p>解法看容器裡跑幾個進程，兩種修法對應兩種情況、不是互斥的競爭方案。單一主進程的情況，用 exec form（或啟動腳本裡 &lt;code>exec&lt;/code>）讓服務直接取代 shell 當 PID 1、自己接手信號就夠。多進程容器還多一個問題：若 PID 1 不做 &lt;code>wait()&lt;/code>，結束的子進程會變殭屍累積，這時要用 tini 或 dumb-init 這類輕量 init 當 PID 1，由它負責信號轉發跟殭屍回收，或在 Kubernetes 設 &lt;code>shareProcessNamespace&lt;/code> 讓 kubelet 接手。一句話分工：exec form 解單進程的信號傳遞，tini／dumb-init 解多進程的信號傳遞加殭屍回收。信號傳不到服務造成的關閉失敗，是 &lt;a href="https://tarrragon.github.io/blog/devops/04-service-health/graceful-shutdown/" data-link-title="Graceful shutdown" data-link-desc="設計服務收到停止信號後的收束流程時，釐清 SIGTERM 到 SIGKILL 的 grace period、退場的固定順序、以及不同 workload 的 drain 窗口要留多長">graceful shutdown&lt;/a> 章最常見的失效模式，這裡是它的選型根因。&lt;/p>
&lt;h2 id="選型收斂">選型收斂&lt;/h2>
&lt;p>單機、服務自己寫得動、要零額外依賴且需要區分就緒與存活 → systemd，用 &lt;code>sd_notify&lt;/code> 宣告就緒與報活。單機但不想綁 systemd、只要基本的拉起與重啟 → supervisord。多機、需要 startup、readiness、liveness、drain 全部分開表達、能吃下配置複雜度 → Kubernetes。容器化但生命週期需求簡單、不需要分離就緒與存活 → Docker restart policy 加 &lt;code>HEALTHCHECK&lt;/code>，不足的部分在應用層補。判準始終是同一條：服務需要的生命週期粒度，跟平台能表達的粒度對不對得上——需求簡單卻上最複雜的平台，付的是配置成本；需求複雜卻用表達力不足的平台，付的是應用層補洞的成本。&lt;/p></description><content:encoded><![CDATA[<p>選 process supervisor 的判準是這個平台能不能分別表達服務生命週期的四個階段：啟動（startup）、就緒（readiness）、存活（liveness）、收束（drain）。表達力越完整，越能讓平台在對的時機做對的動作；表達力有缺，缺的那部分邏輯就要在應用層自己補，複雜度從平台設定轉移到程式碼裡。選型不是比誰功能多，是比這個服務需要的生命週期粒度，跟平台能表達的粒度對不對得上。</p>
<p>在動手比較之前，先問服務四個問題：啟動要多久、哪些依賴是就緒條件；失敗時該自己恢復還是交平台重建；停止時有哪些在途請求、連線、背景工作要收束；以及平台能不能把 startup、readiness、liveness、drain 分開表達。這四個問題的答案決定了要往哪個方向選。</p>
<h2 id="各平台的生命週期表達力">各平台的生命週期表達力</h2>
<p>各平台對這四階段的支援程度不同，這張對照是選型的骨架：</p>
<table>
  <thead>
      <tr>
          <th>平台</th>
          <th>啟動 gate</th>
          <th>就緒與存活</th>
          <th>收束</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>systemd</td>
          <td>無原生 startup gate</td>
          <td><code>sd_notify(READY=1)</code> 宣告就緒</td>
          <td><code>ExecStop</code> + <code>KillSignal</code></td>
      </tr>
      <tr>
          <td>supervisord</td>
          <td>無</td>
          <td>只有 RUNNING 狀態、不分離就緒與存活</td>
          <td><code>stopsignal</code> + <code>stopwaitsecs</code></td>
      </tr>
      <tr>
          <td>Docker</td>
          <td>無</td>
          <td><code>HEALTHCHECK</code> 不分離就緒與存活</td>
          <td><code>stop_grace_period</code></td>
      </tr>
      <tr>
          <td>Kubernetes</td>
          <td><code>startupProbe</code></td>
          <td>readiness 與 liveness 獨立探針</td>
          <td><code>preStop</code> hook + endpoint 摘除</td>
      </tr>
      <tr>
          <td>ECS</td>
          <td>startup health check</td>
          <td>依 health check 設定</td>
          <td>deregistration delay</td>
      </tr>
  </tbody>
</table>
<p>Kubernetes 的表達力最完整——三種探針獨立、收束有 preStop hook 加 endpoint 摘除，能精確表達每個階段。代價是參數最多、也最容易配錯：探針門檻、間隔、grace period 任何一個設歪，行為就跟預期不符。systemd 在單機場景反而直接，<code>sd_notify</code> 讓服務主動宣告狀態，不必外部反覆探測，但它沒有原生的 startup gate 概念，啟動期的健康要自己用就緒宣告的時機表達。</p>
<p>supervisord 是單機上的經典應用監管者，比 systemd 更輕、跨發行版一致，適合不想綁 systemd 的環境；限制是它只有一個 RUNNING 狀態，不像 systemd 有 <code>sd_notify</code> 可以宣告就緒，也就沒辦法區分就緒與存活。這個「不分離就緒與存活」的限制在 Docker 跟 ECS 上一樣存在——<code>HEALTHCHECK</code> 只有一個健康概念，無法同時回答「可以接流量嗎」跟「還活著嗎」。服務若真的需要把這兩者分開（例如依賴斷線時要摘流量但不要重啟），這段差距就得在應用層補：自己維護就緒狀態、自己在健康端點裡分辨這次探測該回答哪個問題。這一段邏輯搬進程式碼是可行的，代價是本來平台該表達的職責變成應用自己扛。</p>
<h2 id="restart-policy-是恢復動作的表達">Restart policy 是恢復動作的表達</h2>
<p>除了生命週期階段，各平台對「進程退出後怎麼辦」也有各自的表達。Docker 的 restart policy 有 <code>no</code>（不重啟）、<code>on-failure</code>（非零退出才重啟，可設次數上限）、<code>always</code>（永遠重啟，含手動停止後 daemon 重啟也拉起）、<code>unless-stopped</code>（類似 always 但尊重手動停止）。Kubernetes 的 Pod <code>restartPolicy</code> 有 <code>Always</code>、<code>OnFailure</code>、<code>Never</code>，語意對應到 Pod 層的容器重啟。</p>
<p>這些選項對應的決策跟 systemd 的 <code>Restart=on-failure</code> 是同一件事：這個服務退出時，是該無條件拉回、只在異常時拉回、還是不動它交給更上層處理。選 <code>always</code> 類的策略要搭配重試上限或退避，否則一個永遠起不來的服務會陷入無限重啟迴圈——這條跟 systemd 的 <code>StartLimitBurst</code> 是同一個問題，<a href="/blog/devops/04-service-health/systemd-watchdog-restart/" data-link-title="systemd watchdog 與自動重啟" data-link-desc="在單機 systemd 上做服務自動恢復時，釐清 watchdog 主動報活與 restart policy 被動拉起是兩套機制、以及為什麼要先重啟幾次才放棄">systemd watchdog 與自動重啟</a> 有單機上的完整設定。</p>
<h2 id="容器裡的-pid-1-是另一層選型">容器裡的 PID 1 是另一層選型</h2>
<p>跑在容器裡時，還有一個容易漏掉的選型：誰當 PID 1。容器的 PID 1 是 init process，除了跑服務，還負責接收 <code>SIGTERM</code>／<code>SIGINT</code> 並轉發給子進程、以及回收結束的子進程（zombie reaping）。這個責任交給誰，直接影響服務收不收得到關閉信號、以及會不會累積殭屍進程。</p>
<p>解法看容器裡跑幾個進程，兩種修法對應兩種情況、不是互斥的競爭方案。單一主進程的情況，用 exec form（或啟動腳本裡 <code>exec</code>）讓服務直接取代 shell 當 PID 1、自己接手信號就夠。多進程容器還多一個問題：若 PID 1 不做 <code>wait()</code>，結束的子進程會變殭屍累積，這時要用 tini 或 dumb-init 這類輕量 init 當 PID 1，由它負責信號轉發跟殭屍回收，或在 Kubernetes 設 <code>shareProcessNamespace</code> 讓 kubelet 接手。一句話分工：exec form 解單進程的信號傳遞，tini／dumb-init 解多進程的信號傳遞加殭屍回收。信號傳不到服務造成的關閉失敗，是 <a href="/blog/devops/04-service-health/graceful-shutdown/" data-link-title="Graceful shutdown" data-link-desc="設計服務收到停止信號後的收束流程時，釐清 SIGTERM 到 SIGKILL 的 grace period、退場的固定順序、以及不同 workload 的 drain 窗口要留多長">graceful shutdown</a> 章最常見的失效模式，這裡是它的選型根因。</p>
<h2 id="選型收斂">選型收斂</h2>
<p>單機、服務自己寫得動、要零額外依賴且需要區分就緒與存活 → systemd，用 <code>sd_notify</code> 宣告就緒與報活。單機但不想綁 systemd、只要基本的拉起與重啟 → supervisord。多機、需要 startup、readiness、liveness、drain 全部分開表達、能吃下配置複雜度 → Kubernetes。容器化但生命週期需求簡單、不需要分離就緒與存活 → Docker restart policy 加 <code>HEALTHCHECK</code>，不足的部分在應用層補。判準始終是同一條：服務需要的生命週期粒度，跟平台能表達的粒度對不對得上——需求簡單卻上最複雜的平台，付的是配置成本；需求複雜卻用表達力不足的平台，付的是應用層補洞的成本。</p>
<h2 id="要不要上-kubernetes">要不要上 Kubernetes</h2>
<p>「要不要引入編排層」是這個選型裡最大的一個決策，值得單獨判。上 Kubernetes 的成本是配置複雜度與一整套維運（叢集升級、網路、儲存、權限），這筆成本是固定的、不隨服務數量攤薄到很小。值得付的訊號是三個同時成立：跑在多台機器上、需要 startup/readiness/liveness/drain 全部分開精確表達、而且有多個服務要統一調度與擴縮。這三個都成立時，Kubernetes 把「本來要自己拼的調度、健康、擴縮」收進一個平台，複雜度換到了值得的地方。</p>
<p>反過來，單機或少數幾台、生命週期需求簡單、服務數量不多時，上 Kubernetes 是拿一大筆配置與維運複雜度、換一套用不到的能力——systemd 或 Docker 加 restart policy 就足夠，省下的複雜度是實打實的。常見的誤區是把 Kubernetes 當成「正規」的預設起點，結果一個兩台機器的服務背上了整個叢集的維運負擔。判準回到同一條：需求的粒度配不配得上平台的粒度，不是「業界都用所以我也要用」。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>平台要表達的 startup、readiness、liveness 各是什麼語意 → <a href="/blog/devops/04-service-health/liveness-vs-readiness/" data-link-title="Liveness 與 Readiness" data-link-desc="分不清該用哪種探針、或探針失敗後平台重啟了不該重啟的服務時，回來釐清 liveness、readiness、startup 三種探針各自宣告什麼、失敗後平台做什麼">Liveness 與 Readiness</a></li>
<li>systemd 上 restart policy 與 watchdog 的完整設定 → <a href="/blog/devops/04-service-health/systemd-watchdog-restart/" data-link-title="systemd watchdog 與自動重啟" data-link-desc="在單機 systemd 上做服務自動恢復時，釐清 watchdog 主動報活與 restart policy 被動拉起是兩套機制、以及為什麼要先重啟幾次才放棄">systemd watchdog 與自動重啟</a></li>
<li>收束階段的信號傳遞與 grace period 設計 → <a href="/blog/devops/04-service-health/graceful-shutdown/" data-link-title="Graceful shutdown" data-link-desc="設計服務收到停止信號後的收束流程時，釐清 SIGTERM 到 SIGKILL 的 grace period、退場的固定順序、以及不同 workload 的 drain 窗口要留多長">Graceful shutdown</a></li>
<li>部署平台的完整生命週期契約 → <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">Backend 部署平台生命週期契約</a></li>
</ul>
]]></content:encoded></item></channel></rss>