本文 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 中設定 MemoryLimit、CPUQuota
零停機部署 必須停止舊服務再啟動新版本,期間不可用 PM2 的 reload、systemd 的 socket activation 等機制支援平滑升級

2. systemd 基本概念與設定

2.1 Unit File 結構

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

  • ExecStart:啟動指令
  • ExecReload:重新載入指令(支援零停機升級)
  • Restart:崩潰後的重啟策略(on-failure、always 等)
  • User、Group:執行的使用者/群組
  • 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
日誌被系統輪替刪除 journalctl 或 pm2 預設不保留過久 設定 SystemMaxUse=(systemd)或 logrotate(pm2、Supervisor)
資源限制未設定 高併發時 CPU/Memory 飆升導致 OOM 使用 MemoryLimit、CPUQuota(systemd)或 max_memory_restart(pm2)
使用 root 執行 安全風險、容器內部衝突 永遠以非 root 使用者(如 ubuntu)執行服務
Graceful reload 未測試 直接 restart 會斷開連線 在開發環境先測試 uvicorn --reload 或 kill -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. 版本控制設定檔:將 *.service、ecosystem.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 工作者。透過 systemd 的 After=network.target、Restart=on-failure,確保 API 與背景工作者在網路斷線或資源不足時自動恢復。日誌則由 Fluent Bit 收集,送至 Loki,實現全鏈路可觀測。


總結

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

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