Architektur

Dienste, Netze, Datenfluss, Objektspeicher und Hintergrundjobs des oPBWeb-Stacks

Zielgruppe: Technische Administration

Überblick

oPBWeb läuft als ein Docker-Compose-Projekt mit dem Namen opbweb. Die Datei docker-compose.yml beschreibt den Produktivbetrieb, docker-compose.dev.yml ergänzt sie für die Entwicklung. Dieses Kapitel zeigt, welche Dienste es gibt, wie sie miteinander verbunden sind und wo welche Daten liegen.

Dienste

DienstContainerImageAufgabe
frontendopbweb-frontendopbweb-frontend:latest (nginx:stable-alpine)liefert die Weboberfläche und das Handbuch aus, leitet /api an die API weiter; einziger Dienst mit veröffentlichten Ports
apiopbweb-apiopbweb-backend:latest (python:3.13-slim)REST-API und WebSocket (FastAPI/uvicorn auf Port 8000), Anmeldung, Datenbankmigrationen beim Start, erstes Administrator-Konto
workeropbweb-workeropbweb-backend:latestführt alle Jobs aus (arq auf Redis); einziger Dienst, der mit den opsi-Configservern spricht und deren Passwörter entschlüsselt
dbopbweb-dbmariadb:11.4alle Anwendungsdaten (Pakete, Versionen, Benutzer, Jobs, Protokoll, Einstellungen)
redisopbweb-redisredis:7-alpineJob-Warteschlange, Heartbeat des Workers, Status der opsi-Verbindung, Sperrliste abgemeldeter Tokens, Depot-Sperren
garageopbweb-garagedxflrs/garage:v2.2.0S3-kompatibler Objektspeicher (ein Knoten, Replikationsfaktor 1) für Dateiinhalte und gebaute Pakete
garage-initopbweb-garage-initopbweb-garage-init:1.0.0 (alpine:3.21)richtet bei jedem Start Layout, Zugangsschlüssel opbweb und Bucket packages ein und beendet sich dann

API und Worker verwenden dasselbe Image; der Worker startet nur mit einem anderen Befehl (./start-worker.sh). Das Image von garage-init trägt auch im Produktivbetrieb das Tag :dev.

Alle Dienste außer garage-init laufen mit restart: unless-stopped und starten nach einem Neustart des Hosts automatisch.

Startreihenfolge

  1. db, redis und garage starten und melden sich über ihre Healthchecks als bereit.
  2. garage-init wartet auf die Admin-API von Garage, legt fehlende Ressourcen an und schreibt die S3-Zugangsdaten in das Volume garage-credentials.
  3. api startet, sobald db und redis gesund sind und garage-init erfolgreich beendet ist. Sie wartet bis zu 60 Sekunden auf die Datenbank, führt alembic upgrade head aus und legt das erste Administrator-Konto an, solange die Benutzertabelle leer ist.
  4. worker startet nach der API, schreibt einen ersten Heartbeat und prüft die Verbindung zu allen Configservern.
  5. frontend startet nach der API und wählt anhand der Zertifikate HTTPS oder HTTP.

Netze

NetzEigenschaftTeilnehmer
frontend-netnormalfrontend, api
backend-netinternal: true (kein Zugang nach außen)api, worker, db, redis, garage
opsi-netnormal, ausgehendworker

Von außen erreichbar sind nur die beiden Ports des Frontends (FRONTEND_HTTP_PORT, FRONTEND_HTTPS_PORT). Datenbank, Redis und Garage haben im Produktivbetrieb keine veröffentlichten Ports. Die API hat keinen Weg nach außen; nur der Worker erreicht über opsi-net die opsi-Configserver und ihre Depots (HTTPS, Port 4447). Die tatsächlichen Docker-Netze heißen mit Projektpräfix, zum Beispiel opbweb_opsi-net.

Datenfluss

 Browser
    │  HTTPS 443 (oder HTTP 80)
    ▼
 ┌──────────────────────── frontend (nginx) ────────────────────────┐
 │  /           Weboberfläche (Vue-SPA)                             │
 │  /manual/    Handbuch Deutsch, /manual/en/ Englisch              │
 │  /health     statische Antwort „healthy“                         │
 │  /api        Proxy inkl. WebSocket ──────────────┐               │
 └──────────────────────────────────────────────────┼───────────────┘
                                   frontend-net     ▼
                                          ┌──── api :8000 ────┐
                                          │ Anmeldung, REST,  │
                                          │ Job-Events (WS)   │
                                          └───┬──────┬─────┬──┘
                         backend-net (intern) │      │     │ Job einreihen
                    ┌─────────────────────────┘      │     ▼
                    ▼                                ▼   redis ◄──────┐
                 db (MariaDB)            garage (S3, Bucket packages) │
                    ▲                                ▲                │
                    └──────────────┬─────────────────┘                │
                                   │                                  │
                              ┌── worker ──┐ ─────────────────────────┘
                              │ arq-Jobs,  │   holt Jobs, Heartbeat
                              │ Cron       │
                              └─────┬──────┘
                                    │ opsi-net, HTTPS :4447
                                    ▼
                    opsi-Configserver und Depots (opsiconfd)
                    JSON-RPC sowie WebDAV /repository, /workbench, /depot

Typische Abläufe:

  • Bearbeiten in der Oberfläche: Browser → nginx → API → Datenbank und Objektspeicher. Der Worker ist nicht beteiligt.
  • Lange Vorgänge (Import, Bauen, Installieren, Depot-Abgleich, Workbench, Dateimanager-Kopien): Die API legt einen Job in der Datenbank an und reiht ihn in Redis ein. Der Worker führt ihn aus und schreibt Fortschritt und Protokoll. Der Browser verfolgt den Job live per WebSocket über /api/jobs/{id}/events.
  • Dateimanager in den Depots: Einzelne Dateioperationen (Auflisten, Lesen, Speichern) reicht die API direkt an den Worker weiter und wartet bis zu 60 Sekunden auf die Antwort. Antwortet der Worker nicht, erscheint „Worker nicht erreichbar.“ bzw. „Der Worker antwortet nicht.“
  • Uploads: nginx nimmt Uploads bis 20 GB an und reicht sie ungepuffert an die API weiter. Die API legt sie im Objektspeicher unter uploads/ ab, ein Job verarbeitet sie.
  • Downloads: Gebaute Pakete liefert die API aus dem Objektspeicher über kurzlebige Download-Links (2 Minuten gültig). Dateien von einem Depot kopiert zuerst ein Job nach downloads/.

Datenhaltung

Datenbank

MariaDB (Datenbank opbweb, Zeichensatz utf8mb4) enthält alle Metadaten: Benutzer, Protokoll, Produkte und Versionen mit control-Daten, Properties, Abhängigkeiten, die Dateiliste je Version mit SHA-256-Prüfsumme, Build-Ergebnisse, Jobs mit Protokollen, Snippets, Anwendungseinstellungen, die Configserver mit ihren Verbindungsdaten und Stufen sowie die Depots, von denen jedes genau einem Configserver zugeordnet ist. Das Passwort des technischen opsi-Benutzers liegt je Configserver verschlüsselt dort (RSA-OAEP): Die API verschlüsselt es mit secrets/opsi_key_public.pem, entschlüsseln kann nur der Worker mit secrets/opsi_key_private.pem.

Objektspeicher

Garage speichert alle Dateiinhalte im Bucket packages. Die Objekte sind nach Präfix gegliedert:

PräfixInhaltLebensdauer
blobs/<2 Zeichen>/<sha256>Inhalte der Paketdateien, nach SHA-256 adressiertdauerhaft; identische Dateien mehrerer Versionen oder Pakete liegen nur einmal vor (Deduplizierung)
artifacts/<productId>/<Dateiname>gebaute Pakete: .opsi, .md5, .zsyncdauerhaft; ein neuer Build ersetzt das vorherige Ergebnis der Version
uploads/<id>/<Dateiname>hochgeladene Dateien bis zur Verarbeitungtemporär, nach 24 Stunden gelöscht
downloads/<Job-ID>/<Dateiname>vom Depot geholte Dateien zum Herunterladentemporär, nach 24 Stunden gelöscht

Wegen der Deduplizierung bleiben nach dem Löschen von Paketen oder Versionen Blobs zurück, die keine Paketdatei mehr verwendet. Sie entfernt ein Administrator gezielt über Bereinigen … im Systemstatus .

Physisch liegen die Daten in zwei Docker-Volumes: garage-meta (Metadaten in LMDB, Schlüssel, Bucket) und garage-data (Datenblöcke). Die Präfixe oben sind Objektschlüssel im Bucket, keine Verzeichnisse im Volume. Die Layout-Kapazität von 100 GB, die garage-init setzt, ist eine Gewichtung für Garage und keine Quota; maßgeblich ist der freie Platz auf dem Host.

Volumes

VolumeDienstInhalt
mariadb-datadbDatenbankdateien
redis-dataredisWarteschlange und Status (AOF-Persistenz)
garage-metagarageMetadaten des Objektspeichers
garage-datagarageDatenblöcke des Objektspeichers
garage-credentialsgarage-init, api, workerS3-Zugangsdaten, von garage-init geschrieben

Docker legt die Volumes mit dem Präfix opbweb_ an, zum Beispiel opbweb_mariadb-data. Was davon zu sichern ist, steht unter Sicherung und Wiederherstellung .

Worker und Jobs

Der Worker arbeitet die Jobs aus Redis ab:

  • bis zu 10 Jobs gleichzeitig, damit kurze Dateimanager-Aufrufe nicht hinter langen Installationen warten,
  • Zeitlimit für Jobs: 8 Stunden (JOB_TIMEOUT_SECONDS), für die übrigen Worker-Funktionen 3600 Sekunden,
  • Jobs für dasselbe Depot laufen nacheinander. Eine Depot-Sperre in Redis sorgt dafür; ein Job wartet bis zu 6 Stunden darauf, sonst bricht er mit „Depot ist zu lange durch einen anderen Job belegt.“ ab.

Zusätzlich laufen im Worker zeitgesteuerte Aufgaben:

AufgabeZeitpunktWirkung
heartbeatalle 30 Sekundenschreibt ein Lebenszeichen nach Redis; älter als 90 Sekunden gilt der Worker im Systemstatus als gestört
check_opsialle 5 Minuten (einstellbar)prüft jeden eingerichteten Configserver (Anmeldung, Admin-Rechte, opsi-Version, Depots), gleicht die Depots jedes erreichbaren Servers ab, vergleicht das CA-Zertifikat und warnt 60 Tage vor Ablauf
cleanup_storagetäglich 03:17 Uhr (einstellbar)löscht Objekte unter uploads/ und downloads/, die älter als 24 Stunden sind

Die Uhrzeit richtet sich nach TZ (Standard Europe/Berlin). Blobs und Build-Ergebnisse löscht die automatische Bereinigung nicht.

Handbuch

Dieses Handbuch ist Teil des Frontend-Images. Beim Bauen erzeugt eine eigene Hugo-Stufe die statische Website, nginx liefert sie aus:

  • Deutsch unter /manual/,
  • Englisch unter /manual/en/.

Das Buch-Symbol in der Kopfleiste öffnet die Sprache der Oberfläche in einem neuen Tab. Das Handbuch ist statisch und ohne Anmeldung erreichbar. Änderungen am Handbuch erscheinen erst mit einem neu gebauten Frontend-Image, siehe Build und Auslieferung .