1. 前言:在背景中執行部署邏輯

大家好!在上一篇文章中,我們設置了預備環境,並一起構建了 FastAPI webhook 伺服器的基本架構,接收 GitHub Webhook 請求並驗證秘鑰。在這個過程中,我們提前包含了 main.py 檔案中的 handle_deployshould_rebuild 函數的代碼,讓我們得以一窺實際部署邏輯的核心理念。

在本篇文章中,我們將再次明確檢視第三篇中簡要討論的部署處理器邏輯的運作方式,並著重說明如何將這個 FastAPI webhook 伺服器註冊為 Systemd 服務,以便它在伺服器重啟時可自動運行並穩定運作。現在,您的自動部署系統已經準備得更為堅固了!

如果您未查看前面的文章,建議您先查看以下文章。

① 為什麼要自己實現?

② 整體架構及流程設計

③ 預備伺服器環境設置及 FastAPI webhook 伺服器基礎搭建


2. 再次檢視部署處理器 (handle_deploy) 邏輯

第三篇中寫的 main.pyhandle_deploy 函數負責在 GitHub Webhook 請求進來時,在背景中執行實際的部署工作。這個函數執行以下核心角色。

建議您參考第三篇的示例代碼的同時,查看下面的指南。

2.1. 多項目管理及環境變數設置

若要通過一個 webhook 伺服器管理多個 GitHub 存儲庫(項目),則需告知每個存儲庫在伺服器內的位置。 handle_deploy 函數為此使用了 repo_paths 字典,並設計成從伺服器的 環境變數 中讀取這些值。

在示例代碼中出現了 SAMPLE_PROJECT_1_PATH 等環境變數。您需要在預備伺服器的 .env 檔案(該檔案必須與 webhook 伺服器的 main.py 位於同一目錄中)或 systemd 服務檔案中設置每個項目的實際路徑,如下所示。

代碼片段

# ~/projects/webhook_server/.env

# GitHub Webhook 秘鑰(第三篇中設置)
GITHUB_WEBHOOK_SECRET="your_github_webhook_secret_value"

# 各項目的伺服器內實際路徑
SAMPLE_PROJECT_1_PATH="/var/www/my-project1"
SAMPLE_PROJECT_2_PATH="/home/user/another-project"
SAMPLE_PROJECT_3_PATH="/opt/third-project"

handle_deploy 函數利用從 webhook 負載中傳遞的存儲庫名稱來查找 repo_paths 字典中的項目路徑。如果沒有映射路徑,則會留下 Unknown repository 警告並終止。

此外,每個項目的 .env 檔案(例如:/var/www/my-project1/.env)中還可以讀取如 DEBUGCOLOR 等額外設置值,用於選擇 Docker Compose 檔案(docker-compose.dev.ymldocker-compose.prod.yml)或設置 Docker 項目名稱等柔性邏輯。這對根據項目特徵自定義部署方式非常有用。

2.2. 決定是否重新構建 Docker 映像 (should_rebuild)

在每次部署時重新構建 Docker 映像會非常耗時且效率低下。 should_rebuild 函數利用 Git 的 diff 命令檢測是否有影響 Docker 映像構建的核心檔案(例如:Dockerfilerequirements.txt.env 檔案)的變更。

該函數通過 git diff --name-only HEAD~1 命令檢查前一提交的變更,若變更檔案列表中包含事先定義的 trigger_files(例如:Dockerfilerequirements.txt.envREBUILD_TRIGGER 等),則返回 True,指示重新構建映像。

尤其是 REBUILD_TRIGGER 檔案,即使是空檔案,只要存在也會強制執行重新構建,這是一個非常有用的技巧。此檔案可在服務器上手動創建,當需重新構建映像但不是因 git pull 的情況下可用。 should_rebuild 函數檢測到此檔案後,會自動將其刪除,以避免在下次部署時不必要的重新構建。

2.3. 執行 Git 和 Docker Compose 命令

handle_deploy 函數利用 Python 的 subprocess 模組於伺服器上執行 gitdocker compose 命令。

  • subprocess.run(["git", "-C", repo_path, "pull"], check=True)check=True 選項在執行 Git 命令時若發生錯誤,會引發 subprocess.CalledProcessError,讓 Python 程式碼能夠檢測並適當地記錄錯誤。 -C 選項使 Git 命令通過指定目錄來執行。

  • subprocess.run(["docker", "compose", "-p", project_name, "-f", compose_file, "up", "-d", "--build"], check=True):根據 should_rebuild 函數的結果,僅執行 up -d 或添加 --build 選項來執行映像重新構建。 -p 選項指定 Docker Compose 項目名稱,以避免多個項目之間發生衝突。

這些邏輯可通過一次 webhook 請求,獲取項目的最新代碼,並僅在必要時重新構建映像,有效地更新服務。


3. 將 FastAPI webhook 伺服器運行作為 Systemd 服務

到目前為止,我們通過 uvicorn main:app --reload 命令手動啟動 webhook 伺服器。然而,當伺服器重啟時,此進程會消失,並且如果終端會話中斷,服務也會中斷。為了防止這種情況並進行穩定運行,必須將 FastAPI webhook 伺服器註冊為 Systemd 服務 進行管理。

3.1. 為什麼要使用 Systemd?

  • 自動啟動:伺服器重啟時,webhook 服務將自動啟動。

  • 持續運行:不依賴於終端會話,始終在背景中運行。

  • 易於管理:使用 systemctl 命令,方便地啟動、停止、重新啟動、檢查狀態以及查看日誌等。

  • 資源效率:正如在第二篇中所述,webhook 服務本身是一個輕量級的 Python 應用程序,通過 Systemd 執行,而 Git 或 Docker 等較重的工具則直接利用系統中安裝的,從而避免不必要的 Docker in Docker 設置或容器大小增加。

3.2. 撰寫 Systemd 服務檔案 (*.service)

Systemd 服務由 .service 檔案定義。該檔案需位於 /etc/systemd/system/ 目錄中。我們將建立一個名為 github-webhook-deployer.service 的檔案。為了便於解釋,我選擇了這個較長的名稱,但您可以選擇簡短且易懂的名稱。

# 創建檔案(需要 sudo 權限)
sudo nano /etc/systemd/system/github-webhook-deployer.service

檔案內容:

# /etc/systemd/system/github-webhook-deployer.service

[Unit]
Description=GitHub Webhook Deployer Service
After=network.target # 網路啟動後啟動此服務。

[Service]
User=your_username # 執行此服務的用戶賬戶(例如:ubuntu,your_user)
Group=your_username # 執行此服務的群組(例如:ubuntu,your_user)
WorkingDirectory=/home/your_username/projects/webhook_server # FastAPI 應用所在的目錄

# 環境變數設置:
# 1. FastAPI 應用本身加載 .env 檔案,因此 GITHUB_WEBHOOK_SECRET 等應用內使用的變數
#    並沒有必要通過 EnvironmentFile 加載。但若希望在 systemd 層級上統一管理所有環境變數,您可以保留 EnvironmentFile= 路徑。
# EnvironmentFile=/home/your_username/projects/webhook_server/.env

# 2. 為了通過 subprocess.run() 調用 git、docker、docker compose 等系統命令,
#    必須顯式將這些命令所在的路徑添加到 PATH 環境變數中。
#    以下示例 PATH 包含一般 Linux 系統路徑和 Python 虛擬環境的 bin 路徑。
#    請根據實際環境修改 'your_username' 和 'venv' 的路徑。
#    確切的 PATH 可通過服務器的 .bashrc 檔案或 'echo $PATH' 命令確認,並包含必要的路徑。
Environment="PATH=/home/your_username/projects/webhook_server/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

ExecStart=/home/your_username/projects/webhook_server/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 # 使用虛擬環境的 uvicorn 路徑
Restart=always # 當服務終止時總是重新啟動。
StandardOutput=journal # 將標準輸出發送到 Systemd 日誌。
StandardError=journal # 將標準錯誤發送到 Systemd 日誌。

[Install]
WantedBy=multi-user.target # 在多用戶模式(即一般伺服器啟動狀態)下自動啟動服務。

解釋

  • [Unit] 區段:定義有關服務的一般信息(描述、依賴性)。 After=network.target 定義在網絡服務啟動後啟動此服務。

  • [Service] 區段:定義服務的執行方式。

    • UserGroup:執行服務的用戶和群組。 必須設置為擁有 Docker 權限的用戶(例如:用 sudo usermod -aG docker your_username 設置的用戶)。

    • WorkingDirectory:FastAPI 應用的 main.py 檔案所在目錄。

    • Environment="PATH=...":這部分是關鍵。在使用 subprocess.run() 調用 gitdockerdocker compose 等外部命令時,systemd 環境下的 PATH 環境變量可能與在 .bashrc 等中設置的不同。因此,必須明確指定虛擬環境的 uvicorn 執行路徑(/home/your_username/projects/webhook_server/venv/bin)和系統的基本執行文件路徑(/usr/local/bin/usr/bin 等)。

    • EnvironmentFile(可選):FastAPI 應用本身通過 python-dotenv 來加載 .env 檔案,因此應用內使用的環境變數(例如:GITHUB_WEBHOOK_SECRETSAMPLE_PROJECT_N_PATH 等)並不需要通過此設置加載。不過,若希望在 systemd 啟動服務前顯式加載這些變數(以便為 ExecStart 執行的 uvicorn 進程及其子進程提供一致的環境),使用 EnvironmentFile 也是有效的。您可根據用戶的環境和偏好選擇保留或註解此項設置。

    • ExecStart:啟動服務的實際命令。 必須正確指定虛擬環境的 uvicorn 執行檔案路徑。 --host 0.0.0.0 --port 8000 用於接收來自所有 IP 的 8000 端口請求。

    • Restart=always:當服務因任何原因終止時,Systemd 會自動嘗試重新啟動。

    • StandardOutput=journalStandardError=journal:將服務的所有輸出和錯誤發送到 Systemd 的綜合日誌系統 journalctl

  • [Install] 區段:定義服務會被哪些目標 (target) 啟用。 WantedBy=multi-user.target 定義在服務器啟動至一般的多用戶狀態時,自動啟動此服務。

3.3. 註冊及啟動 Systemd 服務

創建服務檔案後,需讓 Systemd 辨識該檔案並啟動服務。

# 通知 Systemd 新的服務檔案。
sudo systemctl daemon-reload

# 設置服務在開機時自動啟動。
sudo systemctl enable github-webhook-deployer.service

# 啟動服務。
sudo systemctl start github-webhook-deployer.service

# 檢查服務狀態。應顯示 'active (running)'。
sudo systemctl status github-webhook-deployer.service

現在,您的 FastAPI webhook 伺服器會在伺服器重啟後自動運行,並在背景中穩定地等待 webhook 請求。

Systemd 服務狀態示例


4. 監控及調試 FastAPI webhook 服務

使用 Systemd 服務可更輕鬆地檢查日誌和診斷問題。

  • 檢查服務狀態
sudo systemctl status github-webhook-deployer.service

此命令顯示當前服務狀態、最後執行時間、進程 ID 等信息。

  • 實時檢查日誌
sudo journalctl -u github-webhook-deployer.service -f

-u 選項用於特定服務的日誌,-f 選項則可實時輸出新的日誌。每當 webhook 請求進來時,main.py 中設置的日誌消息將會顯示在此。

若服務未能啟動或運作不正常,應優先檢查 journalctl 日誌以尋找錯誤信息。


5. 總結:下篇預告

在本篇中,我們再次明確檢視第三篇中實現的部署處理器邏輯的運作方式,並學習如何將 FastAPI webhook 伺服器註冊為 Systemd 服務 以便穩定運行。現在,您的 webhook 伺服器在伺服器重啟時也將穩穩運行。

在下一篇文章中,我們將完成最後的拼圖, 設置 Nginx 作為反向代理以安全地向外部公開 webhook 伺服器,應用 HTTPS 以加強安全性,最後將 GitHub 存儲庫與 webhook 對接,以測試實際的自動部署。敬請期待!