1. Einleitung: Backend-Deployment-Logik ausführen

Hallo! In Teil 3 haben wir die Staging-Serverumgebung eingerichtet und gemeinsam das Grundgerüst eines FastAPI-Webhooks erstellt, das GitHub Webhook-Anfragen entgegennimmt und das Secret validiert. In diesem Prozess haben wir die Codes der handle_deploy und should_rebuild Funktionen im main.py vorab integriert, um einen Einblick in die wesentlichen Ideen der tatsächlichen Deploylogik zu erhalten.

In diesem Teil 4 werden wir erneut die Funktionsweise des Deployment-Handlers, die wir in Teil 3 kurz behandelt haben, klar herausarbeiten und uns darauf konzentrieren, wie wir diesen FastAPI-Webhooks so registrieren können, dass er automatisch beim Neustart des Servers ausgeführt wird und stabil betrieben werden kann, indem wir ihn als Systemd-Dienst registrieren. Nun ist euer automatisiertes Deploymentsystem bereit, noch robuster zu werden!

Wer die vorherigen Artikel verpasst hat, sollte diese zuerst lesen.

① Warum implementiere ich das selbst?

② Gesamtarchitektur und Prozessdesign

③ Einrichtung der Staging-Serverumgebung und grundlegender Aufbau des FastAPI-Webhooks


2. Den Deployment-Handler (handle_deploy) erneut überprüfen

Die handle_deploy Funktion, die wir in Teil 3 entwickelt haben, ist verantwortlich für die eigentliche Deployment-Arbeit, die im Hintergrund ausgeführt wird, wenn ein GitHub-Webhooks-Anfrage eingeht. Diese Funktion erfüllt folgende wesentliche Aufgaben.

Es wird empfohlen, den untenstehenden Leitfaden zusammen mit dem Beispielcode von Teil 3 zu betrachten.

2.1. Verwaltung mehrerer Projekte und Umgebungsvariablen

Um mehrere GitHub-Repositories (Projekte) mit einem einzigen Webhook-Server zu verwalten, müssen wir angeben, wo sich jedes Repository auf dem Server befindet. Die handle_deploy Funktion nutzt dazu ein repo_paths Dictionary, dessen Werte so entworfen sind, dass sie von den Umgebungsvariablen des Servers gelesen werden.

In dem Beispielcode tauchten Umgebungsvariablen wie SAMPLE_PROJECT_1_PATH auf. Ihr müsst die tatsächlichen Pfade jedes Projekts im .env Datei des Staging-Servers (diese Datei sollte im gleichen Verzeichnis wie die main.py des Webhook-Servers liegen) oder im systemd Dienstdatei wie folgt festlegen.

Code-Snippet

# ~/projects/webhook_server/.env

# GitHub Webhook Secret (in Teil 3 festgelegt)
GITHUB_WEBHOOK_SECRET="your_github_webhook_secret_value"

# tatsächliche Pfade jedes Projekts auf dem Server
SAMPLE_PROJECT_1_PATH="path/to/project1"
SAMPLE_PROJECT_2_PATH="path/to/project2"
SAMPLE_PROJECT_3_PATH="path/to/project3"

Die handle_deploy Funktion sucht anhand des Repository-Namens, der im Webhook-Payload übergeben wird, den entsprechenden Pfad im repo_paths Dictionary. Wenn kein zugehöriger Pfad gefunden wird, wird eine Warnung Unknown repository hinterlassen und die Funktion beendet.

Außerdem liest sie aus der .env Datei jedes Projekts (z. B. /path/to/project1/.env) zusätzliche Einstellungen wie DEBUG oder COLOR aus, um sie für die Auswahl der Docker-Compose-Datei (docker-compose.dev.yml oder docker-compose.prod.yml) oder zur Festlegung des Docker-Projektnamens zu verwenden. Diese flexible Logik ist sehr nützlich, um die Deploymethode an die spezifischen Merkmale eines Projekts anzupassen.

2.2. Entscheidung über den Docker-Bilderneubau (should_rebuild)

Bei jedem Deployment das Docker-Image neu zu bauen, kostet viel Zeit und ist ineffizient. Die should_rebuild Funktion verwendet den diff Befehl von Git, um festzustellen, ob sich wichtige Dateien, die den Docker-Bilderbau beeinflussen (wie Dockerfile, requirements.txt oder .env Dateien), geändert haben.

Diese Funktion prüft mit dem Befehl git diff --name-only HEAD~1 die Änderungen im Vergleich zum vorherigen Commit und gibt True zurück, wenn die Liste der geänderten Dateien vordefinierte trigger_files (z. B. Dockerfile, requirements.txt, .env, REBUILD_TRIGGER usw.) enthält, um den Neubau des Images anzuweisen.

Besonders nützlich ist die Datei REBUILD_TRIGGER, die auch als leere Datei existieren kann und bei ihrem Vorhandensein eine Neubau anordnet. Diese kann manuell auf dem Server erstellt werden, wenn das Image aus anderen Gründen als einem git pull neu gebaut werden soll. Die should_rebuild Funktion erkennt diese Datei und löscht sie automatisch, um unnötige Neubauten beim nächsten Deployment zu vermeiden.

2.3. Ausführen von Git- und Docker-Compose-Befehlen

Die handle_deploy Funktion verwendet das subprocess Modul von Python, um die git und docker compose Befehle auf dem Server auszuführen.

  • subprocess.run(["git", "-C", repo_path, "pull"], check=True): Die check=True Option löst eine subprocess.CalledProcessError aus, falls während der Ausführung des Git-Befehls ein Fehler auftritt, um diesen im Python-Code zu erkennen und angemessen zu protokollieren. Die -C Option sorgt dafür, dass der Git-Befehl im angegebenen Verzeichnis ausgeführt wird.

  • subprocess.run(["docker", "compose", "-p", project_name, "-f", compose_file, "up", "-d", "--build"], check=True): Je nach Ergebnis der should_rebuild Funktion wird entweder nur up -d ausgeführt oder die --build Option hinzugefügt, um das Image neu zu bauen. Die -p Option legt den Namen des Docker-Compose-Projekts fest, um Konflikte zwischen mehreren Projekten zu vermeiden.


3. FastAPI-Webhooks im Systemd-Dienstbetrieb

Bisher haben wir den Webhook-Server manuell mit dem Befehl uvicorn main:app --reload gestartet. Wenn der Server jedoch neu gestartet wird, verschwindet dieser Prozess, und auch bei einem Abbruch der Terminalsitzung wird der Dienst gestoppt. Um dies zu verhindern und einen stabilen Betrieb zu gewährleisten, sollte der FastAPI-Webhooks als Systemd-Dienst registriert werden.

3.1. Warum sollte man Systemd verwenden?

  • Automatischer Start: Der Webhook-Dienst wird auch beim Neustart des Servers automatisch gestartet.

  • Kontinuierliche Ausführung: Er wird nicht von einer Terminal-Sitzung abhängig und immer im Hintergrund ausgeführt.

  • Einfache Verwaltung: Mit dem systemctl Befehl kann der Dienst einfach gestartet, gestoppt, neu gestartet, der Status überprüft und die Logs eingesehen werden.

  • Ressourceneffizienz: Wie in Teil 2 erläutert, wird der Webhook-Server selbst als leichtes Python-App über Systemd betrieben, während schwerfällige Tools wie Git oder Docker wie gewohnt genutzt werden, sodass unnötige Docker-in-Docker Einstellungen oder das Wachstum der Containergröße vermieden wird.

3.2. Systemd-Dienstdatei (*.service) erstellen

Systemd-Dienste werden über .service Dateien definiert. Diese Datei muss im /etc/systemd/system/ Verzeichnis liegen. Wir werden eine Datei mit dem Namen github-webhook-deployer.service erstellen. Ich habe einen langen Namen gewählt, um es einfach zu erklären, aber ihr könnt einen geeigneten, prägnanten und leicht verständlichen Namen wählen.

# Datei erstellen (sudo-Rechte erforderlich)
sudo nano /etc/systemd/system/github-webhook-deployer.service

Inhalt der Datei:

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

[Unit]
Description=GitHub Webhook Deployer Service
After=network.target # Dieser Dienst wird nach Aktivierung des Netzwerks gestartet.

[Service]
User=your_username # Der Benutzer, der diesen Dienst ausführt (z. B. ubuntu, your_user)
Group=your_username # Die Gruppe, die diesen Dienst ausführt (z. B. ubuntu, your_user)
WorkingDirectory=/home/your_username/projects/webhook_server # Verzeichnis, in dem sich die FastAPI-App befindet

# Umgebungsvariablen:
# 1. Da die FastAPI-App selbst die .env Datei lädt, müssen Variablen, die innerhalb der App verwendet werden (wie GITHUB_WEBHOOK_SECRET),
#    nicht unbedingt über EnvironmentFile geladen werden. Aber wenn man auf Systemd-Ebene die Umgebungen konsistent verwalten möchte,
#    kann der Pfad für EnvironmentFile beibehalten werden.
# EnvironmentFile=/home/your_username/projects/webhook_server/.env

# 2. Um Systembefehle wie git, docker, docker compose mit subprocess.run() aufzurufen,
#    muss der PATH-Umgebungsvariable ausdrücklich der Pfad zu diesen Befehlen hinzugefügt werden.
#    Der Beispiel-PATH enthält Standard-Linux-Systempfade und den bin-Pfad einer Python-virtuellen Umgebung.
#    Passen Sie den Pfad 'your_username' und 'venv' an Ihre tatsächliche Umgebung an.
#    Überprüfen Sie den exakten PATH mit der .bashrc Datei des Servers oder dem Befehl 'echo $PATH', um die erforderlichen Pfade hinzuzufügen.
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 # Pfad zur Uvicorn-Datei der virtuellen Umgebung verwenden
Restart=always # Dienst wird immer neu gestartet, wenn er beendet wird.
StandardOutput=journal # Standardausgabe wird an das Systemd-Journal gesendet.
StandardError=journal # Standardfehler wird an das Systemd-Journal gesendet.

[Install]
WantedBy=multi-user.target # Dienst wird im Standard-Multiuser-Modus gestartet.

Erklärung:

  • [Unit] Abschnitt: Definiert allgemeine Informationen (Beschreibung, Abhängigkeiten) zum Dienst. After=network.target sorgt dafür, dass der Dienst gestartet wird, nachdem der Netzwerkdienst gestartet wurde.

  • [Service] Abschnitt: Definiert, wie der Dienst ausgeführt werden soll.

    • User, Group: Benutzer und Gruppe, unter der der Dienst ausgeführt wird. Es muss ein Benutzer mit Docker-Rechten (z. B. durch sudo usermod -aG docker your_username festgelegt) zugewiesen werden.

    • WorkingDirectory: Verzeichnis, in dem sich die main.py Datei der FastAPI-App befindet.

    • Environment="PATH=...": Dieser Teil ist entscheidend. Wenn subprocess.run() verwendet wird, um externe Befehle wie git, docker, docker compose anzurufen, kann die PATH Umgebungsvariable in der systemd Umgebung von der in .bashrc oder ähnlich festgelegten abweichen. Daher müssen der Ausführungsweg der virtuellen Umgebung (/home/your_username/projects/webhook_server/venv/bin) und die Standard-Ausführungspfade des Systems (/usr/local/bin, /usr/bin usw.) ausdrücklich angegeben werden.

    • EnvironmentFile (optional): Da die FastAPI-App die .env Datei durch python-dotenv lädt, müssen die Umgebungsvariablen (z. B. GITHUB_WEBHOOK_SECRET, SAMPLE_PROJECT_N_PATH usw.), die innerhalb der App verwendet werden, nicht durch dieses Setting geladen werden. Wenn jedoch sichergestellt werden soll, dass diese Variablen vor der Ausführung des ExecStart Kommandos (d. h. der uvicorn Prozess und dessen Unterprozesse) konsistent geladen werden, könnte die Verwendung von EnvironmentFile auch sinnvoll sein. Je nach Umgebung und Vorliebe können Sie diese einfügen oder auskommentieren.

    • ExecStart: Der tatsächliche Befehl zum Starten des Dienstes. Der Pfad zur Uvicorn-Ausführungsdatei der virtuellen Umgebung muss korrekt angegeben werden. --host 0.0.0.0 --port 8000 sorgt dafür, dass Anfragen auf Port 8000 von allen IPs empfangen werden.

    • Restart=always: Wenn der Dienst aus irgendeinem Grund beendet wird, wird versucht, ihn automatisch neu zu starten.

    • StandardOutput=journal, StandardError=journal: Alle Ausgaben und Fehler des Dienstes werden an das integrierte Logging-System von Systemd, journalctl, gesendet.

  • [Install] Abschnitt: Definiert, durch welches Ziel (target) der Dienst aktiviert werden soll. WantedBy=multi-user.target sorgt dafür, dass dieser Dienst automatisch startet, wenn der Server im Standard-Multiuser-Modus hochläuft.

3.3. Systemd-Dienst registrieren und starten

Nachdem die Dienstdatei erstellt wurde, muss Systemd darüber informiert werden, sodass es die Datei erkennt, und der Dienst gestartet werden kann.

# Systemd über die neue Dienstdatei informieren.
sudo systemctl daemon-reload

# Den Dienst so konfigurieren, dass er beim Booten automatisch startet.
sudo systemctl enable github-webhook-deployer.service

# Dienst starten.
sudo systemctl start github-webhook-deployer.service

# Status des Dienstes prüfen. Sollte als 'active (running)' angezeigt werden.
sudo systemctl status github-webhook-deployer.service

Nun läuft euer FastAPI-Webhooks-Server automatisch beim Neustart des Servers und wartet stabil im Hintergrund auf Webhook-Anfragen.

Beispiel für den Status eines Systemd-Dienstes


4. Überwachung und Debugging des FastAPI-Webhooks

Mit einem Systemd-Dienst ist es wesentlich einfacher, Logs zu überprüfen und Probleme zu diagnostizieren.

  • Überprüfen des Dienststatus:
sudo systemctl status github-webhook-deployer.service

Dieser Befehl zeigt den aktuellen Status des Dienstes, die letzte Ausführungszeit, die Prozess-ID usw. an.

  • Echtzeit-Logs anzeigen:
sudo journalctl -u github-webhook-deployer.service -f

Die -u Option zeigt die Logs eines bestimmten Dienstes an, während die -f Option die neuesten Logs in Echtzeit fortlaufend anzeigt. Die Log-Nachrichten, die wir in main.py eingestellt haben, werden jedes Mal erscheinen, wenn eine Webhook-Anfrage eingeht.

Wenn der Dienst nicht startet oder nicht wie erwartet funktioniert, sollte man zuerst die journalctl Logs überprüfen, um nach Fehlermeldungen zu suchen.


5. Fazit: Vorschau auf den nächsten Teil

In diesem Teil 4 haben wir die Funktionsweise des Deployment-Handlers aus Teil 3 erneut klargestellt und die Registrierung des FastAPI-Webhooks als Systemd-Dienst untersucht, um einen stabilen Betrieb zu gewährleisten. Nun sollte euer Webhooks-Server selbst bei einem Serverneustart reibungslos laufen.

Im nächsten Teil 5 werden wir die letzten Puzzlestücke zusammenfügen und das System abschließen. Wir werden Nginx als Reverse-Proxy für die sichere öffentliche Exposition des Webhooks einrichten, HTTPS aktivieren, um die Sicherheit zu erhöhen, und schließlich die Webhooks mit dem GitHub-Repository verknüpfen, um tatsächliche automatisierte Deployments zu testen. Seid gespannt!