本文 AI 產出,尚未審核

FastAPI 部署與架構:使用 systemd、PM2 與 Supervisor 管理服務


簡介

在開發完一套可供外部使用的 FastAPI 應用後,最關鍵的步驟往往是「如何讓它穩定、持續地跑在伺服器上」。
若僅靠 uvicorn app:app 手動啟動,雖然開發階段足夠,但在正式環境中,一旦服務意外關閉、系統重啟或需要滾動升級,手動操作將變得不可接受。

本篇文章聚焦於 三種常見的服務管理工具

  1. systemd – Linux 系統原生的 init 系統與服務管理器,適合在大多數雲端 VM、裸機或容器中使用。
  2. PM2 – 以 Node.js 生態為背景的進程管理器,支援跨平台、零停機重啟與日誌聚合,對於熟悉 JavaScript 的團隊相當友好。
  3. Supervisor – Python 社群常用的進程監控工具,設定簡潔且支援遠端 XML‑RPC 控制。

了解這三者的差異與實作方式,能讓你在 開發、測試、上線 的每個階段,都能選擇最合適的管理方式,提升服務的 可靠性、可觀察性維運效率


核心概念

1. 為什麼需要進程管理器?

需求 若不使用管理器的結果 使用管理器的好處
自動重啟 服務崩潰後必須手動重啟,造成 downtime 當程式因未捕獲例外退出時,管理器自動重新啟動
開機自動啟動 重新開機後服務不會自動跑起來 systemd、Supervisor、PM2 都支援開機自動啟動
日誌集中 只能靠 stdout 或手動 redirect,難以追蹤 管理器提供標準化的日誌檔案、輪替與查詢指令
資源限制 需要自行編寫腳本限制 CPU/Memory systemd 可直接在 unit file 中設定 MemoryLimitCPUQuota
零停機部署 必須停止舊服務再啟動新版本,期間不可用 PM2 的 reload、systemd 的 socket activation 等機制支援平滑升級

2. systemd 基本概念與設定

2.1 Unit File 結構

systemd 以 unit file 來描述服務。常見的 [Service] 欄位包括:

  • ExecStart:啟動指令
  • ExecReload:重新載入指令(支援零停機升級)
  • Restart:崩潰後的重啟策略(on-failurealways 等)
  • UserGroup:執行的使用者/群組
  • Environment:環境變數

2.2 範例:將 FastAPI 以 systemd 管理

假設你的專案結構如下:

/home/ubuntu/myfastapi/
├─ app/
│   └─ main.py
├─ .venv/
└─ requirements.txt

注意:建議使用虛擬環境,避免與系統套件衝突。

# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI application powered by Uvicorn
After=network.target

[Service]
# 使用的使用者與群組
User=ubuntu
Group=ubuntu

# 進入專案目錄
WorkingDirectory=/home/ubuntu/myfastapi

# 啟動指令,使用虛擬環境的 python
ExecStart=/home/ubuntu/myfastapi/.venv/bin/uvicorn app.main:app \
          --host 0.0.0.0 --port 8000 --workers 4

# 重新載入時直接重啟(零停機可改為 graceful reload)
ExecReload=/bin/kill -s HUP $MAINPID

# 若服務意外退出,自動重啟
Restart=on-failure
RestartSec=5

# 設定資源上限(可選)
MemoryLimit=500M
CPUQuota=80%

# 標準輸出與錯誤導向 journal
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

啟動與管理指令

# 重新載入 systemd 配置
sudo systemctl daemon-reload

# 啟動服務
sudo systemctl start fastapi.service

# 設定開機自動啟動
sudo systemctl enable fastapi.service

# 查看狀態
sudo systemctl status fastapi.service

# 查看日誌
journalctl -u fastapi.service -f

小技巧:若想要 Graceful Reload(不斷開連線),可把 ExecReload 改為 kill -s HUP $MAINPID,而在程式碼中加入 uvicorn.run(..., reload=True) 或使用 uvicorn --reload(僅開發環境)。


3. PM2 管理 FastAPI(透過 pm2-runtime

PM2 原本是 Node.js 的進程管理器,但它的 跨語言支援(如 pm2-runtime)讓我們也能用它來管理 Python 應用。

3.1 安裝與初始化

# 全域安裝 pm2(需要 Node.js)
npm install -g pm2

# 安裝 pm2-runtime(用於 Docker 或系統服務)
npm install -g pm2-runtime

3.2 建立 ecosystem.config.js

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "fastapi",
      // 使用虛擬環境的 python 執行 uvicorn
      script: "/home/ubuntu/myfastapi/.venv/bin/uvicorn",
      args: "app.main:app --host 0.0.0.0 --port 8000 --workers 4",
      interpreter: "none", // 不使用 Node.js 解析器
      env: {
        PYTHONUNBUFFERED: "1", // 讓 stdout 即時輸出
      },
      watch: false,
      autorestart: true,
      max_memory_restart: "500M",
      // PM2 內建日誌輪替
      log_date_format: "YYYY-MM-DD HH:mm Z",
      out_file: "/var/log/pm2/fastapi-out.log",
      error_file: "/var/log/pm2/fastapi-err.log",
    },
  ],
};

3.3 使用 pm2 管理

# 以 ecosystem 檔啟動
pm2 start ecosystem.config.js

# 設定開機自動啟動 (產生 systemd unit)
pm2 startup systemd -u ubuntu --hp /home/ubuntu

# 儲存當前的進程列表
pm2 save

# 查看狀態
pm2 status

# 零停機重載(適用於支援 hot‑reload 的應用)
pm2 reload fastapi

為什麼要使用 pm2-runtime
在 Docker 容器裡,pm2-runtime 可以直接作為 PID 1,確保容器收到 SIGTERM 時會正確傳遞給子進程,避免「容器無法關閉」的問題。


4. Supervisor 管理 FastAPI

Supervisor 以 INI 格式的設定檔管理子進程,適合在不想或無法使用 systemd(如某些容器環境)時使用。

4.1 安裝

sudo apt-get update
sudo apt-get install -y supervisor

4.2 設定檔範例

# /etc/supervisor/conf.d/fastapi.conf
[program:fastapi]
directory=/home/ubuntu/myfastapi
command=/home/ubuntu/myfastapi/.venv/bin/uvicorn app.main:app \
        --host 0.0.0.0 --port 8000 --workers 4
autostart=true
autorestart=true
startsecs=5
user=ubuntu
redirect_stderr=true
stdout_logfile=/var/log/supervisor/fastapi.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
environment=PYTHONUNBUFFERED="1"

4.3 操作指令

# 重新讀取設定
sudo supervisorctl reread
sudo supervisorctl update

# 啟動/停止
sudo supervisorctl start fastapi
sudo supervisorctl stop fastapi

# 查看狀態
sudo supervisorctl status fastapi

# 直接查看日誌
tail -f /var/log/supervisor/fastapi.log

提示:Supervisor 支援 XML‑RPC,如果需要遠端控制多台機器的服務,可以寫一個簡單的 Python 客戶端呼叫 supervisorctl 的 API。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記設定 WorkingDirectory 服務啟動後找不到相對路徑或 .env 在 systemd / Supervisor 中明確指定 WorkingDirectory
日誌被系統輪替刪除 journalctlpm2 預設不保留過久 設定 SystemMaxUse=(systemd)或 logrotate(pm2、Supervisor)
資源限制未設定 高併發時 CPU/Memory 飆升導致 OOM 使用 MemoryLimitCPUQuota(systemd)或 max_memory_restart(pm2)
使用 root 執行 安全風險、容器內部衝突 永遠以非 root 使用者(如 ubuntu)執行服務
Graceful reload 未測試 直接 restart 會斷開連線 在開發環境先測試 uvicorn --reloadkill -HUP 的行為
多個管理工具同時啟動 兩套服務同時監控同一進程,會互相干擾 只選擇一種(systemd、PM2 或 Supervisor)作為主要管理器

最佳實踐小結

  1. 先寫好 Dockerfile,在容器內使用 pm2-runtime 作為 PID 1;若不使用容器,則直接走 systemd。
  2. 將環境變數寫入 .env,並在服務管理器裡使用 EnvironmentFile=(systemd)或 environment=(Supervisor/PM2)載入。
  3. 把日誌導向統一平台(如 Loki、ELK),避免只看本機檔案。
  4. 健康檢查:對外提供 /health endpoint,並在 systemd 的 Restart= 或 Kubernetes 的 livenessProbe 中使用。
  5. 版本控制設定檔:將 *.serviceecosystem.config.js*.conf 放入 Git,確保部署時一致。

實際應用場景

場景 推薦工具 原因
單台 VM(Ubuntu 22.04) systemd 內建、資源限制、日誌整合最完整
多服務共存、需要即時重載 PM2 零停機 reload、集中管理多個 Python/Node 服務
舊有環境只能使用 init.d Supervisor 輕量、相容性高,且不依賴 systemd
Docker + CI/CD pm2-runtime + Dockerfile PID 1 管理、容器關閉時自動傳遞 SIGTERM
高可用叢集(K8s) 仍建議使用 systemd/PM2 產生的 Docker image,交給 K8s 直接管理 K8s 已內建 liveness/readiness,管理器僅負責容器內部進程

案例
某電商平台在 AWS EC2 上部署 FastAPI,使用 systemd 作為主要管理器,同時在 Docker 內部跑 Celery 工作者。透過 systemdAfter=network.targetRestart=on-failure,確保 API 與背景工作者在網路斷線或資源不足時自動恢復。日誌則由 Fluent Bit 收集,送至 Loki,實現全鏈路可觀測。


總結

  • systemd、PM2、Supervisor 各有優勢:systemd 最高整合度、PM2 零停機與跨平台友好、Supervisor 設定簡潔且不依賴 systemd。
  • 服務管理的核心 包括自動重啟、開機自動、資源限制與日誌集中,缺一不可。
  • FastAPI 部署時,務必把 虛擬環境路徑、工作目錄、環境變數 明確寫入管理器的設定檔,避免因相對路徑錯誤導致啟動失敗。
  • 最佳實踐:使用非 root 使用者、設定資源上限、導入健康檢查、把設定檔納入版本控制,並根據實際運維需求選擇最合適的管理工具。

掌握了這三種進程管理方式,你就能在不同的部署環境(VM、Docker、叢集)中,快速、穩定地將 FastAPI 應用上線,並在日常運維中減少意外停機、提升服務可觀測性。祝你部署順利、服務永遠線上! 🚀