1. はじめに: バックグラウンドでデプロイロジックを実行する

こんにちは!前回の3編では、ステージングサーバー環境を設定し、GitHub Webhookリクエストを受け取ってSecretを検証するFastAPI webhookサーバーの基本骨組みを一緒に作成しました。この過程で、main.pyファイル内にhandle_deployおよびshould_rebuild関数のコードをあらかじめ含めることにより、実際のデプロイロジックの核心アイデアを垣間見ることができました。

今回は4編では、3編で簡単に取り上げたデプロイハンドラーロジックの動作方式を再度明確にして、このFastAPI webhookサーバーがサーバーの再起動時にも自動的に実行され、安定して運営されることができるように、Systemdサービスとして登録する方法に重点を置いて説明します。これで、皆さんの自動デプロイシステムはさらに堅牢になる準備が整いました!

前の投稿を見られなかった方は、まず前の投稿を読まれることをお勧めします。

① なぜ自分で実装するのか?

② 全体アーキテクチャとプロセス設計

③ ステージングサーバー環境設定とFastAPI webhookサーバーの基礎構築


2. デプロイハンドラー (handle_deploy) ロジックの再確認

3編で作成したmain.pyhandle_deploy関数は、GitHub webhookリクエストが来た時にバックグラウンドで実行される実際のデプロイ作業を担当します。この関数は次のような核心的な役割を果たします。

3編の例コードをご覧になりながら、以下のガイドをご覧になってください。

2.1. 複数プロジェクト管理と環境変数設定

複数のGitHubリポジトリ(プロジェクト)を1つのWebhookサーバーで管理するには、各リポジトリがサーバー内のどこに位置しているかを知らせる必要があります。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ペイロードから受け取ったリポジトリ名を利用してrepo_paths辞書から該当プロジェクトのパスを探します。もしマッピングされたパスがない場合は、Unknown repositoryという警告を残して終了します。

また、各プロジェクト別の.envファイル(例: /var/www/my-project1/.env)からDEBUGCOLORといった追加設定値を読み込み、Docker Composeファイルの選択(docker-compose.dev.ymlまたはdocker-compose.prod.yml)やDockerプロジェクト名設定などに活用する柔軟なロジックも含まれています。これはプロジェクトの特性に応じたデプロイ方式をカスタマイズするのに非常に便利です。

2.2. Dockerイメージ再ビルドの決定 (should_rebuild)

デプロイ時に毎回Dockerイメージを再ビルドすることは時間がかかり、非効率的です。should_rebuild関数はGitのdiffコマンドを活用して、Dockerfile, requirements.txtまたは.envファイルなど、Dockerイメージビルドに影響を与える核心ファイルが変更されたかを検出します。

この関数はgit diff --name-only HEAD~1というコマンドを使用して前のコミットとの変更点を確認し、変更されたファイルリストにあらかじめ定義されたtrigger_files(例: Dockerfile, requirements.txt, .env, REBUILD_TRIGGERなど)が含まれていればTrueを返してイメージの再ビルドを指示します。

特にREBUILD_TRIGGERファイルは、空のファイルでも存在すれば強制的に再ビルドを実行する便利なトリックです。このファイルをサーバーに手動で作成し、git pull以外の理由でイメージの再ビルドが必要な場合に活用できます。should_rebuild関数はこのファイルを検出すると自動的に削除し、次回のデプロイ時には不必要に再ビルドが行われないようにします。

2.3. GitとDocker Composeコマンドの実行

handle_deploy関数はPythonのsubprocessモジュールを使用してサーバーでのgitおよびdocker 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リクエスト1回でプロジェクトの最新コードを取得し、必要な場合にのみイメージを再ビルドして効率的にサービスを更新できます。


3. FastAPI webhookサーバーをSystemdサービスとして運営する

これまでuvicorn main:app --reloadコマンドでWebhookサーバーを手動で実行してきました。しかし、サーバーが再起動するとこのプロセスは消えてしまい、ターミナルセッションが切断されるとサービスが中断されます。これを防ぎ、安定した運営のために、FastAPI webhookサーバーをSystemdサービスとして登録して管理する必要があります。

3.1. なぜSystemdを使用するべきか?

  • 自動起動: サーバー再起動時にも自動的にWebhookサービスが起動します。

  • 持続的な実行: ターミナルセッションに依存せず、バックグラウンドで常に実行されます。

  • 簡単な管理: systemctlコマンドを使ってサービスの開始、停止、再起動、状態確認、ログ確認などを簡単に行えます。

  • リソース効率: 2編で説明したように、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_SECRET, SAMPLE_PROJECT_N_PATHなど)はこの設定を通さなくても構いません。しかし、systemdがサービスを開始する前にこれらの変数を明示的にロードさせたい場合(ExecStartが実行するuvicornプロセス及びそのサブプロセスに一貫した環境を提供したい場合)には、EnvironmentFileを使用することも有効な方法です。ユーザーの環境や好みに応じて、コメントアウトしたり維持したりできます。

    • ExecStart: サービスを開始する実際の命令です。 仮想環境のuvicorn実行ファイルパスを正確に指定しなければなりません。 --host 0.0.0.0 --port 8000はすべてのIPから8000番ポートへのリクエストを受け取るようにします。

    • Restart=always: サービスが何らかの理由で終了した場合、Systemdが自動的に再起動を試みます。

    • StandardOutput=journal, StandardError=journal: サービスのすべての出力およびエラーをSystemdの統合ログシステムであるjournalctlに送信します。

  • [Install]セクション: サービスがどのターゲットによりアクティブ化されるかを定義します。 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. 終わりに: 次回予告

今回は4編では、3編で実装したデプロイハンドラーのロジックの動作方式を再度明確にし、最も重要なステップの1つであるFastAPI webhookサーバーをSystemdサービスとして登録して安定的に運営する方法を学びました。これで皆さんのWebhookサーバーはサーバーの再起動時にも動作することでしょう。

次回の5編では最後のパズルのピースを組み合わせてシステムを完成させます。Nginxをリバースプロキシとして設定してWebhookサーバーを外部に安全に公開し、HTTPSを適用してセキュリティを強化し、最終的にGitHubリポジトリにWebhookを連携させて実際の自動デプロイをテストするプロセスを扱います。お楽しみに!