Klein genug für jede Umgebung
GoModel ist ein einzelnes statisches Go-Binary ohne Laufzeitabhängigkeiten. Es wird mitCGO_ENABLED=0 gebaut und auf distroless/static ausgeliefert. Der
Container hat daher keine Shell, keinen Paketmanager und keine libc, die
gepatcht werden müsste.
Diese Werte stammen von der Seite Benchmarks und wurden
auf identischer Hardware im Vergleich mit anderen Gateways gemessen.
Praktisch bedeutet das: GoModel passt problemlos dorthin, wo Größe und
Kaltstarts bestraft werden – Cloud Run, Fargate, Container Apps, Fly.io,
Container-Lambdas oder eine kleine VM neben Ihrer Anwendung. Es benötigt keinen
eigenen Node-Pool.
Ein Storage-Backend wählen
GoModel speichert Audit-Logs, Nutzungsdaten, Budgets, virtuelle Modelle, verwaltete API-Keys und weiteren Admin-Zustand überSTORAGE_TYPE.
SQLite ist der Standardwert, damit GoModel ohne jede Konfiguration
funktioniert, und für eine einzelne Instanz mit persistentem Volume eine echte
Option für die Produktion. Es spart einen Netzwerk-Hop und eine zu betreibende
Komponente.
In zwei Fällen ist es nicht mehr tragfähig: bei mehr als einem Replikat und bei
jeder Laufzeitumgebung, deren Dateisystem einen Neustart nicht übersteht. Zwei
SQLite-Replikate teilen sich keine Datenbank, sondern haben jeweils eine eigene.
Nutzung, Budgets und verwaltete API-Keys laufen daher unbemerkt auseinander.
Ein auf einem Pod erstellter Key existiert auf dem anderen nicht.
SQLite läuft außerdem bewusst mit einer einzigen offenen Verbindung, um einen
einzigen Schreiber zu haben und Lock-Konflikte zu vermeiden. Jeder Flush von
Audit- und Nutzungsdaten und jede Budget-Prüfung wird darüber serialisiert, was
unter Dauerlast eine Obergrenze für den Durchsatz darstellt.
Für Postgres ist
POSTGRES_MAX_CONNS standardmäßig 10. Erhöhen Sie diesen
Wert zuerst, wenn Sie hohe Parallelität mit aktivierten Budgets oder
Rate-Limits betreiben.
Der Standardpfad für SQLite ist ./data/gomodel.db, wenn neben dem Binary
bereits ein Verzeichnis ./data existiert, andernfalls das
benutzerspezifische Datenverzeichnis des Betriebssystems. Der Standardpfad
hängt damit vom Arbeitsverzeichnis ab; setzen Sie SQLITE_PATH in der
Produktion daher explizit. Der aufgelöste Pfad wird beim Start als
storage configured ausgegeben. Siehe
Wo speichert GoModel seine Daten?.
Redis
Redis ist optional. Es dient als Speicher für den gemeinsamen Snapshot des Modellkatalogs und den exakten Response-Cache. Ohne Redis verwendet GoModel einen prozessinternen Cache und einen lokalen Datei-Cache. Das ist korrekt, wird aber nicht zwischen Replikaten geteilt. Fügen Sie Redis hinzu, wenn mehrere Replikate Discovery-Ergebnisse und Cache-Treffer teilen sollen.Ist ein Redis-Modell-Cache konfiguriert und Redis beim Start nicht
erreichbar, startet das Gateway nicht. Ein späterer Verlust von Redis ist nur
ein eingeschränkter Zustand. Berücksichtigen Sie das bei der Startreihenfolge
der Services.
Provider-Zugangsdaten
GoModel bezieht Provider-Zugangsdaten aus Umgebungsvariablen, ausconfig.yaml oder von der Seite Providers im Dashboard. Alle drei Wege
funktionieren, bergen aber nicht dasselbe Risiko.
Konfigurieren Sie Provider in der Produktion über Umgebungsvariablen und
beziehen Sie die Werte aus Ihrem Secret-Manager. Injizieren Sie sie mit AWS
Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault oder
einem Kubernetes-Secret, das vom External Secrets Operator oder vom Secrets
Store CSI Driver befüllt wird.
Dafür gibt es zwei Gründe, und der zweite wird oft übersehen.
Secrets bleiben aus der Datenbank heraus. Im Dashboard gespeicherte
Provider-Zugangsdaten werden im Speicher provider_credentials persistiert,
Header von MCP-Servern und Secrets von Guardrails in ihren jeweiligen
Speichern. Sofern GOMODEL_ENCRYPTION_KEY nicht gesetzt ist, werden sie im
Klartext gespeichert: Die Maskierung mit *** wird nur beim Lesen angewendet,
sodass der gespeicherte Wert für jeden lesbar ist, der Zugriff auf die
Datenbankdatei, ein Backup, einen Snapshot oder ein Lesereplikat hat. Mit
gesetztem Key werden sie verschlüsselt gespeichert (siehe
Verschlüsselung ruhender Daten), der Key
muss dann aber geschützt und aufbewahrt werden. Zugangsdaten, die über
Umgebungsvariablen oder config.yaml deklariert werden, werden überhaupt nie
in den Speicher geschrieben.
In der Konfiguration deklarierte Provider lassen sich zur Laufzeit nicht
bearbeiten. Ein in Umgebungsvariablen oder config.yaml deklarierter
Provider ist im Dashboard schreibgeschützt. Ein in der Datenbank gespeicherter
Provider kann von jedem bearbeitet werden, der einen Key mit Dashboard-Zugriff
besitzt – einschließlich seiner base_url. Wird base_url auf einen vom
Angreifer kontrollierten Host umgebogen, landen jeder Prompt und der
injizierte Upstream-Key unbemerkt dort. Werden Provider als Code deklariert,
kann eine solche Änderung nur über Ihre Deployment-Pipeline erfolgen.
Wenn Ihre Secret-Werkzeuge Dateien einbinden, verweisen Sie mit einer
Secret-Referenz auf die Datei, statt den
Inhalt in eine Umgebungsvariable zu kopieren:
GOMODEL_ENCRYPTION_KEY und
behandeln Sie die Datenbank trotzdem als sensibel: Verschlüsseln Sie das
Volume, beschränken Sie den Zugriff und stufen Sie Backups entsprechend ein.
Weiterer Umgang mit Zugangsdaten
- Verwaltete API-Keys werden als SHA-256-Hash eines zufälligen Secrets mit hoher Entropie gespeichert, niemals wiederherstellbar. Der Klartext wird einmalig bei der Erstellung angezeigt. Das unterscheidet sich bewusst von Provider-Zugangsdaten.
- Header mit Client-Zugangsdaten werden vor dem Persistieren geschwärzt.
Mit
LOGGING_LOG_HEADERS=truewerdenAuthorization,x-api-key,Cookieund die übrigen Header mit Zugangsdaten beim Schreiben durch[REDACTED]ersetzt, sodass der Audit-Speicher sie nie erhält. - Setzen Sie
GOMODEL_MASTER_KEY. Ist er leer und gibt es keine verwalteten Keys, wird jeder Request ohne Authentifizierung zugelassen, und/admin/*wird als Wiederherstellungspfad bei Aussperrung in die Liste der von der Authentifizierung ausgenommenen Pfade aufgenommen. Ein aus dem Internet erreichbares Gateway in diesem Zustand gibt jedem Zugriff auf die vollständige Admin API.
TLS
GoModel terminiert kein TLS. Es liefert einfaches HTTP aus und erwartet einen Reverse Proxy oder Load Balancer davor. Betreiben Sie einen solchen bei jedem Deployment, das nicht nur auf Loopback läuft: Ohne ihn gehen Client-API-Keys, verwaltete Gateway-Keys und der Master-Key im Klartext über das Netzwerk. Es gibt außerdem keine CORS-Middleware, sodass Browser-Clients von einem anderen Origin das Gateway nicht direkt aufrufen können. Ergänzen Sie die Header bei Bedarf in Ihrem Proxy.Upstream-Zertifikate hinter einer privaten CA
Ausgehendes HTTPS – zu Providern und MCP-Servern – wird gegen das CA-Bundle des Systems geprüft. Die Container-Images liefern es mit (/etc/ssl/certs/ca-certificates.crt), sodass öffentliche Endpunkte keine
Einrichtung benötigen.
Bei einem Upstream, dessen Zertifikat von einer internen oder
unternehmenseigenen CA ausgestellt wurde, schlägt die Verbindung mit
x509: certificate signed by unknown authority fehl. Binden Sie das
CA-Zertifikat in das vertrauenswürdige Verzeichnis ein, das GoModel zusätzlich
zu den öffentlichen Root-Zertifikaten liest:
SSL_CERT_FILE funktioniert ebenfalls, ersetzt aber die Bundle-Datei, statt
sie zu ergänzen. Verweisen Sie damit also auf ein Bundle, das auch die
benötigten öffentlichen Root-Zertifikate enthält.
Air-Gapped- und Offline-Deployments
GoModel läuft vollständig air-gapped. Es gibt keine Telemetrie, keine Analytics und keine Lizenzprüfung. Das Admin-Dashboard ist in das Binary eingebettet und seine Schriftarten werden mitgeliefert, sodass die Oberfläche keine CDN-Ressourcen lädt. In Kombination mit lokalen Modellservern wie Ollama, SGLang oder vLLM benötigt das Gateway keine Route ins öffentliche Internet. Standardmäßig führt das Gateway genau zwei ausgehende Aufrufe durch, die nicht an einen konfigurierten Provider gehen, und ein einziger Schalter deaktiviert beide:GOMODEL_OFFLINE=true (oder offline: true in config.yaml) deaktiviert die
Update-Prüfung und verwirft jede HTTP-URL für den Katalog. Die Einstellung wird
nach allen anderen angewendet, sodass nichts in config.yaml oder in der
Umgebung darunter einen ausgehenden Aufruf wieder aktivieren kann. Aufrufe an
konfigurierte Provider und an alles, was Sie selbst deklariert haben – etwa
einen OTLP-Collector, MCP-Upstreams oder einen Vector Store –, bleiben
unberührt. Das Startprotokoll bestätigt den Modus.
Sie können die beiden Aufrufe auch einzeln steuern, mit
GOMODEL_VERSION_CHECK_ENABLED=false und MODEL_LIST_URL=off.
Der Abruf des Katalogs erfolgt nach dem Best-Effort-Prinzip. Ist er
abgeschaltet oder schlägt er fehl, startet das Gateway normal, /health und
/health/ready liefern 200, die Provider-Discovery funktioniert weiterhin,
weil die Modelllisten vom jeweiligen /models-Endpunkt des Providers stammen,
und Requests werden normal geroutet. Was verloren geht, ist die Anreicherung
mit Metadaten.
Preise lassen sich in einer Air-Gapped-Umgebung auf drei Arten erhalten:
-
Den Katalog als Datei mitliefern. Laden Sie
models.min.jsonaußerhalb der Air-Gapped-Umgebung herunter, kopieren Sie die Datei hinein und richten Sie das Gateway darauf aus. Eine lokale Datei wird gelesen, nie abgerufen, und bleibt daher auch mitGOMODEL_OFFLINE=trueaktiv:Die Datei wird bei jeder Cache-Aktualisierung neu gelesen. Wenn Sie sie auf der Festplatte ersetzen, werden die Preise ohne Neustart aktualisiert. -
Den Katalog spiegeln. Stellen Sie die Datei über einen internen
HTTP-Host bereit und setzen Sie
MODEL_LIST_URLauf diese URL. Lassen SieGOMODEL_OFFLINEungesetzt und deaktivieren Sie die Update-Prüfung separat, oder richten Sie auchGOMODEL_VERSION_CHECK_URLauf einen internen Spiegel aus. -
Preise pro Modell deklarieren unter
providers.<name>.models[].metadata.pricinginconfig.yaml.
Wenn Sie
MODEL_LIST_URL auf einen leeren String setzen, wird der Download
nicht deaktiviert: Leere Umgebungswerte werden übersprungen, sodass die
Standard-URL erhalten bleibt. Verwenden Sie off oder
GOMODEL_OFFLINE=true.169.254.169.254) nach
Zugangsdaten abfragen. Eine Air-Gapped-Installation würde Bedrock nicht
konfigurieren, aber es ist gut, das zu wissen.
Mehr als ein Replikat betreiben
Die Request-Verarbeitung ist zustandslos, sodass Replikate für gewöhnlichen Traffic keine Affinität benötigen. Mehrere Features halten jedoch Zustand im Prozessspeicher und stimmen sich nicht zwischen Instanzen ab.
Überraschend sind meist die Rate-Limits: Sie sind eine prozessinterne
Kontrolle, sodass drei Replikate mit einer Regel von 100 rpm insgesamt bis zu
300 rpm zulassen. Bemessen Sie Regeln pro Replikat und verwenden Sie
Budgets als dauerhafte, instanzübergreifende Kontrolle,
da sie aus der gemeinsamen Datenbank lesen.
Konfigurationsänderungen werden nicht sofort verteilt
Vom Admin verwaltete Konfiguration liegt dauerhaft in der Datenbank, wird auf jedem Replikat aber aus einem In-Memory-Snapshot bedient. Eine im Dashboard gespeicherte Änderung aktualisiert das Replikat, das den Request verarbeitet hat, sofort. Andere Replikate übernehmen sie in ihrem eigenen Aktualisierungsrhythmus – und zwei Bereiche haben gar keinen.Health-Probes und Herunterfahren
GoModel stellt Liveness und Readiness getrennt bereit, und das Binary kann sich selbst prüfen. Das funktioniert auch in einem Distroless-Image ohne Shell odercurl.
Richten Sie die Kubernetes-Readiness auf
/health/ready aus, nicht auf
/health. Das Helm-Chart im Verzeichnis helm/ des Repositorys erledigt das
bereits, führt das Gateway als Nicht-Root-Benutzer des Images auf einem
schreibgeschützten Dateisystem aus, hält SQLite auf einem persistenten Volume
und verweigert mehr als ein Replikat, bis Sie storage.type auf postgresql
oder mongodb umstellen. Die Werte finden Sie in helm/README.md.
Readiness wartet nicht auf den Modellkatalog, der asynchron geladen wird.
Ein frischer Pod kann „ready“ melden, während
GET /v1/models noch leer ist
und die ersten Requests fehlschlagen. Berücksichtigen Sie das in den
Rollout-Einstellungen oder wärmen Sie Pods vor, bevor Sie Traffic umleiten.SIGTERM lässt das Gateway laufende HTTP-Arbeit 10 Sekunden lang
auslaufen und schließt den Abbau dann innerhalb von 30 Sekunden ab, wobei
gepufferte Nutzungs- und Audit-Einträge geschrieben werden. Keiner der beiden
Timeouts ist konfigurierbar. Laufende gestreamte Responses werden nach 10
Sekunden abgebrochen, sodass eine lange Completion während eines Rolling
Deployments abgeschnitten wird. Setzen Sie terminationGracePeriodSeconds auf
mehr als 30 (45 ist eine sinnvolle Wahl), damit der Abbau nicht mit SIGKILL um
die Wette läuft.
Datenaufbewahrung und Festplattenwachstum
Audit-Logging und Nutzungserfassung schreiben pro Request eine Zeile, und beide bereinigen stündlich.
Zwei weitere Punkte bei hohem Volumen:
- Nutzungs- und Audit-Schreibvorgänge werden gepuffert und bei vollem Puffer
verworfen, nicht blockiert. Ein Burst oder eine langsame Datenbank kann
unbemerkt Nutzungseinträge verlieren, was zu verlorenen Kostendaten und zu
gering belasteten Budgets führt. Das einzige Signal ist eine Warnung
usage log buffer fullin den Logs – richten Sie dafür einen Alert ein. - Budget-Prüfungen fragen bei jedem Request die Nutzungstabelle ab. Halten
Sie
USAGE_RETENTION_DAYSknapp, wenn Budgets aktiviert sind, und ziehen Sie Postgres SQLite vor, bei dem die einzelne Verbindung dies zum dominierenden Kostenfaktor macht.
Observability
- Metriken sind standardmäßig deaktiviert. Setzen Sie
METRICS_ENABLED=true, um Prometheus-Metriken unterMETRICS_ENDPOINT(/metrics) bereitzustellen. Der Endpunkt ist nicht authentifiziert, halten Sie ihn also innerhalb Ihres Perimeters. Siehe den Prometheus-Leitfaden. - Der OpenTelemetry-Export ist standardmäßig deaktiviert. Setzen Sie
OTEL_ENABLED=trueundOTEL_EXPORTER_OTLP_ENDPOINT, um Traces und GenAI-Metriken an Ihren Collector zu senden. Siehe den OpenTelemetry-Leitfaden. - Logs sind automatisch JSON, wenn stdout kein TTY ist, sodass Container
ohne Konfiguration strukturierte Logs erhalten. Setzen Sie
LOG_FORMATexplizit, um dies zu überschreiben.LOG_LEVEList standardmäßiginfo; ein ungültiger Wert lässt den Start fehlschlagen. gomodel_circuit_breaker_stateist die Gauge, auf die Sie für den Provider-Zustand Alerts setzen sollten. Sie wird pro Request aktualisiert, sodass ein inaktiver Provider seinen zuletzt beobachteten Wert behält.- Die Request-Dauer bei Streaming-Responses misst die Zeit bis zum Aufbau des Streams, nicht die Gesamtdauer des Streams. Lesen Sie Panels zur Stream-Latenz mit diesem Wissen.
- In Prometheus gibt es keine Token- oder Kostenmetriken. Die Erfassung
von Ausgaben und Tokens liegt in der Nutzungsdatenbank, im Dashboard und in
GET /v1/usage. Setzen Sie Alerts für Ausgaben über Budgets, nicht über Prometheus.
Hinweise zur Härtung
- Platzieren Sie die Admin-Oberfläche hinter Ihrem Perimeter. Das
Dashboard und
/admin/*werden auf demselben Port wie die Modell-API ausgeliefert und lassen sich nicht auf einen separaten Listener verlegen. Beschränken Sie sie auf Proxy- oder Netzwerkebene oder deaktivieren Sie sie mitADMIN_ENDPOINTS_ENABLED=false. Die Dashboard-Shell und ihre statischen Ressourcen umgehen die Authentifizierung bewusst; geschützt sind nur die Daten, die sie laden. - Beschränken Sie den Zugriff auf die Datenbankdatei. Das
SQLite-Datenverzeichnis wird mit
0755und die Datenbankdatei mit Standardberechtigungen angelegt, sodass sie auf einem gemeinsam genutzten Host für alle lesbar sein kann. Führen Sie GoModel unter einem eigenen Benutzer aus und verschärfen Sie die Berechtigungen, insbesondere wenn Zugangsdaten über das Dashboard gespeichert werden. - Halten Sie
BODY_SIZE_LIMITvernünftig. Der Standardwert ist10M, und diese Einstellung verhindert, dass ein übergroßer Body zum Vektor für Speichererschöpfung wird. Ein nicht parsbarer Wert fällt nur mit einer Warnung auf den Standardwert zurück – prüfen Sie also die Schreibweise. - Lassen Sie
STREAM_STALL_TIMEOUTaktiviert. Der Standardwert ist60Sekunden, und diese Einstellung gibt die Goroutine und die Upstream-Provider-Verbindung frei, die ein Client belegt, wenn er einen Stream öffnet und aufhört zu lesen. Wird sie deaktiviert (0), kann ein solcher Client beides blockieren, bis der providerseitigeHTTP_TIMEOUTabläuft. - Lassen Sie
pprofdeaktiviert. Es ist standardmäßig aus und im aktivierten Zustand nicht authentifiziert. - Grenzen Sie verwaltete API-Keys ein. Geben Sie jedem Verbraucher einen
eigenen Key mit eigenem User Path; das ermöglicht
auch Rate-Limits und Budgets pro Verbraucher. Vergeben Sie
dashboard_accessundpassthrough_accessnur, wo sie benötigt werden.
Checkliste für den Produktivbetrieb
- Setzen Sie
GOMODEL_MASTER_KEYauf einen starken Wert. - Terminieren Sie TLS in einem Proxy vor dem Gateway.
- Beschränken Sie
/admin/*, das Dashboard und/metricsam Netzwerkperimeter. - Verwenden Sie Postgres oder MongoDB bei mehr als einem Replikat oder bei
einem flüchtigen Dateisystem; binden Sie bei SQLite ein Volume ein und setzen
Sie
SQLITE_PATH. - Deklarieren Sie Provider in Umgebungsvariablen oder
config.yaml, mit Werten aus einem Secret-Manager. - Deklarieren Sie Budgets und Rate-Limits als Code, wenn Sie mehrere Replikate betreiben, und bemessen Sie Rate-Limit-Regeln pro Replikat.
- Entscheiden Sie sich bewusst für eine Einstellung von
LOGGING_LOG_BODIESund legen Sie die Aufbewahrungsfristen gezielt fest. - Richten Sie die Readiness auf
/health/readyaus und setzen SieterminationGracePeriodSecondsauf mehr als 30. - Aktivieren Sie Metriken und richten Sie Alerts für
gomodel_circuit_breaker_stateundusage log buffer fullein. - Setzen Sie bei Air-Gapped-Installationen
GOMODEL_OFFLINE=trueund liefern Sie dann den Modellkatalog als Datei mit oder deklarieren Sie Modellpreise in der Konfiguration, damit Kostenerfassung und Budgets weiter funktionieren.