1. 引言:在后台执行部署逻辑

大家好!在上一篇中,我们一起设置了暂存服务器环境,并创建了一个接收 GitHub Webhook 请求并验证 Secret 的 FastAPI Webhook 服务器的基本骨架。在这个过程中,main.py 文件中提前包含了 handle_deployshould_rebuild 函数的代码,让我们了解到实际部署逻辑的核心思想。

在本篇中,我们将再次明确讨论上一篇中简要提到的部署处理程序的逻辑运作方式,并重点介绍如何将该 FastAPI Webhook 服务器注册为 Systemd 服务,以便在服务器重启时自动运行并稳定操作。现在,您的自动部署系统已经做好了更坚固的准备!

如果您没有看到上一篇,建议您先查看之前的文章。

① 为什么要自己实现?

② 整体架构及过程设计

③ 暂存服务器环境设置及 FastAPI Webhook 服务器基础构建


2. 再次查看部署处理程序 (handle_deploy) 逻辑

在第三部分中编写的 main.py 中,handle_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 Secret (在第3篇中设置)
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 payload 中获得的存储库名称来查找 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 命令来检测Dockerfilerequirements.txt.env 文件等影响 Docker 镜像构建的关键文件是否已更改。

该函数通过 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 服务运行

到目前为止,我们手动执行 Webhook 服务器的命令是 uvicorn main:app --reload。但是,服务器重启后,该进程会消失,终端会话断开时服务也会中断。为了防止这种情况,并确保稳定运行,必须将 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] 部分:定义服务如何运行。

    • User, Group:运行服务的用户和组。 必须设定为具有 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 以增强安全性,并最终将 Webhook 连接到 GitHub 存储库以测试实际自动部署的过程。请期待!