- Python 72.5%
- HTML 16.2%
- JavaScript 4.8%
- Shell 2.8%
- Jinja 1.6%
- Other 2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
CI / ci (push) Has been cancelled
All eleven items of the 0.8 plan are implemented (docs/DESIGN_SPEC.md §4): password self-service, API key and reasoning control for llama.cpp, vLLM and colibri, backup/restore commands, region comparison, proportions and charts with confidence intervals, text-block library, one synthesis path with text sources (legacy path removed), longer model ids, audit retention. Spec: release history row 0.8, §4 "Released in 0.8". Helm chart 0.4.0 with appVersion 0.8. Handbook: update notes for 0.8 (migrations 0022-0026, legacy path removal, new commands), release tags in mitarbeiten.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| Alt | ||
| Backend | ||
| ci | ||
| deploy | ||
| docs | ||
| Frontend | ||
| installer | ||
| vorlagen | ||
| .gitignore | ||
| action_log.txt | ||
| alternate_synthesis_path.txt | ||
| compose.yaml | ||
| graph.json | ||
| install.sh | ||
| justfile | ||
| linkfinder.py | ||
| linkfinder_scrapy.py | ||
| Links_Ingestion_testset.txt | ||
| Links_thueringen_20052026.txt | ||
| README.md | ||
| system_description_Release_0_5.txt | ||
| user-test-script-selfguided.txt | ||
| user_test_script.txt | ||
GBE — Gesundheitsberichterstattung
GBE erstellt Gesundheitsberichte für Gesundheitsämter: Es analysiert Datenquellen (Webseiten und Dateien im Netz), wertet sie aus und lässt lokale KI-Sprachmodelle (Ollama) die Texte schreiben. Das Ergebnis ist ein PDF und ein bearbeitbares Word- oder ODT-Dokument im Layout einer eigenen Vorlage.
Das vollständige Handbuch (Anleitungen für Fachanwender, Administration für den IT-Betrieb)
ist nach der Installation in der Anwendung unter /docs/ erreichbar, für angemeldete Konten.
Die Quellen liegen in Backend/handbook/. Die Installationsanleitung
steht hier, damit sie vor der Installation lesbar ist; sie entspricht der Handbuchseite
Installation.
Installation
GBE wird mit ./install.sh im Wurzelverzeichnis des Repositorys eingerichtet. Das Skript startet den eigentlichen Installer (python -m installer) in einem Wegwerf-Container, stellt einige Fragen, schreibt die Konfiguration (.env, Reverse-Proxy-Teil bzw. Kubernetes-Manifeste oder Helm-Werte) und startet GBE auf Wunsch. Auf dem Server selbst wird nichts installiert. Den Aufbau des Stacks beschreibt Architektur und Betrieb.
Wer jeden Schritt selbst ausführen oder die erzeugten Dateien frei anpassen will, findet alle Befehle und Dateien ohne Installer unter Manuelle Installation.
Voraussetzungen
| Komponente | Anforderung |
|---|---|
| Host | Linux mit POSIX-Shell und Git |
| Docker | Docker Engine mit Compose-Plugin v2 (docker compose); der Benutzer darf Docker bedienen. Auch für Kubernetes nötig: Docker führt den Installer aus und baut das Image. |
just |
empfohlen; das justfile im Wurzelverzeichnis enthält alle Betriebsbefehle (just --list). Fehlt es, meldet install.sh „Hinweis: just fehlt (https://just.systems); die Befehle danach sind just-Rezepte.“ und läuft trotzdem; die Befehle, die dieses Kapitel ausschreibt, funktionieren auch ohne just. |
| Kubernetes | kubectl mit Zugriff auf den Cluster; für das Helm-Chart zusätzlich helm 3; eine Registry, die der Cluster erreicht, oder ein Cluster, in den Sie das Image selbst laden (z. B. kind) |
| Netz beim Installieren und Bauen | Zugriff auf ghcr.io (Image des Installers), PyPI (der Installer lädt Jinja2 und PyYAML), Docker Hub, npm-Registry, Debian-Spiegel und den Download-Server von Playwright |
| Netz im Betrieb | ausgehendes HTTP/HTTPS aus GBE zu den Datenquellen und zum Inferenzserver |
| Inferenzserver | Ollama, llama.cpp (llama-server), vLLM oder colibri, aus den Containern bzw. Pods erreichbar, ohne API-Schlüssel, mindestens ein Modell geladen, siehe Sprachmodelle und Inferenzserver |
| Hostname | für den Betrieb ein DNS-Name, der auf den Reverse Proxy zeigt; zum Ausprobieren genügt http://localhost:8080 |
1. Installer starten
git clone ssh://git@fj16.bastler.space:223/markusw/GBE_Prototyp2.git gbe
cd gbe
./install.sh # oder: just install
Der Installer fragt nacheinander; Enter übernimmt den Wert in eckigen Klammern. Bei einem erneuten Lauf stammen diese Werte aus der vorhandenen .env, Sie bestätigen also nur, was sich nicht ändern soll. Jede Frage hat einen Schlüssel gleichen Namens für eine Antwortdatei.
install.sh startet den Installer im Container ghcr.io/astral-sh/uv:python3.12-bookworm-slim mit --network host. So sieht der Installer, welche Ports auf dem Server belegt sind. Läuft GBE schon, prüft er die eigenen Ports nicht.
2. Die Fragen
Für alle Betriebsarten:
| Frage | Standard | Schlüssel | Hinweis |
|---|---|---|---|
| „Öffentliche Adresse (URL, unter der die Nutzerinnen und Nutzer GBE öffnen)“ | http://localhost:8080 |
public_url |
beginnt mit http:// oder https://; daraus entstehen ALLOWED_HOSTS und CSRF_TRUSTED_ORIGINS (mit Port, wenn er nicht der Standardport des Schemas ist) |
| „Wie soll GBE laufen?“ | compose |
orchestrator |
compose (docker compose auf diesem Server), kubernetes (Manifeste und Kustomization) oder helm (Helm-Chart deploy/helm/gbe) |
| „Welcher Server stellt die Sprachmodelle bereit?“ | ollama |
llm_server_type |
ollama, llamacpp, vllm oder colibri |
| „Adresse des Inferenzservers (…)“ | http://host.docker.internal:<Port> mit 11434 (Ollama), 8080 (llama.cpp) bzw. 8000 (vLLM, colibri); bei Kubernetes kein Standard |
ollama_base_url |
aus Sicht der Container (host.docker.internal ist der Docker-Host) bzw. aus dem Cluster erreichbar. Bei einem erneuten Lauf schlägt der Installer die bisherige Adresse vor. War das die Standardadresse des bisherigen Servertyps, schlägt er nach einem Wechsel des Typs die Standardadresse des neuen vor. |
| „API-Schlüssel des LLM-Servers (leer: keiner)“ | leer | llm_api_key |
nur bei llamacpp, vllm und colibri: der Schlüssel, mit dem der Server gestartet wurde (--api-key). Die Eingabe bleibt unsichtbar. Bei einem erneuten Lauf zeigt die Frage [gespeichert; Enter behält ihn, - entfernt ihn]. Leerzeichen und die Zeichen ", ', #, $, \ und ` lehnt der Installer ab. Siehe API-Schlüssel. |
| „Größte Datei zum Hochladen in MB (Vorlagen, Datenquellen)“ | 50 |
upload_max_mb |
Grenze für Anfragen in der erzeugten Proxy-Konfiguration; GBE selbst begrenzt Uploads nicht |
Mit docker compose (compose) folgen:
| Frage | Standard | Schlüssel | gestellt bei |
|---|---|---|---|
| „Reverse Proxy (gefunden: …):“ | A |
proxy_mode |
immer; Buchstabe A–L, siehe Reverse Proxy. „gefunden“ nennt laufende Proxy-Container und installierte Proxy-Programme, nur als Hinweis. |
| „Ist der Name aus dem Internet erreichbar (Let's Encrypt)? Nein = Caddys eigene CA“ | ja | tls_public |
A mit https:// und einem Namen außer localhost |
| „E-Mail für Let's Encrypt (leer: ohne)“ | leer | acme_email |
ebenso, wenn die Antwort davor „ja“ war |
| „Adresse:Port für HTTP“, „Adresse:Port für HTTPS“ | 0.0.0.0:80, 0.0.0.0:443 |
http_bind, https_bind |
A mit https:// und einem Namen außer localhost |
| „Adresse:Port des mitgelieferten Proxys“ | bei localhost bzw. 127.0.0.1 in der Adresse 127.0.0.1:<Port der Adresse> (ohne Port 8080), bei jedem anderen Namen 0.0.0.0:80 |
http_bind |
A mit http:// oder localhost; der Port muss zur öffentlichen Adresse passen. https:// mit localhost oder 127.0.0.1 lehnt der Installer in Modus A ab und startet dann nichts. |
| „Zertifikat (Pfad, wie der Proxy ihn sieht)“ und „Privater Schlüssel (Pfad, wie der Proxy ihn sieht)“ | /etc/ssl/certs/gbe.pem, /etc/ssl/private/gbe.key |
tls_cert, tls_key |
D, E, I, L mit https:// |
| „PEM-Datei mit Zertifikat und Schlüssel (Pfad, wie HAProxy sie sieht)“ | /etc/haproxy/certs/gbe.pem |
tls_pem |
F mit https:// |
| „Name oder ID des Cloudflare-Tunnels (cloudflared tunnel create …)“ | leer (in der Datei dann gbe) |
cloudflare_tunnel |
K |
| „Zugangsdatei, wie cloudflared sie sieht“ | /etc/cloudflared/credentials.json |
cloudflare_credentials |
K |
| „Traefik-Entrypoint“ | websecure |
traefik_entrypoint |
C |
| „Traefik-Zertifikatsresolver (leer: keiner)“ | leer | traefik_certresolver |
C |
| „Wo läuft der Proxy?“ | docker |
proxy_location |
D–G, I–L: docker (Container auf diesem Server, gemeinsames Docker-Netz) oder host (direkt auf dem Server oder auf einem anderen Rechner) |
| „Docker-Netz, das GBE mit dem Proxy teilt“ | proxy |
external_network |
B, C und docker |
| „Adresse, auf der GBE lauscht (127.0.0.1 = nur dieser Server)“ und „Port“ | 127.0.0.1, 8000 |
bind_host, web_port |
H und host; mit 0.0.0.0 trägt der Installer 127.0.0.1:<Port> als Ziel in die Proxy-Konfiguration ein |
| „Name des Compose-Projekts (bestimmt die Volume-Namen)“ | gbe |
compose_project |
immer; Volumes heißen <Projekt>_gbe_db und <Projekt>_gbe_media |
| „Jetzt bauen und starten (docker compose up)?“ | ja | start |
immer |
Mit Kubernetes (kubernetes oder helm) folgen:
| Frage | Standard | Schlüssel | gestellt bei |
|---|---|---|---|
| „Namespace“ | gbe |
k8s_namespace |
immer |
| „Name des Helm-Release“ | gbe |
helm_release |
helm |
| „Image-Registry-Präfix (leer: Image liegt schon auf den Knoten, z. B. kind/k3s)“ | leer | k8s_registry |
immer; das Image heißt dann <Präfix>/gbe:<Tag>, sonst gbe:<Tag> |
| „Image-Tag“ | latest |
k8s_image_tag |
immer |
| „StorageClass (leer: Standard des Clusters)“ | leer | k8s_storage_class |
immer |
| „Zugriffsart des Medien-Volumes (ReadWriteMany bei mehreren Knoten, ReadWriteOnce auf einem Knoten)“ | ReadWriteOnce |
k8s_data_access |
kubernetes |
| „Größe des Medien-Volumes (Vorlagen, Berichte)“, „Größe des Datenbank-Volumes“ | 20Gi, 10Gi |
k8s_media_size, k8s_db_size |
immer |
| „Eingang in den Cluster:“ | nginx |
k8s_ingress |
immer; siehe Kubernetes |
| „IngressClass (leer: Standard des Clusters)“ | leer | k8s_ingress_class |
nginx, traefik, haproxy |
| „Gateway, an das die Route gehängt wird (Namespace/Name)“ | gateway-system/gateway |
k8s_gateway |
gateway |
| „cert-manager-ClusterIssuer (leer: Sie stellen das TLS-Secret selbst)“ und „Name des TLS-Secrets“ | leer, gbe-tls |
k8s_cert_issuer, k8s_tls_secret |
https:// mit nginx, traefik, haproxy |
| „Ist der Name aus dem Internet erreichbar (Let's Encrypt)? …“ | ja | tls_public |
caddy mit https:// |
| „E-Mail für Let's Encrypt (leer: ohne)“ | leer | acme_email |
ebenso, wenn die Antwort davor „ja“ war |
| „Image jetzt bauen und ausrollen (…)?“ | nein | start |
immer |
Ohne Frage, nur über die Antwortdatei: db_name und db_user (Standard gbe). Die Geheimnisse erzeugt der Installer beim ersten Lauf selbst und behält sie danach:
Wert in .env |
Schlüssel | Inhalt |
|---|---|---|
SECRET_KEY |
secret_key |
64 zufällige Zeichen |
DB_PASSWORD |
db_password |
32 zufällige Zeichen |
SANDBOX_TOKEN |
sandbox_token |
gemeinsames Geheimnis von Worker und Sandbox, 43 zufällige Zeichen |
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD |
admin_password, user_password |
Passwörter, mit denen die Startkonten Admin und User angelegt werden, je 16 zufällige Zeichen |
3. Reverse Proxy bei docker compose
Die Buchstaben stehen in .env (GBE_PROXY_MODE) und ändern sich zwischen Versionen nicht. Jede erzeugte Datei nennt im Kopf, wofür sie gedacht ist, und in Kommentaren, was Sie noch tun müssen. Alle Varianten erlauben Anfragen bis zur Größe aus der Upload-Frage und warten bis zu 300 s auf eine Antwort, weil Dialoggeführt im Web-Request auf das Sprachmodell wartet.
| Modus | Proxy | der Installer schreibt | Sie tun |
|---|---|---|---|
| A | mitgelieferter Caddy (Dienst gbe_proxy, Profil bundled-proxy) |
deploy/proxy/Caddyfile; Ports von gbe_proxy in compose.override.yaml |
nichts. Bei https:// mit öffentlichem Namen holt Caddy das Zertifikat bei Let's Encrypt; DNS und die Ports 80 und 443 müssen dafür aus dem Internet erreichbar sein. Mit „Caddys eigene CA“ (tls internal) warnen Browser, bis die Clients Caddys Wurzelzertifikat vertrauen. https:// geht nur mit einem Servernamen, nicht mit localhost. |
| B | vorhandener Caddy | deploy/proxy/caddy.snippet; gbe_web tritt dem Docker-Netz bei |
Snippet in Ihr Caddyfile aufnehmen (import /pfad/zu/gbe/deploy/proxy/caddy.snippet), Caddy-Container am selben Netz, Caddy neu laden |
| C | Traefik v3 | Docker-Labels an gbe_web in compose.override.yaml; alternativ deploy/proxy/traefik-gbe.yaml für den Datei-Provider |
Traefik nutzt den Docker-Provider und hängt am Netz; am Entrypoint in der statischen Konfiguration respondingTimeouts (readTimeout, writeTimeout) auf mindestens 300 s setzen |
| D | nginx | deploy/proxy/nginx.conf (server-Block) |
Datei in http { … } einbinden, z. B. als /etc/nginx/conf.d/gbe.conf; nginx -t && nginx -s reload |
| E | Apache httpd 2.4 | deploy/proxy/apache.conf (VirtualHost) |
Module laut Dateikopf aktivieren (Debian: a2enmod …), Datei einbinden, Apache neu laden |
| F | HAProxy | deploy/proxy/haproxy.cfg (frontend/backend) |
hinter global/defaults in Ihre haproxy.cfg übernehmen, haproxy -c -f haproxy.cfg, neu laden |
| G | Nginx Proxy Manager | deploy/proxy/nginx-proxy-manager.md |
Proxy Host in der Weboberfläche nach der Anleitung anlegen, Snippet unter Advanced einfügen |
| H | keiner oder ein anderer | deploy/proxy/REQUIREMENTS.md; gbe_web veröffentlicht <Adresse>:<Port> |
Ihren Proxy nach den Anforderungen in der Datei einrichten |
| I | Envoy | deploy/proxy/envoy.yaml (statische Bootstrap-Konfiguration) |
envoy -c envoy.yaml oder Listener und Cluster in Ihre Konfiguration übernehmen. Envoy begrenzt Anfragen nicht; für die Upload-Grenze den Filter envoy.filters.http.buffer mit max_request_bytes ergänzen (Hinweis im Dateikopf). |
| J | Kong Gateway (DB-less) | deploy/proxy/kong.yml |
mit KONG_DECLARATIVE_CONFIG=kong.yml laden oder deck gateway sync kong.yml; bei https:// das Zertifikat in Kong selbst hinterlegen |
| K | Cloudflare Tunnel | deploy/proxy/cloudflared.yml (Ingress-Regeln) |
Tunnel anlegen (cloudflared tunnel create gbe), Namen routen (cloudflared tunnel route dns gbe <Name>), cloudflared tunnel run. Cloudflare beendet Anfragen meist nach 100 s und begrenzt Uploads auf 100 MB; eine lange Antwort unter Dialoggeführt scheitert dann. |
| L | lighttpd | deploy/proxy/lighttpd.conf |
aus lighttpd.conf einbinden (include "gbe.conf"); lighttpd nach jedem Neuerzeugen der GBE-Container neu laden, weil sich deren Adresse ändern kann |
Bei D–G und I–L entscheidet „Wo läuft der Proxy?“: Mit docker tritt gbe_web dem Docker-Netz des Proxys bei und ist dort als gbe_web:8000 erreichbar, ohne einen Port zu veröffentlichen. Mit host veröffentlicht gbe_web die Adresse aus „Adresse, auf der GBE lauscht“ (Standard 127.0.0.1:8000). Binden Sie GBE an eine andere Adresse als 127.0.0.1, ist es dort ohne TLS erreichbar; beschränken Sie den Port per Firewall auf den Proxy. Bei 0.0.0.0 trägt der Installer 127.0.0.1:<Port> als Ziel in die Proxy-Konfiguration ein. Läuft der Proxy auf einem anderen Rechner, ersetzen Sie das Ziel dort durch die Adresse dieses Servers.
Für https tragen nginx, Apache, Envoy und lighttpd die Pfade aus den Fragen „Zertifikat …“ und „Privater Schlüssel …“ ein, HAProxy die PEM-Datei mit beidem. Die Pfade gelten so, wie der Proxy sie sieht (bei einem Proxy-Container also im Container). Der Installer speichert sie in der .env (GBE_TLS_CERT, GBE_TLS_KEY, GBE_TLS_PEM); Zertifikate beschafft und erneuert er nicht.
Ein gemeinsames Docker-Netz muss existieren, bevor GBE startet. Fehlt es, bricht install.sh mit „Das Docker-Netz … des Proxys fehlt: docker network create …“ ab. Legen Sie es an und starten Sie mit just up.
Die erzeugten Konfigurationen prüft just proxy-check mit dem Image des jeweiligen Proxys (caddy validate, nginx -t, apachectl configtest, haproxy -c usw.), ohne dass GBE laufen muss. Geben Sie /media/ nie direkt aus dem Volume frei: GBE prüft bei jeder Datei die Berechtigung.
4. Was der Installer schreibt und startet
| Datei | Betriebsart | Inhalt |
|---|---|---|
.env |
alle | alle Einstellungen und Geheimnisse; Rechte 600. compose.yaml und das justfile lesen sie. |
compose.override.yaml |
compose | der Teil für den Reverse Proxy: Ports von gbe_proxy (A), Docker-Netz und Labels von gbe_web (B, C, D–G und I–L mit docker) oder der veröffentlichte Port von gbe_web |
deploy/proxy/… |
compose | Konfiguration des gewählten Proxys, siehe oben |
deploy/k8s/… |
kubernetes | Manifeste, kustomization.yaml, jobs/migrate.yaml und gbe.env (Kopie der .env, Quelle des Secrets gbe-env, Rechte 600) |
deploy/helm-values.yaml |
helm | Werte für das Chart deploy/helm/gbe, mit Geheimnissen; Rechte 600 |
Alle diese Dateien sind in Git ausgeschlossen. Unveränderte Dateien schreibt der Installer nicht neu; eine geänderte Datei sichert er vorher als <Datei>.bak-JJJJMMTT-HHMMSS. Jede geschriebene Datei meldet er mit geschrieben: …, zum Schluss folgen die „Nächste Schritte:“.
Haben Sie „Jetzt bauen und starten“ bejaht, startet install.sh GBE nach dem Ende des Installer-Containers auf dem Host:
| Betriebsart | Schritte |
|---|---|
| compose | docker compose up -d --build --wait, dann docker compose exec -T gbe_web python manage.py apply_install_settings; Ende mit „GBE läuft: · Prüfung: just doctor“ |
| kubernetes | Image bauen (mit Registry-Präfix auch taggen und pushen), kubectl apply -k deploy/k8s, warten, bis das Deployment web bereit ist (web migriert beim Start selbst), dann Job gbe-migrate neu anlegen und abwarten (Migrationen, Startkonten, Inferenzserver in die Einstellungen), dann kubectl rollout restart für web, worker und sandbox, damit ein neu gebautes Image unter demselben Tag läuft |
| helm | Image bauen und pushen, helm upgrade --install <Release> deploy/helm/gbe -n <Namespace> --create-namespace -f deploy/helm-values.yaml --wait --timeout 15m, kubectl rollout restart für <fullname>-app und <fullname>-sandbox und warten, bis <fullname>-app bereit ist, dann apply_install_settings im Container web |
apply_install_settings trägt Servertyp, Adresse und (wenn gesetzt) API-Schlüssel des Inferenzservers aus der .env bei jedem Lauf in die Einstellungen ein, siehe Sprachmodelle und Inferenzserver. Beim ersten Start legt entrypoint.sh die Datenbank an (migrate) und die Startkonten (create_default_users).
Findet der Installer ein Problem, meldet er es mit !. Ein belegter Port (Port … ist schon belegt), bei Kubernetes eine fehlende Adresse des Inferenzservers oder in Modus A eine https://-Adresse mit localhost bzw. 127.0.0.1 („https mit localhost geht mit dem mitgelieferten Caddy nicht: http://localhost:8080 oder einen Servernamen nehmen“) verhindert den Start: Der Installer schreibt die Dateien trotzdem, endet aber mit „Nicht gestartet: erst die mit ! markierten Probleme beheben, dann ./install.sh erneut.“
Bei docker compose besteht der Stack aus compose.yaml (im Repository) und compose.override.yaml: gbe_web, gbe_worker, gbe_sandbox (führt KI-erzeugten Code aus), gbe_db, gbe_valkey und bei Modus A gbe_proxy. Datenbank und Broker liegen in einem privaten Netz, das nur GBE erreicht; die Sandbox teilt ihr Netz nur mit dem Worker.
| Modus | Proxy erreicht GBE über | veröffentlichte Ports |
|---|---|---|
| A | Netz edge zwischen gbe_proxy und gbe_web |
gbe_proxy: die Adressen aus den Fragen (80 und 443 bzw. 127.0.0.1:8080) |
B, C, D–G und I–L mit docker |
gemeinsames Docker-Netz, gbe_web:8000 |
keine |
H, D–G und I–L mit host |
veröffentlichte Adresse von gbe_web |
nur diese eine |
5. Kubernetes
Es gibt zwei Wege in den Cluster:
- Manifeste (
kubernetes): Der Installer schreibtdeploy/k8s/, eine Datei je Teil (namespace,storage,db,valkey,backend,sandbox, dazuingressbzw.REQUIREMENTS.md) und einekustomization.yaml, die das Secretgbe-envausdeploy/k8s/gbe.enverzeugt. Web, Worker, Sandbox und Valkey sind eigene Deployments, die Datenbank ein StatefulSet. Migrationen laufen im Jobdeploy/k8s/jobs/migrate.yaml. - Helm (
helm): Der Installer schreibt nurdeploy/helm-values.yamlfür das Chartdeploy/helm/gbe. Web und Worker laufen dort als zwei Container in einem Pod<fullname>-app.
Eingang in den Cluster:
| Art | Manifeste | Helm | was entsteht | Sie tun |
|---|---|---|---|---|
caddy |
ja | nein | mitgelieferter Caddy als Deployment proxy mit Service vom Typ LoadBalancer und PVC caddy-data für die Zertifikate |
DNS auf die externe Adresse des Service; bei https:// müssen 80 und 443 aus dem Internet erreichbar sein |
nginx |
ja | ja | Ingress für ingress-nginx mit Body-Grenze und Timeouts als Annotationen | Controller vorhanden; TLS über cert-manager-Issuer oder ein eigenes Secret |
traefik |
ja | ja | Ingress, bei Manifesten mit Middleware gbe-body für die Body-Grenze |
respondingTimeouts am Entrypoint von Traefik auf mindestens 300 s |
haproxy |
ja | ja | Ingress für den HAProxy Kubernetes Ingress Controller mit timeout-server |
Controller vorhanden; TLS wie bei nginx |
gateway |
ja | ja | HTTPRoute (Gateway API) am angegebenen Gateway, Timeout 300 s | TLS am Listener des Gateways; Body-Grenze der Implementierung mindestens so groß wie die Upload-Grenze |
none |
ja | ja | nur ClusterIP-Services; Manifeste: deploy/k8s/REQUIREMENTS.md |
Verkehr selbst auf den Service web (Helm: <fullname>-web), Port 8000, leiten |
Ohne TLS-Secret und ohne Issuer verwendet ein Ingress-Controller sein Standardzertifikat. Ein eigenes Secret legen Sie so an: kubectl -n gbe create secret tls gbe-tls --cert=… --key=….
Speicher. Das Medien-Volume gbe-media (bei Helm <fullname>-media) nutzen Web und Worker gemeinsam. Bei den Manifesten sind Web und Worker getrennte Pods: Mit ReadWriteOnce bekommt der Worker eine Pod-Affinität zum Web-Pod und läuft immer auf dessen Knoten. Mit ReadWriteMany (eine StorageClass, die das kann) dürfen beide auf verschiedenen Knoten laufen. Helm braucht nur ReadWriteOnce, weil beide Container im selben Pod laufen.
Image. Ohne Registry-Präfix muss gbe:<Tag> schon auf den Knoten liegen; bei kind lädt just k8s-load es hinein. Mit Präfix schiebt just k8s-build es nach <Präfix>/gbe:<Tag>. just k8s-up und just helm-up starten die GBE-Pods jedes Mal neu, damit ein neu gebautes Image auch unter demselben Tag verwendet wird; mit Registry zieht der Cluster es aber nur beim Tag latest immer neu (imagePullPolicy: Always, sonst IfNotPresent).
Entfernen. just k8s-down löscht die Workloads der Manifeste; die PVCs, das Secret gbe-env und der Namespace bleiben. just helm-down entfernt das Release; die PVCs bleiben.
Ohne automatischen Start führen Sie die Schritte selbst aus:
# Manifeste
just k8s-build # Image bauen (und mit Registry-Präfix hochladen)
just k8s-up # kubectl apply -k deploy/k8s, Pods neu starten
just k8s-migrate # Migrationen, Startkonten, Inferenzserver (ein Job)
just k8s-status # Pods, Services, Volumes, Ingress
# Helm
just k8s-build
just helm-up # helm upgrade --install …, Pods neu starten, apply_install_settings
just helm-status
6. Prüfen
just doctor # docker compose
just k8s-doctor # Kubernetes mit Manifesten
just helm-doctor # Helm
just doctor prüft im Container gbe_worker mit manage.py doctor Datenbank und Migrationen, Aufgabenwarteschlange und Worker (ein gerade beschäftigter Worker ergibt nur einen Hinweis), den Inferenzserver (Zahl der Modelle, Warnung, wenn das Standardmodell fehlt), das Handbuch und einen Probelauf in der Sandbox. Danach prüft python -m gbe_sandbox.selfcheck im Container gbe_sandbox, dass die Sandbox keine Geheimnisse kennt und Datenbank und Valkey nicht erreicht, und curl ruft <öffentliche Adresse>/healthz über den Proxy auf (mit Caddys eigener CA ohne Prüfung des Zertifikats). ✓ heißt bestanden, ! ist ein Hinweis, ✗ ein Fehler. Die Kubernetes-Varianten prüfen die ersten beiden Teile ohne den Aufruf über die öffentliche Adresse. Meldungen und Abhilfen stehen in der Fehlerbehebung.
/healthz antwortet ohne Anmeldung mit ok (HTTP 200) bzw. mit 503, solange die Datenbank nicht erreichbar ist. Proxys und Load Balancer können es als Gesundheitsprüfung nutzen.
7. Erste Anmeldung
Die Passwörter der Startkonten stehen in der .env:
grep '^DEFAULT_' .env
DEFAULT_ADMIN_PASSWORD gehört zu Admin (Administrator, Zugang zu /admin/), DEFAULT_USER_PASSWORD zu User. Bei Helm stehen sie zusätzlich in deploy/helm-values.yaml (startAccounts). Melden Sie sich unter <öffentliche Adresse>/anmelden/ als Admin an, ändern Sie auf der Seite Benutzer die Passwörter der Startkonten (Konto öffnen → Passwort setzen) und legen Sie über Neues Konto persönliche Konten an.
Die Werte in der .env gelten nur, wenn create_default_users ein Konto neu anlegt. Ein bestehendes Konto behält sein Passwort; nach einer Änderung auf der Seite Benutzer ist der Wert in der .env also veraltet.
Warning
Startkonten nicht löschen
create_default_usersläuft bei jedem Start. Fehlt ein Konto namensAdminoderUser, legt es das Konto neu an, mit dem Passwort ausDEFAULT_ADMIN_PASSWORDbzw.DEFAULT_USER_PASSWORD. Ohne diese Werte, etwa im lokalen Stack, verwendet es das öffentlich bekannte Passwort ausBackend/gbe/management/commands/create_default_users.py. Brauchen Sie ein Konto nicht, deaktivieren Sie es (Seite Benutzer → Konto öffnen → Deaktivieren), statt es zu löschen; die Seite Benutzer lässt Startkonten ohnehin nicht löschen.
Mehr zu Konten unter Benutzer und Anmeldung.
Unbeaufsichtigte Installation mit answers.toml
Jede Frage hat einen Schlüssel gleichen Namens (siehe die Tabellen oben). Fehlende Schlüssel nehmen den Wert aus der vorhandenen .env, sonst den Standard. Ohne start = true startet der Installer dabei nichts. Die Datei darf an beliebiger Stelle liegen; install.sh stellt sie dem Installer-Container schreibgeschützt bereit.
public_url = "https://gbe.example.org"
orchestrator = "compose"
proxy_mode = "D"
proxy_location = "host"
bind_host = "127.0.0.1"
web_port = 8000
llm_server_type = "vllm"
ollama_base_url = "http://host.docker.internal:8000"
llm_api_key = "<Schlüssel des vLLM-Servers>"
upload_max_mb = 50
compose_project = "gbe"
start = true
tls_cert = "/etc/letsencrypt/live/gbe.example.org/fullchain.pem"
tls_key = "/etc/letsencrypt/live/gbe.example.org/privkey.pem"
./install.sh --answers /etc/gbe/answers.toml
Fehlt die Datei, bricht install.sh mit „Antwortdatei nicht gefunden: …“ ab. Einen Schlüssel, den der Installer nicht kennt, meldet er mit „! : unbekannte Antwort <Schlüssel> wird ignoriert (Tippfehler?)“ und lässt ihn weg. Eine answers.toml im Wurzelverzeichnis des Repositorys ist in Git ausgeschlossen.
Weitere Optionen:
| Option | Wirkung |
|---|---|
--answers DATEI |
Antworten aus einer TOML-Datei, ohne Rückfragen |
--rotate-secrets |
neuer SECRET_KEY und neues SANDBOX_TOKEN; alle Sitzungen enden. Das Datenbankpasswort bleibt, siehe Konfiguration. |
--no-start |
nur Dateien schreiben, nichts starten |
--dry-run |
ausgeben, was geschrieben würde, ohne etwas zu schreiben |
--out VERZ |
in ein anderes Verzeichnis schreiben (für Prüfungen); die bisherigen Antworten kommen dann auch von dort |
Erneut installieren
Ein weiterer Lauf von ./install.sh schlägt alle bisherigen Antworten vor und behält die Geheimnisse. Zeilen, die Sie selbst in die .env geschrieben haben (etwa HTTPS_PROXY), übernimmt er unter # kept from the previous .env. Die Daten in den Volumes bleiben erhalten. Einzelheiten unter Konfiguration.
Umstieg vom bisherigen Installer
Bis Release 0.6 hieß der Installer deploy/install.sh und schrieb nach deploy/generated/. Der neue Installer übernimmt dessen Antworten und Geheimnisse (install.conf, .env, bei Kubernetes values.yaml), solange im Wurzelverzeichnis noch keine .env liegt; er meldet das mit „Antworten des bisherigen Installers (deploy/generated/) werden übernommen.“ Der Name der Installation wird zum Compose-Projekt bzw. Helm-Release, die Volumes <Name>_gbe_db und <Name>_gbe_media bzw. die PVCs des Charts bleiben also in Gebrauch.
-
Sichern Sie Datenbank, Medien und
deploy/generated/, siehe Sicherung und Wiederherstellung. -
Halten Sie bei Docker den alten Stack an (die Volumes bleiben):
docker compose -p <Name> -f deploy/generated/docker-compose.yml down -
Neuen Stand holen und den Installer ausführen; prüfen Sie dabei die vorgeschlagenen Antworten:
git pull ./install.sh
bisher (GBE_PROXY) |
jetzt |
|---|---|
none (mitgelieferter Caddy) |
A |
caddy, traefik |
B, C (nur in Docker; lief der Proxy direkt auf dem Server, wählen Sie H) |
nginx, apache, haproxy, npm, envoy, kong, cloudflared |
D, E, F, G, I, J, K, mit derselben Antwort auf „Wo läuft der Proxy?“ |
other |
H |
| Betriebsart Kubernetes | helm mit demselben Release; Ingress nginx, bei Traefik traefik, bei HAProxy haproxy, bei Envoy gateway |
Danach gilt:
- Die öffentliche Adresse ist
https://<bisheriger Hostname>, mit Port, wenn er nicht 443 war. - Ein Proxy in Docker erreicht GBE unter
gbe_web:8000; einen eigenen Netzwerk-Alias gibt es nicht mehr. Vergleichen Sie Ihre Proxy-Konfiguration mit der neu erzeugten unterdeploy/proxy/. - Bei Helm heißt das Image jetzt
<Präfix>/gbe:<Tag>; aus einem bisherigen Repositoryregistry.example.org/gbewird das Präfixregistry.example.org. - Die Passwörter der Startkonten übernimmt der Installer in die
.env; einecredentials.txtgibt es nicht mehr. just doctorersetztdeploy/install.sh --verify.
Läuft alles, archivieren Sie deploy/generated/ verschlüsselt und löschen es; es enthält Geheimnisse.
Eine noch ältere Installation aus Backend/docker-compose.yml (Compose-Projekt backend) übernimmt der Installer nicht von selbst. Legen Sie vor dem ersten Lauf eine .env im Wurzelverzeichnis an, mit COMPOSE_PROJECT_NAME=backend sowie SECRET_KEY und DB_PASSWORD aus Backend/.env (ohne Eintrag dort gilt das Datenbankpasswort gbepw), halten Sie den alten Stack an (cd Backend && docker compose -p backend down) und führen Sie ./install.sh aus. Die Startkonten existieren dort schon und behalten ihre bisherigen Passwörter.
Checkliste
- DNS-Eintrag des Hostnamens zeigt auf den Reverse Proxy.
just doctor(bzw.just k8s-doctor,just helm-doctor) endet mit „Alle Prüfungen bestanden.“ und ohne ✗.- Anmeldung als
Adminfunktioniert; Passwörter der Startkonten geändert, persönliche Konten angelegt. - Eine erste Sicherung mit
just backup(Kubernetes:just k8s-backupbzw.just helm-backup) liegt außerhalb des Servers; sie enthält auch.envund die Kubernetes-Dateien, siehe Sicherung und Wiederherstellung. - Testlauf: eine Datenquelle hinzufügen und einen kurzen Bericht erzeugen.
Manuelle Installation
./install.sh tut zweierlei: Der Installer (python -m installer) leitet aus Ihren Antworten Werte ab und schreibt daraus Dateien (.env, compose.override.yaml und die Proxy-Konfiguration, Kubernetes-Manifeste oder Helm-Werte). Danach führt install.sh auf dem Host einige Befehle aus (Image bauen, starten, Einstellungen übernehmen). Diese Seite zeigt beides als einfache Dateien und Befehle, für alle drei Betriebsarten und ohne Installer und ohne just. Damit können Sie GBE in eine eigene Automatisierung einbauen oder weiter vom Standard abweichen, als die Fragen des Installers erlauben.
Es gelten dieselben Voraussetzungen wie für den Installer, nur just und der Zugriff auf ghcr.io und PyPI für den Installer-Container entfallen. Alle Pfade sind relativ zum Wurzelverzeichnis des Repositorys. Den Aufbau des Stacks beschreibt Architektur und Betrieb.
Quellen der Wahrheit
Die Dateien auf dieser Seite sind genau die, die der Installer schreibt. Maßgeblich sind:
| Was | Wo |
|---|---|
| Vorlagen aller erzeugten Dateien | installer/templates/*.j2, Kubernetes unter installer/templates/k8s/ |
| Ableitung jedes Werts aus den Antworten | installer/__init__.py → template_vars() |
| fertig gerenderte Beispiele jeder Variante | installer/tests/snapshots/<Variante>/ |
| Befehle nach dem Schreiben | install.sh (Ende der Datei) und justfile |
Die Beispiele unter installer/tests/snapshots/ sind mit dem Host gbe.example.org und Platzhaltern statt Geheimnissen (SSSS…, PPPP…) erzeugt. Ein Schrägstrich im Pfad ist dort als __ geschrieben: D/deploy__proxy__nginx.conf ist die Datei deploy/proxy/nginx.conf für Modus D.
| Verzeichnis | Variante |
|---|---|
A … L |
docker compose, Proxy-Modus A–L, https://gbe.example.org, Proxy in Docker am Netz proxy_net |
A-local |
Modus A mit http://localhost:8080 |
D-host … L-host |
Modi D–G und I–L mit dem Proxy auf dem Host, http://gbe.example.org |
k8s-<Art> |
Kubernetes-Manifeste mit dem Eingang caddy, nginx, traefik, haproxy, gateway oder none |
helm-<Art> |
Helm-Werte mit dem Eingang nginx, traefik, haproxy, gateway oder none |
Die Kubernetes- und Helm-Beispiele verwenden das Registry-Präfix registry.example.org/gbe, den Tag 1.4.0, die StorageClass fast, den ClusterIssuer letsencrypt und den Inferenzserver http://ollama.ai:11434 vom Typ vllm.
Mittelweg: Dateien rendern lassen und anpassen
Oft genügt es, den Installer nur die Dateien schreiben zu lassen und diese anzupassen:
| Befehl | Wirkung |
|---|---|
./install.sh --dry-run |
stellt die Fragen (oder liest die Antworten aus --answers) und gibt jede Datei nach einer Zeile ===== <Pfad> aus; schreibt und startet nichts. Ohne vorhandene .env sind die Geheimnisse bei jedem Lauf neu. |
./install.sh --answers antworten.toml --out render |
schreibt alle Dateien nach render/ im Repository statt ins Projekt und startet nichts; die bisherigen Antworten kommen aus render/.env, nicht aus der .env des Projekts. Das Verzeichnis muss innerhalb des Repositorys liegen und relativ angegeben werden, weil der Installer im Container nur das Repository sieht. render/ ist nicht in Git ausgeschlossen und enthält Geheimnisse. |
./install.sh --no-start |
schreibt die Dateien ins Projekt und startet nichts |
Die Schlüssel der Antwortdatei beschreibt Installation.
Warning
Ein späterer Lauf überschreibt Ihre Änderungen
./install.shschreibt bei jedem Lauf.env,compose.override.yaml,deploy/proxy/…,deploy/k8s/…unddeploy/helm-values.yamlneu; eine geänderte Datei sichert er vorher als<Datei>.bak-JJJJMMTT-HHMMSS. Nach Änderungen von Hand führen Sie den Installer also nicht mehr aus, oder Sie halten Ihre Änderungen in Dateien, die er nicht schreibt (Tabelle unten).
| Betriebsart | eigene Änderungen, die ein späterer Lauf behält |
|---|---|
| alle | Zeilen in der .env mit Variablen, die der Installer nicht schreibt; er übernimmt sie unter # kept from the previous .env. Werte der Variablen, die er schreibt, schlägt er beim nächsten Lauf vor; DEBUG, ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, COMPOSE_PROFILES und GBE_HELM_FULLNAME leitet er immer neu ab, siehe Konfiguration. |
| docker compose | eine eigene Datei, z. B. compose.local.yaml, eingebunden mit der Zeile COMPOSE_FILE=compose.yaml:compose.override.yaml:compose.local.yaml in der .env. Mit COMPOSE_FILE lädt Compose compose.override.yaml nicht mehr von selbst, sie muss deshalb in der Liste stehen. |
| Kubernetes mit Manifesten | eine eigene Kustomization (Overlay) außerhalb von deploy/k8s/, die deploy/k8s als Ressource einbindet und Patches ergänzt; anwenden mit kubectl apply -k <Overlay> statt just k8s-up |
| Helm | eine zweite Werte-Datei nach der des Installers: helm upgrade --install … -f deploy/helm-values.yaml -f eigene-werte.yaml; die spätere Datei gewinnt. just helm-up kennt sie nicht. |
Gemeinsame Schritte
Code holen
git clone ssh://git@fj16.bastler.space:223/markusw/GBE_Prototyp2.git gbe
cd gbe
Geheimnisse erzeugen
Der Installer erzeugt die Geheimnisse beim ersten Lauf mit Pythons secrets.token_urlsafe(). Dieselben Werte erhalten Sie so:
| Variable | Befehl | Länge |
|---|---|---|
SECRET_KEY |
python3 -c 'import secrets; print(secrets.token_urlsafe(48))' |
64 Zeichen |
DB_PASSWORD |
python3 -c 'import secrets; print(secrets.token_urlsafe(24))' |
32 Zeichen |
SANDBOX_TOKEN |
python3 -c 'import secrets; print(secrets.token_urlsafe(32))' |
43 Zeichen |
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD |
python3 -c 'import secrets; print(secrets.token_urlsafe(12))' |
je 16 Zeichen |
Ohne Python liefert openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n' einen gleichwertigen Wert (Zahl der Bytes wie oben). Bleiben Sie bei Buchstaben, Ziffern, - und _: Die Werte stehen ohne Anführungszeichen in der .env, und Compose, kubectl und Kustomize behandeln Zeichen wie $, # oder Anführungszeichen unterschiedlich.
DB_PASSWORD übernimmt PostgreSQL nur beim ersten Start mit leerem Volume. Ändern Sie es danach nicht mehr in der .env allein, siehe Geheimnisse erneuern. Ohne DEFAULT_ADMIN_PASSWORD und DEFAULT_USER_PASSWORD legt create_default_users die Startkonten mit den öffentlich bekannten Passwörtern aus Backend/gbe/management/commands/create_default_users.py an; setzen Sie beide immer.
Abgeleitete Werte
Aus der öffentlichen Adresse und den übrigen Antworten leitet template_vars() diese Werte ab. Bilden Sie sie von Hand genauso:
| Wert | Regel | Beispiel |
|---|---|---|
ALLOWED_HOSTS |
Hostname der öffentlichen Adresse, localhost und 127.0.0.1 (der Installer sortiert alphabetisch; die Reihenfolge ist egal). localhost muss bleiben: Healthcheck und Kubernetes-Proben senden Host: localhost. |
127.0.0.1,gbe.example.org,localhost |
CSRF_TRUSTED_ORIGINS |
Schema und Hostname; der Port nur, wenn er nicht 443 (https) bzw. 80 (http) ist |
https://gbe.example.org, https://gbe.example.org:8443 |
OLLAMA_BASE_URL |
docker compose: http://host.docker.internal:<Port> mit 11434 (Ollama), 8080 (llama.cpp), 8000 (vLLM, colibri), wenn der Inferenzserver auf dem Docker-Host läuft. Kubernetes: eine aus dem Cluster erreichbare Adresse, es gibt keinen Standard. |
http://host.docker.internal:11434 |
LLM_SERVER_TYPE |
ollama, llamacpp, vllm oder colibri |
ollama |
LLM_API_KEY |
der API-Schlüssel, mit dem llama.cpp, vLLM oder colibri gestartet wurde (--api-key); leer: keiner, bei Ollama immer leer. Ohne Leerzeichen, ", ', #, $, \ und `. |
(leer) |
DEBUG |
immer false |
false |
DB_NAME, DB_USER |
Standard gbe |
gbe |
| Image | ohne Registry gbe:<Tag>, mit Registry-Präfix <Präfix>/gbe:<Tag> |
registry.example.org/gbe/gbe:1.4.0 |
imagePullPolicy |
Always nur mit Registry-Präfix und dem Tag latest, sonst IfNotPresent |
IfNotPresent |
| Upstream des Proxys | gbe_web:8000 (Proxy am selben Docker-Netz), <Adresse>:<Port> von gbe_web (Proxy auf dem Host, Standard 127.0.0.1:8000; bei der Adresse 0.0.0.0 127.0.0.1:<Port>; ein Proxy auf einem anderen Rechner braucht die Adresse des Servers), web:8000 (Manifeste), <fullname>-web:8000 (Helm) |
gbe_web:8000 |
| Body-Grenze | die Upload-Grenze in MB (Standard 50), je nach Proxy in Bytes (52428800) oder KiB (51200). GBE selbst begrenzt Uploads nicht; die Grenze gibt es nur im Proxy. |
50m |
| Timeout | 300 s für Lesen und Senden, weil Dialoggeführt im Web-Request auf das Sprachmodell wartet | 300s |
Helm-fullname |
der Release-Name, wenn er gbe enthält, sonst <Release>-gbe |
gbe |
Die Bedeutung aller Variablen steht unter Umgebungsvariablen und Konfiguration.
Image bauen
docker compose baut das Image selbst (--build, siehe unten). Für Kubernetes und Helm bauen Sie es vorher und schieben es in eine Registry, die der Cluster erreicht:
docker build -t gbe:1.4.0 Backend
docker tag gbe:1.4.0 registry.example.org/gbe/gbe:1.4.0
docker push registry.example.org/gbe/gbe:1.4.0
Der Build braucht Zugriff auf Docker Hub, ghcr.io, die npm-Registry, PyPI, Debian-Spiegel und den Download-Server von Playwright. Ohne Registry laden Sie das Image direkt auf die Knoten, siehe Image ohne Registry.
docker compose
compose.yaml aus dem Repository bleibt unverändert. Sie schreiben daneben:
| Datei | Inhalt |
|---|---|
.env |
Einstellungen und Geheimnisse; Compose liest sie für die Variablen in compose.yaml, gbe_web und gbe_worker bekommen alle Werte über env_file |
compose.override.yaml |
der Teil für den Reverse Proxy; Compose lädt die Datei neben compose.yaml von selbst |
deploy/proxy/<Datei> |
Konfiguration des Proxys; compose.yaml bindet nur das Caddyfile des mitgelieferten Caddy (Modus A) ein, andere Proxys binden ihre Datei selbst ein oder übernehmen den Inhalt |
.env schreiben
Das Beispiel entspricht installer/tests/snapshots/A/.env (mitgelieferter Caddy, https://gbe.example.org). Die Kommentarzeilen sind entbehrlich.
SECRET_KEY=<64 Zeichen>
DEBUG=false
ALLOWED_HOSTS=127.0.0.1,gbe.example.org,localhost
CSRF_TRUSTED_ORIGINS=https://gbe.example.org
DB_NAME=gbe
DB_USER=gbe
DB_PASSWORD=<32 Zeichen>
SANDBOX_TOKEN=<43 Zeichen>
DEFAULT_ADMIN_PASSWORD=<16 Zeichen>
DEFAULT_USER_PASSWORD=<16 Zeichen>
OLLAMA_BASE_URL=http://host.docker.internal:11434
LLM_SERVER_TYPE=ollama
LLM_API_KEY=
GBE_PUBLIC_URL=https://gbe.example.org
GBE_ORCHESTRATOR=compose
GBE_UPLOAD_MAX_MB=50
GBE_TLS_PUBLIC=yes
GBE_IMAGE_TAG=latest
COMPOSE_PROJECT_NAME=gbe
COMPOSE_PROFILES=bundled-proxy
GBE_PROXY_MODE=A
GBE_PROXY_LOCATION=docker
GBE_PROXY_NETWORK=
GBE_HTTP_BIND=0.0.0.0:80
GBE_HTTPS_BIND=0.0.0.0:443
GBE_ACME_EMAIL=admin@example.org
chmod 600 .env
Wer was davon liest:
| Variablen | gelesen von | ohne Wert |
|---|---|---|
DB_PASSWORD, SANDBOX_TOKEN |
Compose (${…:?…} in compose.yaml) sowie GBE bzw. Sandbox |
Compose bricht mit run ./install.sh first ab |
SECRET_KEY |
GBE | fehlt die Zeile, gilt der öffentlich bekannte Schlüssel aus config/settings.py; ist sie leer, bricht Django ab. Immer setzen. |
ALLOWED_HOSTS |
GBE | jede Anfrage endet mit 400, auch der Healthcheck |
CSRF_TRUSTED_ORIGINS |
GBE | Formulare scheitern hinter einem TLS-Proxy mit 403 |
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD |
create_default_users |
öffentlich bekannte Startpasswörter |
OLLAMA_BASE_URL, LLM_SERVER_TYPE, LLM_API_KEY |
GBE beim ersten Start und apply_install_settings |
Standard der Einstellungen; dort änderbar (LLM_API_KEY leer: kein Schlüssel bzw. der gespeicherte bleibt) |
DEBUG, DB_NAME, DB_USER |
GBE, DB_NAME und DB_USER auch gbe_db |
false, gbe, gbe |
GBE_IMAGE_TAG |
Compose (Image gbe:<Tag>) |
latest |
COMPOSE_PROJECT_NAME |
Compose; bestimmt die Volume-Namen <Projekt>_gbe_db, <Projekt>_gbe_media |
gbe (name: in compose.yaml) |
COMPOSE_PROFILES |
Compose; bundled-proxy startet gbe_proxy |
kein gbe_proxy. Nur in Modus A setzen. |
GBE_PUBLIC_URL, GBE_TLS_PUBLIC |
Prüfung (just doctor) |
keine Prüfung der öffentlichen Adresse |
GBE_PROXY_NETWORK |
install.sh (prüft, ob das Netz existiert) |
– |
übrige GBE_… |
nur der Installer, als Vorgabe beim nächsten Lauf | der Installer schlägt seine Standards vor |
Die übrigen GBE_…-Zeilen braucht GBE nicht. Schreiben Sie sie trotzdem, wenn Sie später den Installer verwenden könnten; sonst schlägt er etwa Modus A vor. Je Modus ändern sich nur die Zeilen ab COMPOSE_PROFILES (Vorlage installer/templates/env.j2):
| Modus | Zeilen |
|---|---|
| A | COMPOSE_PROFILES=bundled-proxy, GBE_PROXY_NETWORK= leer, GBE_HTTP_BIND, GBE_HTTPS_BIND, GBE_ACME_EMAIL |
| B, C sowie D–G und I–L mit Proxy in Docker | COMPOSE_PROFILES= leer, GBE_PROXY_NETWORK=<Netz>; bei C zusätzlich GBE_TRAEFIK_ENTRYPOINT, GBE_TRAEFIK_CERTRESOLVER |
| H sowie D–G und I–L mit Proxy auf dem Host | COMPOSE_PROFILES= leer, GBE_PROXY_NETWORK= leer, GBE_WEB_BIND=127.0.0.1:8000 |
| K zusätzlich | GBE_CLOUDFLARE_TUNNEL, GBE_CLOUDFLARE_CREDENTIALS |
D, E, I, L mit https:// zusätzlich |
GBE_TLS_CERT, GBE_TLS_KEY |
F mit https:// zusätzlich |
GBE_TLS_PEM |
GBE_PROXY_MODE ist der Buchstabe, GBE_PROXY_LOCATION ist docker oder host (der Installer schreibt docker auch bei Modi ohne diese Wahl).
compose.override.yaml für den Proxy
Es gibt vier Formen. Kopieren Sie die passende aus den Beispielen und ersetzen Sie Host, Netz und Adressen.
Mitgelieferter Caddy (Modus A). gbe_proxy gehört zum Profil bundled-proxy (COMPOSE_PROFILES=bundled-proxy in der .env) und liest deploy/proxy/Caddyfile. Die Override-Datei setzt nur seine Ports (installer/tests/snapshots/A/compose.override.yaml):
services:
gbe_proxy:
ports: !override
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
- "0.0.0.0:443:443/udp"
Bei http:// bleibt nur die erste Zeile, für localhost mit 127.0.0.1:<Port der Adresse> statt 0.0.0.0:80 (A-local: "127.0.0.1:8080:80"). https:// mit localhost oder 127.0.0.1 geht mit dem mitgelieferten Caddy nicht (der Installer lehnt es ab); nehmen Sie http://localhost:8080 oder einen Servernamen. Das passende deploy/proxy/Caddyfile (A/deploy__proxy__Caddyfile):
{
admin off
email admin@example.org
servers {
timeouts {
read_body 10m
read_header 30s
idle 5m
}
}
}
gbe.example.org {
request_body {
max_size 50MB
}
encode zstd gzip
reverse_proxy gbe_web:8000 {
# Dialoggeführt waits for the language model inside the request.
transport http {
read_timeout 300s
write_timeout 300s
}
}
}
Die Site-Adresse ist bei https:// der Hostname (Caddy holt das Zertifikat bei Let's Encrypt), bei http:// http://<Hostname>, bei localhost :80. Ist der Name aus dem Internet nicht erreichbar, steht im Site-Block zusätzlich tls internal (Caddys eigene CA; in der .env dann GBE_TLS_PUBLIC=no). Ohne E-Mail-Adresse entfällt die Zeile email.
Proxy in Docker an einem gemeinsamen Netz (B, C sowie D–G und I–L mit docker). gbe_web tritt dem Netz des Proxys bei und ist dort als gbe_web:8000 erreichbar, ohne einen Port zu veröffentlichen (D/compose.override.yaml):
services:
gbe_web:
networks: [backend, edge, proxy_net]
networks:
proxy_net:
external: true
Das Netz muss vor dem Start existieren, und der Proxy-Container muss ihm ebenfalls angehören:
docker network inspect proxy_net >/dev/null 2>&1 || docker network create proxy_net
Traefik mit Docker-Labels (C). Dazu kommen Labels an gbe_web (C/compose.override.yaml):
services:
gbe_web:
networks: [backend, edge, proxy_net]
labels:
traefik.enable: "true"
traefik.docker.network: "proxy_net"
traefik.http.routers.gbe.rule: "Host(`gbe.example.org`)"
traefik.http.routers.gbe.entrypoints: "websecure"
traefik.http.routers.gbe.tls: "true"
traefik.http.routers.gbe.tls.certresolver: "le"
traefik.http.routers.gbe.middlewares: "gbe-body,gbe-headers"
traefik.http.middlewares.gbe-body.buffering.maxRequestBodyBytes: "52428800"
traefik.http.middlewares.gbe-body.buffering.memRequestBodyBytes: "1048576"
traefik.http.middlewares.gbe-headers.headers.customrequestheaders.X-Forwarded-Proto: "https"
traefik.http.services.gbe.loadbalancer.server.port: "8000"
networks:
proxy_net:
external: true
Bei http:// entfällt tls: "true", ohne Zertifikatsresolver die Zeile certresolver. Die Antwortzeit begrenzt Traefik am Entrypoint in der statischen Konfiguration; setzen Sie dort mindestens 300 s:
entryPoints:
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: 300s
writeTimeout: 300s
Proxy auf dem Host oder einem anderen Rechner (H sowie D–G und I–L mit host). gbe_web veröffentlicht einen Port (D-host/compose.override.yaml):
services:
gbe_web:
ports:
- "127.0.0.1:8000:8000"
Läuft der Proxy auf einem anderen Rechner, binden Sie an eine Adresse, die er erreicht, tragen in seiner Konfiguration die Adresse dieses Servers als Ziel ein und beschränken den Port per Firewall auf den Proxy; GBE spricht dort unverschlüsseltes HTTP. Der Installer schreibt bei der Adresse 0.0.0.0 das Ziel 127.0.0.1:<Port>, das nur für einen Proxy auf demselben Server passt.
Proxy-Konfiguration
Jeder Proxy muss für GBE:
- alle Pfade an den Upstream weiterreichen (GBE stellt Seiten, statische Dateien und das Handbuch
/docs/selbst zur Verfügung); - Anfragen bis zur Upload-Grenze annehmen (Standard 50 MB);
- 300 s auf Antworten warten (Lesen und Senden);
- den Header
Hostunverändert weitergeben undX-Forwarded-Proto,X-Forwarded-ForundX-Forwarded-Hostsetzen; - bei
https://TLS für den Hostnamen terminieren.
Antworten zu komprimieren ist erwünscht, WebSockets braucht GBE nicht. GET /healthz antwortet mit ok (200) bzw. 503, solange die Datenbank nicht erreichbar ist. Geben Sie /media/ nie direkt aus dem Volume frei: GBE prüft bei jeder Datei die Berechtigung.
Vollständiges Beispiel für nginx in Docker mit https:// (D/deploy__proxy__nginx.conf):
server {
listen 80;
server_name gbe.example.org;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name gbe.example.org;
# adjust to your certificate (e.g. /etc/letsencrypt/live/gbe.example.org/fullchain.pem and privkey.pem)
ssl_certificate /etc/ssl/certs/gbe.pem;
ssl_certificate_key /etc/ssl/private/gbe.key;
client_max_body_size 50m; # uploaded templates and data files
gzip on;
gzip_types application/json text/plain text/css application/javascript image/svg+xml;
location / {
# Docker's DNS, asked per request: nginx starts even while GBE is down.
resolver 127.0.0.11 valid=30s ipv6=off;
set $gbe_upstream http://gbe_web:8000;
proxy_pass $gbe_upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# Dialoggeführt waits for the language model inside the request.
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
Mit nginx auf dem Host (D-host/deploy__proxy__nginx.conf) entfallen resolver und set, dort steht proxy_pass http://127.0.0.1:8000;.
Die übrigen Proxys mit ihren entscheidenden Einstellungen; die vollständigen Dateien stehen in der Vorlage und im Beispiel installer/tests/snapshots/<Modus>/deploy__proxy__<Datei> (Proxy auf dem Host: <Modus>-host):
| Modus | Datei (Vorlage) | Body-Grenze | 300 s | Besonderheiten |
|---|---|---|---|---|
| B Caddy | caddy.snippet (caddy-snippet.j2) |
request_body { max_size 50MB } |
transport http { read_timeout 300s; write_timeout 300s } |
Site-Block wie in Modus A, ohne globale Optionen; per import in Ihr Caddyfile |
| C Traefik, Datei-Provider | traefik-gbe.yaml (traefik-file.yaml.j2) |
Middleware buffering.maxRequestBodyBytes: 52428800 |
respondingTimeouts am Entrypoint |
Alternative zu den Labels; Service-URL http://gbe_web:8000, Middleware mit customRequestHeaders für X-Forwarded-Proto |
| E Apache 2.4 | apache.conf (apache.conf.j2) |
LimitRequestBody 52428800 |
ProxyTimeout 300 |
Module proxy, proxy_http, headers, deflate, bei https ssl und alias; ProxyPreserveHost On, RequestHeader set X-Forwarded-Proto, RequestHeader unset X-Forwarded-For early |
| F HAProxy | haproxy.cfg (haproxy.cfg.j2) |
http-request deny deny_status 413 if { req.hdr_val(content-length) gt 52428800 } |
timeout client und timeout server 300s |
option forwardfor nach del-header X-Forwarded-For; in Docker resolvers mit nameserver docker 127.0.0.11:53 und init-addr last,libc,none; bei https ssl crt mit einer PEM-Datei aus Zertifikat und Schlüssel |
| G Nginx Proxy Manager | nginx-proxy-manager.md (npm.md.j2) |
unter Advanced client_max_body_size 50m; |
proxy_read_timeout 300s;, proxy_send_timeout 300s; |
Proxy Host in der Weboberfläche: Schema http, Ziel gbe_web, Port 8000 (Proxy auf dem Host: Adresse und Port von gbe_web) |
| H anderer Proxy | REQUIREMENTS.md (requirements.md.j2) |
wie oben | wie oben | die Anforderungsliste oben, mit Ihren Werten |
| I Envoy | envoy.yaml (envoy.yaml.j2) |
keine; Envoy reicht den Body gestreamt weiter. Für eine Grenze den Filter envoy.filters.http.buffer mit max_request_bytes: 52428800 vor dem Router ergänzen (Hinweis im Dateikopf). |
route: { cluster: gbe_web, timeout: 300s } |
statische Bootstrap-Konfiguration; use_remote_address: true, Cluster vom Typ STRICT_DNS, Kompression per envoy.filters.http.compressor |
| J Kong (DB-less) | kong.yml (kong.yml.j2) |
Plugin request-size-limiting mit allowed_payload_size: 50 |
read_timeout und write_timeout 300000 (ms) |
preserve_host: true; bei https das Zertifikat in Kong selbst hinterlegen |
| K Cloudflare Tunnel | cloudflared.yml (cloudflared.yml.j2) |
Cloudflare begrenzt auf 100 MB | Cloudflare beendet Anfragen meist nach 100 s | Ingress-Regel hostname → service: http://gbe_web:8000 mit httpHostHeader, danach http_status:404 |
| L lighttpd | lighttpd.conf (lighttpd.conf.j2) |
server.max-request-size = 51200 (KiB) |
server.max-read-idle und server.max-write-idle = 300 |
proxy.forwarded für for/proto/host; lighttpd löst gbe_web beim Start auf, nach dem Neuerzeugen der Container neu laden |
Die Pfade zu Zertifikat und Schlüssel gelten so, wie der Proxy sie sieht (bei einem Proxy-Container also im Container). Der Installer setzt /etc/ssl/certs/gbe.pem und /etc/ssl/private/gbe.key, bei HAProxy /etc/haproxy/certs/gbe.pem.
Konfiguration prüfen
deploy/proxy-check.sh prüft von Hand geschriebene Dateien genauso wie erzeugte, mit dem Image des jeweiligen Werkzeugs (caddy validate, nginx -t, apachectl configtest, haproxy -c, envoy --mode validate, kong config parse, cloudflared tunnel ingress validate, lighttpd -tt, für Kubernetes kubectl kustomize und kubeconform, für Helm helm lint und helm template, dazu docker compose config). GBE muss dafür nicht laufen.
./deploy/proxy-check.sh # die Dateien im Projekt
./deploy/proxy-check.sh render # ein anderes Verzeichnis (relativ zum Repository oder absolut)
Das Skript sucht die Dateien unter denselben Pfaden und Namen, die der Installer schreibt: deploy/proxy/<Datei>, compose.override.yaml (zusammen mit compose.yaml und der .env desselben Verzeichnisses, die deshalb vorhanden sein muss), deploy/k8s/kustomization.yaml mit deploy/k8s/jobs/migrate.yaml und deploy/helm-values.yaml. Die Namen gbe_web, web und gbe-web zeigen dabei auf 127.0.0.1. Ein Wegwerf-Zertifikat liegt nur unter den Standardpfaden aus dem vorigen Abschnitt; nennt Ihre Konfiguration andere Pfade, meldet die Prüfung fehlende Dateien, obwohl die Konfiguration stimmen kann. Alles gültig, endet es mit „proxy-check: alles gültig“, sonst mit „proxy-check: FEHLER“.
Starten
docker compose up -d --build --wait
docker compose exec -T gbe_web python manage.py apply_install_settings
up --build baut das Image gbe:<GBE_IMAGE_TAG> aus Backend/, gbe_worker und gbe_sandbox verwenden dasselbe Image. gbe_web legt beim ersten Start die Datenbank an (migrate) und die Startkonten (create_default_users, beides in entrypoint.sh); --wait wartet, bis alle Dienste gesund sind. apply_install_settings schreibt OLLAMA_BASE_URL und LLM_SERVER_TYPE in die Einstellungen und meldet „Inferenzserver: () — aktualisiert“ bzw. „— unverändert“. Bei der ersten Installation füllt GBE die Einstellungen ohnehin aus diesen Werten; nötig ist der Befehl, wenn Sie die Werte später ändern.
Dieselben beiden Befehle übernehmen auch jede spätere Änderung an .env, compose.override.yaml oder ein neues Image, siehe Aktualisierung und Wartung.
Prüfen
docker compose exec -T gbe_worker python manage.py doctor
docker compose exec -T gbe_sandbox python -m gbe_sandbox.selfcheck gbe_db:5432 gbe_valkey:6379
curl -fsS https://gbe.example.org/healthz # mit Caddys eigener CA zusätzlich -k
doctor endet mit „Alle Prüfungen bestanden.“, der Selbsttest der Sandbox gibt ok aus, /healthz antwortet mit ok. Was die Prüfungen bedeuten, steht unter Installation und Fehlerbehebung.
Kubernetes mit Manifesten
Dateien
Der Installer schreibt deploy/k8s/. Am schnellsten kopieren Sie die Dateien aus installer/tests/snapshots/k8s-<Art>/ (ohne das Präfix deploy__k8s__) und ersetzen Namespace, Image, StorageClass, Größen, Host und Issuer.
| Datei | Inhalt | Werte darin |
|---|---|---|
namespace.yaml |
Namespace | Namespace |
storage.yaml |
PVC gbe-media (Vorlagen, Berichte) |
Zugriffsart, Größe (20Gi), StorageClass |
db.yaml |
Service und StatefulSet db (postgres:18-alpine) mit volumeClaimTemplates pgdata |
POSTGRES_USER, POSTGRES_DB und die Argumente von pg_isready (gleich DB_USER und DB_NAME), Größe (10Gi), StorageClass |
valkey.yaml |
Service und Deployment valkey (valkey/valkey:8-alpine) |
– |
backend.yaml |
Service web und Deployments web und worker |
Image (zweimal), imagePullPolicy, Zugriffsart des Medien-Volumes |
sandbox.yaml |
Service und Deployment sandbox, NetworkPolicies db-ingress, valkey-ingress, sandbox-ingress |
Image |
ingress.yaml |
Eingang in den Cluster, siehe unten; entfällt bei none |
Host, TLS, Issuer, IngressClass |
kustomization.yaml |
Namespace, Liste der Ressourcen, Secret gbe-env aus gbe.env |
Namespace |
gbe.env |
Kopie der .env, Quelle des Secrets; Rechte 600 |
– |
jobs/migrate.yaml |
Job gbe-migrate: migrate, create_default_users, apply_install_settings; nicht Teil der Kustomization |
Image, Namespace (steht in der Datei) |
kustomization.yaml aus k8s-nginx:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: gbe
resources:
- namespace.yaml
- storage.yaml
- db.yaml
- valkey.yaml
- backend.yaml
- sandbox.yaml
- ingress.yaml
secretGenerator:
- name: gbe-env
envs: [gbe.env] # a copy of .env, written by the installer (keep it private, like .env)
generatorOptions:
disableNameSuffixHash: true # the migrate Job refers to it by name; pods roll on the env-hash annotation
Secret gbe-env
gbe.env enthält dieselben GBE-Variablen wie die .env bei docker compose (SECRET_KEY bis LLM_API_KEY), ohne die COMPOSE_…-Zeilen; OLLAMA_BASE_URL muss aus dem Cluster erreichbar sein. Web, Worker und der Migrations-Job bekommen alle Werte über envFrom, die Datenbank nur DB_PASSWORD, die Sandbox nur SANDBOX_TOKEN. Kustomize liest gbe.env nur aus dem Verzeichnis der Kustomization:
cp .env deploy/k8s/gbe.env
chmod 600 deploy/k8s/gbe.env
Wollen Sie keine Geheimnisse in einer Datei unter deploy/k8s/, entfernen Sie secretGenerator und generatorOptions aus der Kustomization und legen das Secret selbst an, zum Ändern mit apply:
kubectl create namespace gbe
kubectl -n gbe create secret generic gbe-env --from-env-file=.env --dry-run=client -o yaml | kubectl apply -f -
Die Pod-Vorlagen von web, worker und sandbox tragen die Annotation gbe/env-hash (der Installer setzt die ersten 16 Zeichen des SHA-256 der .env ohne den Zeitstempel im Kopf); ein neuer Wert rollt die Pods neu aus. Ohne diese Annotation starten Sie die Pods nach einer Änderung des Secrets mit kubectl rollout restart neu.
Stolperfallen
enableServiceLinks: falsesteht in jedem Pod. Sonst setzt Kubernetes für jeden Service Variablen wieSANDBOX_PORT=tcp://…;gbe_sandboxliestSANDBOX_PORTals seinen Port und startet nicht.- Proben mit
Host: localhost: Startup-, Readiness- und Liveness-Probe vonwebrufen/healthzmit dem HeaderHost: localhostauf. Ohne ihn sendet das Kubelet die Pod-Adresse, die nicht inALLOWED_HOSTSsteht, und erhält 400. - Medien-Volume: Mit
ReadWriteOncehabenwebundworkerdie StrategieRecreate, undworkerhat einepodAffinityzum Web-Pod (topologyKey: kubernetes.io/hostname), weil das Volume nur auf einem Knoten eingehängt werden kann. MitReadWriteManyentfallen beide. - NetworkPolicies:
dbundvalkeynehmen nur Verbindungen von Pods mit den Labelsgbe/db-access: "true"undapp.kubernetes.io/part-of: gbean (web,worker, Jobgbe-migrate),sandboxnur vonworker. Ein eigener Pod, der die Datenbank braucht, benötigt dieselben Labels. Wirksam sind die Policies nur mit einem CNI, das sie umsetzt. - PostgreSQL 18 legt seine Daten in einem versionierten Verzeichnis unterhalb von
/var/lib/postgresqlab; das Volume wird deshalb dort eingehängt, nicht unter/var/lib/postgresql/datawie bei früheren Versionen des Images. - Sandbox: läuft als Benutzer 65534 mit schreibgeschütztem Wurzeldateisystem,
emptyDirunter/tmp, ohne Capabilities, mitSANDBOX_MEMORY_MB=4096und einer Speichergrenze von6Gi.automountServiceAccountToken: falsegilt für alle GBE-Pods. imagePullPolicysteht ausdrücklich in jedem Pod. Ohne Registry und mit dem Taglatestwäre der StandardAlways, und der Knoten versuchte,gbe:latestvon Docker Hub zu holen.
Eingang in den Cluster
| Art | ingress.yaml enthält |
Vorlage |
|---|---|---|
caddy |
ConfigMap caddyfile (Caddyfile wie Modus A mit Upstream web:8000, mit E-Mail-Adresse für Let's Encrypt in der globalen Option email), PVC caddy-data (1 Gi, Zertifikate), Service proxy vom Typ LoadBalancer (Port 80, bei https:// außer mit localhost auch 443), Deployment proxy (caddy:2-alpine, Strategie Recreate) |
k8s/ingress-caddy.yaml.j2 |
nginx |
Ingress mit nginx.ingress.kubernetes.io/proxy-body-size: "50m", proxy-read-timeout: "300", proxy-send-timeout: "300" |
k8s/ingress-nginx.yaml.j2 |
traefik |
Middleware gbe-body (buffering.maxRequestBodyBytes: 52428800) und Ingress mit traefik.ingress.kubernetes.io/router.middlewares: "<Namespace>-gbe-body@kubernetescrd", bei https:// router.tls: "true"; respondingTimeouts am Entrypoint wie bei Compose |
k8s/ingress-traefik.yaml.j2 |
haproxy |
Ingress mit haproxy.org/timeout-server: "300s" |
k8s/ingress-haproxy.yaml.j2 |
gateway |
HTTPRoute gbe mit parentRefs auf Ihr Gateway, timeouts: { request: 300s }; TLS und Body-Grenze am Gateway |
k8s/ingress-gateway.yaml.j2 |
none |
keine Datei; leiten Sie Ihren Proxy auf den Service web, Port 8000 |
requirements.md.j2 |
Bei nginx, traefik und haproxy enthält der Ingress mit https:// einen tls-Block mit dem Secret gbe-tls, mit ClusterIssuer zusätzlich cert-manager.io/cluster-issuer: "<Issuer>", und mit IngressClass ingressClassName. Der Ingress für ingress-nginx (k8s-nginx/deploy__k8s__ingress.yaml):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gbe
labels:
app.kubernetes.io/name: ingress
app.kubernetes.io/part-of: gbe
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
cert-manager.io/cluster-issuer: "letsencrypt"
spec:
tls:
- hosts: [gbe.example.org]
secretName: gbe-tls
rules:
- host: gbe.example.org
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: web, port: { number: 8000 } } }
Ohne Issuer legen Sie das TLS-Secret selbst an: kubectl -n gbe create secret tls gbe-tls --cert=… --key=….
Image ohne Registry
Ohne Registry-Präfix heißt das Image gbe:<Tag> und muss auf jedem Knoten liegen, der GBE-Pods ausführt:
docker build -t gbe:1.4.0 Backend
kind load docker-image gbe:1.4.0 --name kind # kind
docker save gbe:1.4.0 | sudo k3s ctr images import - # k3s, auf jedem Knoten
Das Image steht in backend.yaml (zweimal), sandbox.yaml und jobs/migrate.yaml.
Anwenden
kubectl apply -k deploy/k8s
kubectl -n gbe rollout status deployment web --timeout=15m
kubectl -n gbe delete job gbe-migrate --ignore-not-found
kubectl apply -f deploy/k8s/jobs/migrate.yaml
kubectl -n gbe wait --for=condition=complete job/gbe-migrate --timeout=900s
kubectl -n gbe logs job/gbe-migrate --tail=20
kubectl -n gbe rollout restart deployment web worker sandbox
web migriert beim Start selbst (entrypoint.sh); rollout status wartet darauf, damit Web-Pod und Job nie gleichzeitig migrieren. Ein Job lässt sich nicht ändern, deshalb wird der vorige Lauf erst gelöscht. Der Job hängt kein Medien-Volume ein und kann deshalb auch mit ReadWriteOnce auf jedem Knoten laufen. Der Neustart am Ende sorgt dafür, dass ein neu gebautes Image unter demselben Tag verwendet wird; mit Registry und einem anderen Tag als latest (IfNotPresent) behält der Knoten aber das zwischengespeicherte Image, dort hilft nur ein neuer Tag. Bei späteren Änderungen wiederholen Sie dieselben Befehle.
Prüfen
kubectl -n gbe get pods,svc,pvc,ingress -o wide
kubectl -n gbe exec deploy/worker -- python manage.py doctor
kubectl -n gbe exec deploy/sandbox -- python -m gbe_sandbox.selfcheck db:5432 valkey:6379
Helm
Werte-Datei
Das Chart liegt in deploy/helm/gbe; seine Standardwerte stehen in deploy/helm/gbe/values.yaml. Der Installer schreibt deploy/helm-values.yaml (Rechte 600, enthält Geheimnisse). Das Beispiel installer/tests/snapshots/helm-nginx/deploy__helm-values.yaml mit Platzhaltern statt Geheimnissen:
host: "gbe.example.org"
publicScheme: https
publicPort: 443
image:
repository: "registry.example.org/gbe/gbe"
tag: "1.4.0"
pullPolicy: IfNotPresent
ollamaBaseUrl: "http://ollama.ai:11434"
llmServerType: vllm
llmApiKey: ""
secretKey: "<64 Zeichen>"
sandbox:
token: "<43 Zeichen>"
startAccounts:
adminPassword: "<16 Zeichen>"
userPassword: "<16 Zeichen>"
database:
name: gbe
user: gbe
password: "<32 Zeichen>"
storageSize: 10Gi
media:
storageSize: 20Gi
storageClass: "fast"
ingress:
enabled: true
className: ""
annotations: # body limit for uploads, long timeouts (Dialoggeführt waits for the language model)
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
cert-manager.io/cluster-issuer: "letsencrypt"
tls:
enabled: true
secretName: "gbe-tls"
httpRoute:
enabled: false
parentRefs: []
chmod 600 deploy/helm-values.yaml
| Schlüssel | Bedeutung |
|---|---|
host, publicScheme, publicPort |
öffentliche Adresse; daraus bildet das Chart ALLOWED_HOSTS (<host>,localhost,127.0.0.1) und CSRF_TRUSTED_ORIGINS (mit Port, wenn er nicht der Standardport ist) |
image.repository, image.tag, image.pullPolicy |
<Präfix>/gbe (ohne Registry gbe), Tag, Pull-Policy nach der Regel oben; repository ist Pflicht |
ollamaBaseUrl, llmServerType |
Inferenzserver, aus dem Cluster erreichbar; ollamaBaseUrl ist Pflicht |
llmApiKey |
API-Schlüssel des Inferenzservers; leer: keiner. Steht im Secret <fullname>-env (Schlüssel LLM_API_KEY, nur wenn gesetzt) |
secretKey, sandbox.token, database.password |
Geheimnisse; Pflicht, außer mit existingSecret |
startAccounts.adminPassword, startAccounts.userPassword |
Startpasswörter; leer gelten die öffentlich bekannten |
database.name, database.user, database.storageSize, media.storageSize, storageClass |
Datenbank und Volumes; leere StorageClass: Standard des Clusters |
ingress.enabled, ingress.className, ingress.annotations |
Ingress für nginx, traefik und haproxy; bei gateway und none enabled: false |
ingress.tls.enabled, ingress.tls.secretName |
TLS am Ingress bei https://; der Installer setzt gbe-tls (Standard des Charts: <fullname>-tls) |
httpRoute.enabled, httpRoute.parentRefs |
HTTPRoute für gateway mit - name: <Gateway> und namespace: <Namespace>; hat Vorrang vor dem Ingress |
Annotationen je Controller, wie sie der Installer setzt:
| Eingang | ingress.annotations |
|---|---|
nginx |
nginx.ingress.kubernetes.io/proxy-body-size: "50m", proxy-read-timeout: "300", proxy-send-timeout: "300" |
haproxy |
haproxy.org/timeout-server: "300s" |
traefik |
bei https:// traefik.ingress.kubernetes.io/router.tls: "true", sonst {}; respondingTimeouts am Entrypoint von Traefik auf mindestens 300 s. Eine Body-Grenze gibt es hier nicht: Das Chart legt keine Middleware an, und Traefik begrenzt von sich aus nicht. |
gateway, none |
{} |
Mit https:// und ClusterIssuer kommt bei nginx, haproxy und traefik cert-manager.io/cluster-issuer: "<Issuer>" dazu. Den Timeout von 300 s setzt das Chart in der HTTPRoute selbst.
Weitere Schlüssel des Charts, die der Installer nicht setzt: imagePullSecrets, existingSecret, sandbox.memoryMb (4096), sandbox.resources, networkPolicy.enabled (true), worker.enabled (true) und resources.web, resources.worker, resources.postgres, resources.valkey.
Geheimnisse in einem eigenen Secret
Statt die Geheimnisse in die Werte-Datei zu schreiben, verweisen Sie mit existingSecret auf ein Secret mit den Schlüsseln SECRET_KEY, DB_PASSWORD und SANDBOX_TOKEN sowie DEFAULT_ADMIN_PASSWORD und DEFAULT_USER_PASSWORD:
kubectl create namespace gbe
kubectl -n gbe create secret generic gbe-secrets --from-env-file=gbe-secrets.env
gbe-secrets.env enthält die fünf Zeilen SCHLÜSSEL=Wert, bei einem Inferenzserver mit API-Schlüssel als sechste LLM_API_KEY=…; löschen Sie die Datei danach oder geben Sie ihr die Rechte 600. In der Werte-Datei steht dann existingSecret: gbe-secrets; secretKey, sandbox.token, database.password, startAccounts und llmApiKey entfallen, das Chart legt kein eigenes Secret an.
Warning
Startpasswörter im eigenen Secret
Für das Chart sind
DEFAULT_ADMIN_PASSWORDundDEFAULT_USER_PASSWORDoptional. Fehlen sie im Secret, legtcreate_default_usersdie Startkonten mit den öffentlich bekannten Passwörtern an. Ein geändertes eigenes Secret rollt die Pods nicht neu aus (die Prüfsummechecksum/secretsieht es nicht); starten Sie sie danach mitkubectl rollout restartneu.
Installieren
Die Objektnamen beginnen mit dem fullname (Release gbe → gbe, Release prod → prod-gbe):
helm upgrade --install gbe deploy/helm/gbe -n gbe --create-namespace -f deploy/helm-values.yaml --wait --timeout 15m
kubectl -n gbe rollout restart deployment gbe-app gbe-sandbox
kubectl -n gbe rollout status deployment gbe-app --timeout=15m
kubectl -n gbe exec deploy/gbe-app -c web -- python manage.py apply_install_settings
Web und Worker laufen als Container web und worker im Pod <fullname>-app; der Container web migriert beim Start selbst, einen Migrations-Job gibt es bei Helm nicht. Der Neustart nach helm upgrade sorgt wie bei den Manifesten für ein neu gebautes Image unter demselben Tag. Nach der Installation zeigt Helm die Hinweise aus NOTES.txt mit den Befehlen für Status und Protokolle.
Prüfen
helm -n gbe status gbe
kubectl -n gbe get pods -l app.kubernetes.io/instance=gbe
kubectl -n gbe exec deploy/gbe-app -c worker -- python manage.py doctor
kubectl -n gbe exec deploy/gbe-sandbox -- python -m gbe_sandbox.selfcheck gbe-postgres:5432 gbe-valkey:6379
Die Werte-Datei prüfen Sie vorab mit helm lint deploy/helm/gbe -f deploy/helm-values.yaml und helm template gbe deploy/helm/gbe -f deploy/helm-values.yaml.
Befehle ohne just
just liest die .env und nimmt daraus Namespace (GBE_K8S_NAMESPACE), Release (GBE_HELM_RELEASE), fullname (GBE_HELM_FULLNAME) und Tag (GBE_IMAGE_TAG). Die Tabelle zeigt die Standardwerte gbe und latest.
| Rezept | Befehle |
|---|---|
just install |
./install.sh |
just up |
docker compose up -d --build --wait, dann docker compose exec -T gbe_web python manage.py apply_install_settings |
just down |
docker compose down |
just ps |
docker compose ps |
just logs <Dienst> |
docker compose logs -f --tail=200 <Dienst> |
just doctor |
die drei Befehle unter „Prüfen“ bei docker compose |
just backup [--encrypt] [Verzeichnis] |
deploy/backup.sh backup [--encrypt] [Verzeichnis], siehe Sicherung und Wiederherstellung (die Einzelschritte unter „Von Hand sichern“) |
just restore [--force] [--no-config] <Archiv> |
deploy/backup.sh restore [--force] [--no-config] <Archiv> |
just proxy-check |
./deploy/proxy-check.sh . |
just proxy-check-all |
./deploy/proxy-check.sh --all |
just k8s-build |
docker build -t gbe:latest Backend; mit Registry-Präfix zusätzlich docker tag und docker push nach <Präfix>/gbe:latest |
just k8s-load <Cluster> |
kind load docker-image gbe:latest --name <Cluster> |
just k8s-up |
kubectl apply -k deploy/k8s, dann kubectl -n gbe rollout restart deployment web worker sandbox |
just k8s-migrate |
die fünf Befehle von rollout status deployment web bis logs job/gbe-migrate unter „Anwenden“ |
just k8s-status |
kubectl -n gbe get pods,svc,pvc,ingress -o wide |
just k8s-logs <Teil> |
kubectl -n gbe logs -f --tail=200 -l app.kubernetes.io/name=<Teil> |
just k8s-doctor |
die beiden exec-Befehle unter „Prüfen“ bei den Manifesten |
just k8s-backup …, just k8s-restore … |
deploy/backup.sh backup --k8s … bzw. deploy/backup.sh restore --k8s … |
just k8s-down |
kubectl -n gbe delete deployment,statefulset,service,ingress,job,networkpolicy,configmap -l app.kubernetes.io/part-of=gbe, dann kubectl -n gbe delete httproute,middlewares.traefik.io -l app.kubernetes.io/part-of=gbe (Fehler dabei sind unerheblich); PVCs, Secret und Namespace bleiben |
just helm-up |
die vier Befehle unter „Installieren“ bei Helm |
just helm-status |
helm -n gbe status gbe, kubectl -n gbe get pods -l app.kubernetes.io/instance=gbe |
just helm-doctor |
die beiden exec-Befehle unter „Prüfen“ bei Helm; braucht worker.enabled: true |
just helm-backup …, just helm-restore … |
deploy/backup.sh backup --helm … bzw. deploy/backup.sh restore --helm … |
just helm-down |
helm -n gbe uninstall gbe; die PVCs bleiben |
Was der Installer zusätzlich tut
Beim manuellen Weg übernehmen Sie auch das, was der Installer nebenbei erledigt:
- Prüfungen vor dem Start: belegte Ports (Modus A: die HTTP- und HTTPS-Adressen; H und Proxy auf dem Host: der Port von
gbe_web), bei Kubernetes eine fehlende Adresse des Inferenzservers, in Modus A einehttps://-Adresse mitlocalhost, vorhandene Werkzeuge (install.shprüftdockerundjustvorab,kubectlundhelmvor dem Start). Prüfen Sie Ports etwa mitss -ltn. - Gedächtnis: Die
.envist die Vorgabe für den nächsten Lauf; Geheimnisse bleiben erhalten,--rotate-secretserneuertSECRET_KEYundSANDBOX_TOKEN. - Sicherungen: Vor jeder Änderung einer erzeugten Datei legt er
<Datei>.bak-JJJJMMTT-HHMMSSan; unveränderte Dateien schreibt er nicht neu. - Abgeleitete Werte und Prüfsummen:
ALLOWED_HOSTS,CSRF_TRUSTED_ORIGINS, Upstreams, Body-Grenzen, die Annotationengbe/env-hashundgbe/caddyfile-hashsowie die Kopiedeploy/k8s/gbe.enventstehen bei jedem Lauf neu aus denselben Antworten. - Rechte 600 für
.env,deploy/k8s/gbe.envunddeploy/helm-values.yaml. - Übernahme des alten Installers aus
deploy/generated/, siehe Installation. - Hinweise: gefundene Proxys, die „Nächste Schritte:“ und die Prüfung des Docker-Netzes vor
docker compose up.
Weiterführend
Nach der Installation im Handbuch unter /docs/ (Kapitel „Administration“) oder direkt in
den Quellen: