FastAPI 部署與架構:使用 systemd、PM2 與 Supervisor 管理服務
簡介
在開發完一套可供外部使用的 FastAPI 應用後,最關鍵的步驟往往是「如何讓它穩定、持續地跑在伺服器上」。
若僅靠 uvicorn app:app 手動啟動,雖然開發階段足夠,但在正式環境中,一旦服務意外關閉、系統重啟或需要滾動升級,手動操作將變得不可接受。
本篇文章聚焦於 三種常見的服務管理工具:
- systemd – Linux 系統原生的 init 系統與服務管理器,適合在大多數雲端 VM、裸機或容器中使用。
- PM2 – 以 Node.js 生態為背景的進程管理器,支援跨平台、零停機重啟與日誌聚合,對於熟悉 JavaScript 的團隊相當友好。
- 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)作為主要管理器 |
最佳實踐小結
- 先寫好
Dockerfile,在容器內使用pm2-runtime作為 PID 1;若不使用容器,則直接走 systemd。 - 將環境變數寫入
.env,並在服務管理器裡使用EnvironmentFile=(systemd)或environment=(Supervisor/PM2)載入。 - 把日誌導向統一平台(如 Loki、ELK),避免只看本機檔案。
- 健康檢查:對外提供
/healthendpoint,並在 systemd 的Restart=或 Kubernetes 的livenessProbe中使用。 - 版本控制設定檔:將
*.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 應用上線,並在日常運維中減少意外停機、提升服務可觀測性。祝你部署順利、服務永遠線上! 🚀