Na tyle mały, że działa wszędzie
GoModel to pojedyncza statyczna binarka Go bez zależności uruchomieniowych. Jest budowana zCGO_ENABLED=0 i dystrybuowana na distroless/static, więc kontener nie ma powłoki,
menedżera pakietów ani biblioteki libc do łatania.
Te liczby pochodzą ze strony benchmarków, gdzie zmierzono je
w porównaniu z innymi gatewayami na identycznym sprzęcie.
W praktyce oznacza to, że GoModel swobodnie mieści się tam, gdzie rozmiar i zimne
starty są karane: Cloud Run, Fargate, Container Apps, Fly.io, kontenerowe
Lambdy albo mała maszyna wirtualna obok Twojej aplikacji. Nie potrzebuje dedykowanej puli węzłów.
Wybór backendu storage
GoModel utrwala logi audytowe, rekordy użycia, budżety, modele wirtualne, zarządzane klucze API i inny stan administracyjny za pośrednictwemSTORAGE_TYPE.
SQLite jest domyślny, aby GoModel działał bez żadnej konfiguracji, i jest pełnoprawną
opcją produkcyjną dla pojedynczej instancji z trwałym wolumenem. Eliminuje przeskok
sieciowy i komponent, który trzeba utrzymywać.
Przestaje się sprawdzać w dwóch przypadkach: przy więcej niż jednej replice oraz w każdym środowisku, którego
system plików nie przetrwa restartu. Dwie repliki z SQLite nie współdzielą
bazy danych, tylko każda ma własną, więc użycie, budżety i zarządzane klucze API
po cichu się rozjeżdżają. Klucz utworzony w jednym podzie nie istnieje w drugim.
SQLite działa też celowo z jednym otwartym połączeniem, aby był jeden zapisujący
i aby uniknąć rywalizacji o blokady. Każdy zapis logów audytowych, zapis użycia i sprawdzenie budżetu
jest przez nie serializowane, co stanowi pułap przepustowości przy stałym obciążeniu.
W przypadku Postgresa
POSTGRES_MAX_CONNS ma domyślnie wartość 10. Zwiększ ją w pierwszej kolejności, jeśli
masz dużą współbieżność przy włączonych budżetach lub limitach żądań.
Domyślna ścieżka SQLite to ./data/gomodel.db, jeśli katalog ./data już
istnieje obok binarki, a w przeciwnym razie katalog danych użytkownika w systemie operacyjnym. To
sprawia, że domyślna ścieżka zależy od katalogu roboczego, więc na produkcji ustaw SQLITE_PATH
jawnie. Rozwiązana ścieżka jest wypisywana przy starcie jako
storage configured. Zobacz
Gdzie GoModel przechowuje dane?.
Redis
Redis jest opcjonalny. Przechowuje współdzielony snapshot katalogu modeli i dokładny cache odpowiedzi. Bez niego GoModel używa cache’u w procesie oraz lokalnego cache’u plikowego, co działa poprawnie, ale nie jest współdzielone między replikami. Dodaj Redisa, gdy kilka replik ma współdzielić wyniki wykrywania modeli i trafienia w cache.Jeśli skonfigurowano cache modeli w Redisie, a Redis jest nieosiągalny przy starcie,
gateway nie uruchomi się. Późniejsza utrata Redisa to jedynie stan obniżonej sprawności (degraded).
Uwzględnij to przy ustalaniu kolejności uruchamiania usług.
Dane uwierzytelniające providerów
GoModel pobiera dane uwierzytelniające providerów ze zmiennych środowiskowych, zconfig.yaml lub ze strony Providers w dashboardzie. Wszystkie trzy sposoby działają, ale nie
niosą takiego samego ryzyka.
Na produkcji konfiguruj providerów przez zmienne środowiskowe, a wartości
dostarczaj z menedżera sekretów. Wstrzykuj je za pomocą AWS Secrets Manager,
Google Secret Manager, Azure Key Vault, HashiCorp Vault lub Kubernetes Secret
wypełnianego przez External Secrets Operator albo sterownik Secrets Store CSI.
Są dwa powody, a ludzie zwykle przeoczają ten drugi.
Sekrety nie trafiają do bazy danych. Dane uwierzytelniające providerów zapisane z
dashboardu są utrwalane w magazynie provider_credentials, a nagłówki serwerów MCP
i sekrety guardrails — w swoich. Jeśli nie ustawiono GOMODEL_ENCRYPTION_KEY,
są przechowywane jawnym tekstem: maskowanie *** jest stosowane wyłącznie przy
odczycie, więc zapisana wartość jest czytelna dla każdego, kto ma plik bazy danych,
kopię zapasową, snapshot lub replikę do odczytu. Przy ustawionym kluczu są szyfrowane
w spoczynku (zobacz Szyfrowanie danych w spoczynku), ale
wtedy trzeba chronić i przechowywać sam klucz. Dane uwierzytelniające zadeklarowane w zmiennych
środowiskowych lub config.yaml nigdy nie są w ogóle zapisywane w magazynie.
Providerów zadeklarowanych w konfiguracji nie można edytować w czasie działania. Provider zadeklarowany
w zmiennych środowiskowych lub config.yaml jest w dashboardzie tylko do odczytu. Providera zapisanego
w bazie danych może edytować każdy, kto ma klucz z dostępem do dashboardu, łącznie z jego
base_url. Przestawienie base_url na host kontrolowany przez atakującego po cichu
przekierowuje do niego każdy prompt i wstrzykiwany klucz upstreamu. Deklarowanie providerów
w kodzie oznacza, że taka zmiana może przyjść wyłącznie przez Twój pipeline wdrożeniowy.
Jeśli Twoje narzędzia do sekretów montują pliki, wskaż plik w ustawieniu za pomocą
referencji do sekretu, zamiast kopiować go do
zmiennej środowiskowej:
GOMODEL_ENCRYPTION_KEY i mimo to traktuj bazę danych jako wrażliwą: szyfruj
wolumen, ograniczaj dostęp i odpowiednio klasyfikuj kopie zapasowe.
Inne kwestie związane z danymi uwierzytelniającymi
- Zarządzane klucze API są przechowywane jako hash SHA-256 losowego sekretu o wysokiej entropii, nigdy w postaci możliwej do odzyskania. Jawny tekst jest pokazywany raz, przy utworzeniu. To celowo inne podejście niż w przypadku danych uwierzytelniających providerów.
- Nagłówki z danymi uwierzytelniającymi klienta są redagowane przed utrwaleniem. Przy
LOGGING_LOG_HEADERS=truenagłówkiAuthorization,x-api-key,Cookiei inne nagłówki z danymi uwierzytelniającymi są zastępowane przez[REDACTED]na ścieżce zapisu, więc magazyn audytu nigdy ich nie otrzymuje. - Ustaw
GOMODEL_MASTER_KEY. Jeśli jest pusty i nie istnieją żadne zarządzane klucze, każdy request jest przepuszczany bez uwierzytelnienia, a/admin/*zostaje dodane do listy ścieżek pomijających uwierzytelnianie jako ścieżka odzyskiwania dostępu. Gateway dostępny z internetu w takim stanie oddaje każdemu pełne Admin API.
TLS
GoModel nie terminuje TLS. Serwuje zwykłe HTTP i oczekuje przed sobą reverse proxy lub load balancera. Uruchom je w każdym wdrożeniu, które nie jest ograniczone do loopbacka: bez niego klucze API klientów, zarządzane klucze gatewaya i klucz główny przechodzą przez sieć jawnym tekstem. Nie ma też middleware CORS, więc klienci przeglądarkowi z innego originu nie mogą wywoływać gatewaya bezpośrednio. Jeśli tego potrzebujesz, dodaj nagłówki w swoim proxy.Certyfikaty upstreamu za prywatnym CA
Wychodzące połączenia HTTPS — do providerów i serwerów MCP — są weryfikowane względem systemowego pakietu CA. Obrazy kontenerów go zawierają (/etc/ssl/certs/ca-certificates.crt), więc
publiczne endpointy nie wymagają żadnej konfiguracji.
Upstream, którego certyfikat wystawiło wewnętrzne lub firmowe CA, kończy
połączenie błędem x509: certificate signed by unknown authority. Zamontuj
certyfikat CA w katalogu zaufanych certyfikatów, który GoModel odczytuje dodatkowo
oprócz publicznych certyfikatów głównych:
SSL_CERT_FILE też działa, ale zastępuje plik pakietu zamiast go uzupełniać,
więc wskaż w nim pakiet, który zawiera również potrzebne Ci publiczne certyfikaty główne.
Wdrożenia air-gapped i offline
GoModel działa w pełni w trybie air-gapped. Nie ma telemetrii, analityki ani sprawdzania licencji. Dashboard administracyjny jest wbudowany w binarkę, a jego fonty są dołączone, więc UI nie ładuje żadnych zasobów z CDN. W połączeniu z lokalnymi serwerami modeli, takimi jak Ollama, SGLang czy vLLM, gateway nie potrzebuje trasy do publicznego internetu. Domyślnie gateway wykonuje dokładnie dwa wywołania wychodzące, które nie trafiają do skonfigurowanego providera, a jeden przełącznik wyłącza oba:GOMODEL_OFFLINE=true (lub offline: true w config.yaml) wyłącza
sprawdzanie aktualizacji i pomija każdy URL katalogu HTTP. Jest stosowane po wszystkich innych
ustawieniach, więc nic w config.yaml ani w zmiennych środowiskowych nie może pod spodem ponownie włączyć
wywołania wychodzącego. Wywołania do skonfigurowanych providerów i do wszystkiego, co
zadeklarujesz samodzielnie, na przykład kolektora OTLP, upstreamów MCP czy magazynu
wektorowego, pozostają nietknięte. Log startowy potwierdza tryb.
Oba wywołania możesz też kontrolować osobno za pomocą
GOMODEL_VERSION_CHECK_ENABLED=false i MODEL_LIST_URL=off.
Pobieranie katalogu działa na zasadzie best-effort. Gdy jest wyłączone lub się nie powiedzie, gateway
uruchamia się normalnie, /health i /health/ready zwracają 200, wykrywanie modeli u providerów nadal
działa, ponieważ listy modeli pochodzą z własnego endpointu /models każdego providera,
a requesty są normalnie routowane. Tracisz natomiast wzbogacanie metadanymi.
Zachowaj ceny w środowisku air-gapped na jeden z trzech sposobów:
-
Dostarcz katalog jako plik. Pobierz
models.min.jsonpoza środowiskiem air-gapped, skopiuj go do środka i wskaż go gatewayowi. Plik lokalny jest odczytywany, a nie pobierany, więc pozostaje aktywny przyGOMODEL_OFFLINE=true:Plik jest odczytywany ponownie przy każdym odświeżeniu cache’u, więc podmiana go na dysku aktualizuje ceny bez restartu. -
Utwórz mirror. Serwuj plik z wewnętrznego hosta HTTP i ustaw
MODEL_LIST_URLna ten URL. PozostawGOMODEL_OFFLINEnieustawione i osobno wyłącz sprawdzanie aktualizacji albo również skierujGOMODEL_VERSION_CHECK_URLna wewnętrzny mirror. -
Zadeklaruj ceny per model w
providers.<name>.models[].metadata.pricingwconfig.yaml.
Ustawienie
MODEL_LIST_URL na pusty string nie wyłącza
pobierania: puste wartości zmiennych środowiskowych są pomijane, więc domyślny URL zostaje. Użyj
off lub GOMODEL_OFFLINE=true.169.254.169.254) w poszukiwaniu danych uwierzytelniających. Instalacja
air-gapped raczej nie skonfiguruje Bedrocka, ale warto o tym wiedzieć.
Uruchamianie więcej niż jednej repliki
Obsługa requestów jest bezstanowa, więc repliki nie potrzebują powiązania (affinity) dla zwykłego ruchu. Kilka funkcji przechowuje jednak stan w pamięci procesu i nie koordynuje go między instancjami.
Najbardziej zaskakują limity żądań: to mechanizm kontroli w procesie, więc
trzy repliki z regułą 100 rpm przepuszczą łącznie do 300 rpm. Dobieraj reguły
per replika, a jako trwałej kontroli między instancjami używaj budżetów,
ponieważ odczytują dane ze współdzielonej bazy danych.
Zmiany konfiguracji nie propagują się natychmiast
Konfiguracja zarządzana przez administratora jest trwale zapisana w bazie danych, ale serwowana ze snapshotu w pamięci na każdej replice. Zmiana zapisana w dashboardzie od razu aktualizuje replikę, która obsłużyła request. Pozostałe repliki pobierają ją we własnym cyklu odświeżania, a dwa rodzaje danych w ogóle go nie mają.Sondy health i zamykanie
GoModel udostępnia liveness i readiness osobno, a binarka potrafi sama się odpytać, co działa w obrazie distroless bez powłoki i bezcurl.
Skieruj readiness w Kubernetes na
/health/ready, a nie na /health. Chart Helm w
katalogu helm/ repozytorium już to robi, uruchamia gateway jako
użytkownika obrazu bez uprawnień roota na systemie plików tylko do odczytu, trzyma SQLite na trwałym
wolumenie i odmawia uruchomienia więcej niż jednej repliki, dopóki nie przełączysz storage.type na
postgresql lub mongodb. Wartości opisuje helm/README.md.
Readiness nie czeka na katalog modeli, który ładuje się asynchronicznie. Świeży
pod może zgłaszać gotowość, gdy
GET /v1/models jest jeszcze puste, a pierwsze
requesty kończą się błędem. Uwzględnij to w ustawieniach rolloutu albo rozgrzej pody przed
przełączeniem ruchu.SIGTERM gateway przez 10 sekund dokańcza bieżącą pracę HTTP, a następnie
kończy zamykanie w ciągu 30 sekund, zapisując po drodze zbuforowane wpisy użycia i audytu.
Żadnego z tych timeoutów nie można konfigurować. Streamowane odpowiedzi w toku
są przerywane po 10 sekundach, więc długie completion trwające podczas rolling
deploymentu zostanie ucięte. Ustaw terminationGracePeriodSeconds powyżej 30 (45 to
rozsądny wybór), aby zamykanie nie ścigało się z SIGKILL.
Retencja danych i przyrost dysku
Logowanie audytowe i śledzenie użycia zapisują jeden wiersz na request, a oba mechanizmy czyszczą dane co godzinę.
Dwie dodatkowe kwestie przy dużym wolumenie:
- Zapisy użycia i audytu są buforowane i odrzucane, a nie blokowane, gdy
bufor się zapełni. Skok ruchu lub wolna baza danych mogą po cichu zgubić wpisy użycia,
co oznacza utracone dane o kosztach i zaniżone obciążenie budżetów. Jedynym sygnałem jest
ostrzeżenie
usage log buffer fullw logach, więc ustaw na nie alert. - Sprawdzenia budżetów odpytują tabelę użycia przy każdym requeście. Trzymaj
USAGE_RETENTION_DAYSna niskim poziomie, gdy budżety są włączone, i wybieraj Postgresa zamiast SQLite, którego pojedyncze połączenie sprawia, że to dominujący koszt.
Obserwowalność
- Metryki są domyślnie wyłączone. Ustaw
METRICS_ENABLED=true, aby udostępnić metryki Prometheus podMETRICS_ENDPOINT(/metrics). Endpoint nie wymaga uwierzytelnienia, więc trzymaj go wewnątrz swojego perymetru. Zobacz przewodnik po Prometheusie. - Eksport OpenTelemetry jest domyślnie wyłączony. Ustaw
OTEL_ENABLED=trueiOTEL_EXPORTER_OTLP_ENDPOINT, aby wysyłać trace’y i metryki GenAI do swojego kolektora. Zobacz przewodnik po OpenTelemetry. - Logi są automatycznie w formacie JSON, gdy stdout nie jest terminalem (TTY), więc kontenery dostają
ustrukturyzowane logi bez żadnej konfiguracji. Aby to nadpisać, ustaw jawnie
LOG_FORMAT.LOG_LEVELma domyślnie wartośćinfo; nieprawidłowa wartość przerywa start. gomodel_circuit_breaker_stateto wskaźnik, na który warto ustawić alerty dotyczące stanu providerów. Aktualizuje się przy każdym requeście, więc bezczynny provider zachowuje ostatnią zaobserwowaną wartość.- Czas trwania requestu dla odpowiedzi streamowanych mierzy czas do nawiązania streamu, a nie całkowity czas trwania streamu. Pamiętaj o tym, czytając panele opóźnień streamów.
- W Prometheusie nie ma metryk tokenów ani kosztów. Rozliczanie wydatków i tokenów
znajduje się w bazie danych użycia, w dashboardzie i w
GET /v1/usage. Alerty dotyczące wydatków ustawiaj na podstawie budżetów, a nie Prometheusa.
Uwagi dotyczące hardeningu
- Umieść powierzchnię administracyjną za swoim perymetrem. Dashboard i
/admin/*są serwowane na tym samym porcie co API modeli i nie można ich przenieść na osobny listener. Ogranicz do nich dostęp na poziomie proxy lub sieci albo wyłącz je za pomocąADMIN_ENDPOINTS_ENABLED=false. Powłoka dashboardu i jej zasoby statyczne celowo pomijają uwierzytelnianie; chronione są tylko dane, które ładują. - Ogranicz dostęp do pliku bazy danych. Katalog danych SQLite jest tworzony z uprawnieniami
0755, a plik bazy danych z domyślnymi uprawnieniami, więc na współdzielonym hoście może być czytelny dla wszystkich. Uruchamiaj GoModel jako dedykowany użytkownik i zaostrz uprawnienia, zwłaszcza jeśli jakiekolwiek dane uwierzytelniające są zapisywane przez dashboard. - Utrzymuj rozsądną wartość
BODY_SIZE_LIMIT. Domyślnie wynosi10Mi to ona chroni przed użyciem zbyt dużego body do wyczerpania pamięci. Wartość, której nie da się sparsować, wraca do wartości domyślnej z jedynie ostrzeżeniem, więc sprawdź pisownię. - Pozostaw włączone
STREAM_STALL_TIMEOUT. Domyślnie wynosi60sekund i to ono zwalnia goroutine i połączenie z upstreamowym providerem, które trzyma klient, gdy otworzy stream i przestanie czytać. Wyłączenie go (0) pozwala takiemu klientowi blokować oba zasoby, dopóki nie wygaśnieHTTP_TIMEOUTpo stronie providera. - Pozostaw
pprofwyłączone. Jest domyślnie wyłączone, a po włączeniu nie wymaga uwierzytelnienia. - Ograniczaj zakres zarządzanych kluczy API. Daj każdemu konsumentowi własny klucz z własną
ścieżką użytkownika, co jednocześnie umożliwia limity żądań
i budżety per konsument. Nadawaj
dashboard_accessipassthrough_accesstylko tam, gdzie to potrzebne.
Lista kontrolna wdrożenia produkcyjnego
- Ustaw
GOMODEL_MASTER_KEYna silną wartość. - Terminuj TLS w proxy przed gatewayem.
- Ogranicz dostęp do
/admin/*, dashboardu i/metricsna perymetrze sieci. - Używaj Postgresa lub MongoDB przy więcej niż jednej replice lub dowolnym ulotnym systemie plików;
przy SQLite zamontuj wolumen i ustaw
SQLITE_PATH. - Deklaruj providerów w zmiennych środowiskowych lub
config.yaml, z wartościami z menedżera sekretów. - Przy kilku replikach deklaruj budżety i limity żądań w kodzie i dobieraj reguły limitów żądań per replika.
- Zdecyduj o
LOGGING_LOG_BODIESi świadomie ustaw okresy retencji. - Skieruj readiness na
/health/readyi ustawterminationGracePeriodSecondspowyżej 30. - Włącz metryki i ustaw alerty na
gomodel_circuit_breaker_stateoraz nausage log buffer full. - W instalacjach air-gapped ustaw
GOMODEL_OFFLINE=true, a następnie dostarcz katalog modeli jako plik lub zadeklaruj ceny modeli w konfiguracji, aby śledzenie kosztów i budżety nadal działały.