Orbito - Dokumentacja modułówOrbito - Dokumentacja modułów
Wprowadzenie
Pierwsze kroki
API
Komunikacja
Widoki
Wdrożenie
Społeczność
For AI agents
Wprowadzenie
Pierwsze kroki
API
Komunikacja
Widoki
Wdrożenie
Społeczność
For AI agents
  • Wdrożenie

    • Wdrożenie modułu
    • Konto i namespace'y
    • omm — CLI do publikacji
    • Pakowanie modułu
    • Pule rdzeni i router

Pule rdzeni i router

Dla kogo

Ta strona jest dla operatora instalacji Orbito, który uruchamia platformę na kilku serwerach. Autor modułu nie musi nic z tego robić: moduł działa tak samo na jednym i na wielu serwerach, a SDK samo łączy się ponownie, gdy firma zmienia proces rdzenia.

Rdzeń Orbito (core-back) może działać jako jeden proces albo jako pula rdzeni — kilka procesów (członków) na kilku serwerach. Firma należy do jednej puli i jest przypięta do jednego członka, który obsługuje ją w całości: REST panelu, WebSocket, połączenia modułów, automatyzacje i kontenery. Gdy członek albo cały serwer pada, jego firmy w ciągu kilkunastu sekund przejmują pozostali członkowie puli. Wdrożenie nowej wersji rdzenia odbywa się bez przestoju: członek oddaje firmy po kolei (drain), dostaje nowy obraz i wraca.

Przed członkami stoi router (core-router) — bezstanowy proces na każdym serwerze aplikacyjnym, który kieruje każde żądanie i każde połączenie modułu do członka, który w tej chwili trzyma firmę.

Topologia

                  Cloudflare Load Balancer (health check GET /health)
                 ┌───────────────────┴───────────────────┐
              app1                                    app2                          app3
   ┌──────────────────────────┐          ┌──────────────────────────┐    ┌──────────────────┐
   │ nginx → router           │          │ nginx → router           │    │ router (opcja)   │
   │ członek app1-a (pula A)  │   WG     │ członek app2-a (pula B)  │ WG │ członek app3-a   │
   │ członek app1-b (pula B)  │◄────────►│ członek app2-b (pula B)  │◄──►│ Mongo: arbiter   │
   │ Mongo: dane              │          │ Mongo: dane              │    │ k3s: agent       │
   │ moduły: lokalny Docker   │          │ k3s: serwer, moduły      │    │                  │
   └──────────────────────────┘          └──────────────────────────┘    └──────────────────┘

Każda warstwa na ścieżce żądania istnieje na co najmniej dwóch serwerach: wejście (Cloudflare), router, członkowie rdzenia, moduły (k3s) i baza (replica set).

Komponenty

KomponentRola
Cloudflare Load Balancerwejście https://<domena panelu>; kieruje tylko do zdrowych serwerów (monitor GET /health routera)
nginx (na każdym serwerze)statyczny front, /api, /ws i /health → lokalny router
router core-routerczyta z Mongo przypięcia firm i adresy członków; kieruje REST, WS i RPC modułów do właściciela firmy
członek puliten sam obraz rdzenia, konfigurowany przez ENV; zgłasza się heartbeatem i trzyma dzierżawy swoich firm
MongoDB replica setjedyny wspólny stan: dane, definicje pul, członkowie, dzierżawy firm
WireGuardsieć prywatna między serwerami: router → członek, członek → Mongo, Mongo ↔ Mongo, członek → API k3s
k3smoduły firm, które mają przetrwać awarię serwera; router w klastrze jako Deployment ×2 + Service core-router

Jak router rozpoznaje firmę

RuchSkąd firma
REST panelunagłówek x-company-id albo ścieżka /companies/:companyId/...
REST bez firmy (logowanie, lista firm, /admin/*…)dowolny żywy członek puli domyślnej (round-robin)
POST /webhooks/*rozesłany do wszystkich żywych członków
WebSocketparametr ?companyId= w adresie połączenia
RPC modułutoken modułu z pierwszej ramki rejestracji (claim companyId)

Moduł nigdy nie zna adresu rdzenia — dzwoni do routera na swoim serwerze (lokalny Docker: brama sieci kontenerów; k3s: Service core-router), a router łączy go z właścicielem firmy po WireGuard. Dzięki temu przejęcie firmy przez innego członka nie restartuje kontenerów modułów.

Pule, członkowie, dzierżawy

  • Pula jest definiowana w panelu administratora (strona „Pule”): nazwa, region, obsługiwane środowiska modułów (np. local-docker, k3s) i flaga puli domyślnej — jedna na region; trafiają do niej firmy bez przypisanej puli.
  • Członek dołącza do puli przez ENV (CORE_POOL) i co 2 s odnawia swój wpis w bazie. Wpis starszy niż 6 s oznacza członka martwego. Członek, którego region albo środowiska nie pokrywają puli, nie przejmuje firm (widać go w panelu i w logu).
  • Dzierżawa firmy — firmę przejmuje ten członek, któremu pierwszemu uda się atomowy zapis „wolna albo wygasła”. Członek trzyma najwyżej równy udział firm puli i nie podkrada ich innym; nowy członek dostaje firmy, gdy się zwolnią albo po ręcznym „rebalance” w panelu.

Firma na lokalnym Dockerze jednego serwera musi należeć do puli, której wszyscy członkowie stoją na tym serwerze — chroni ją to przed awarią procesu rdzenia, ale nie serwera. Pełną odporność na awarię serwera mają firmy, których moduły działają w wielowęzłowym k3s.

Kolejność uruchamiania

  1. Zegar i sieć. NTP na każdym serwerze (router ocenia żywotność członków własnym zegarem). Mesh WireGuard między serwerami, firewall tunelu, Docker startujący po tunelu.
  2. MongoDB replica set — dwa węzły z danymi i arbiter, po WireGuard, z keyFile i osobnymi użytkownikami dla rdzenia (readWrite + dbAdmin) i routera (read). Przed konwersją istniejącego węzła zrób kopię.
  3. Klucze JWT rdzenia — ta sama para Ed25519 na każdym serwerze (wolumen keys/). Nie generuj nowych: podpisują tokeny modułów i sesji, a klucz publiczny weryfikuje repozytorium modułów i router.
  4. Pule w panelu — przynajmniej jedna pula domyślna w regionie. Bez definicji puli członek nie przejmuje firm.
  5. Członkowie i router na każdym serwerze (docker compose up -d). Router kieruje wyłącznie do członków zgłoszonych w bazie, więc stawiaj go razem z członkami — rdzeń bez CORE_POOL nie zgłasza się i nie może stać za routerem.
  6. nginx na każdym serwerze: /api, /ws, /health → router; ten sam build frontu na każdym serwerze.
  7. k3s (jeśli moduły działają w klastrze): router w klastrze, NetworkPolicy modułów (egress tylko DNS i router), advertise środowiska wskazujące Service core-router.
  8. Cloudflare — load balancer z originem każdego serwera i monitorem /health.

Konfiguracja członka

Członek to obraz rdzenia z konfiguracją wyłącznie w ENV i wolumenach. Wartości wspólne (DB_URI z wszystkimi węzłami replica setu, klucze JWT, sekrety szyfrowania, rejestr obrazów, repozytorium modułów, poczta, AI) muszą być identyczne u wszystkich członków — firma przechodzi między nimi w locie. Wartości per członek:

CORE_POOL=eu1                 # pula; brak = tryb zgodności (jeden proces, wszystkie firmy)
CORE_MEMBER=app2-a            # unikalny w całym systemie
CORE_REGION=eu                # musi się zgadzać z regionem puli (domyślnie eu)
CORE_ADVERTISE_HOST=10.8.0.2  # adres WireGuard, pod którym router dociera do członka (wymagany z CORE_POOL)
REST_PORT=3023
WS_PORT=3024
MODULE_RUNTIMES_FILE=/app/runtimes.yaml

Porty publikuj na hoście pod tymi samymi numerami i tylko na adresie WireGuard — członek ogłasza routerowi CORE_ADVERTISE_HOST + swoje porty.

Plik środowisk (runtimes.yaml) członka opisuje środowiska modułów i nasłuch RPC, na który dzwoni router. listen.port = port publikowany, advertise = adres lokalnego routera, który dostają moduły:

default: k3s
rpc:
  upstream:                                         # nazwa = rpc.upstreamListener routera
    listen: { host: 0.0.0.0, port: 7723 }
    advertise: { host: core-router.orbito-modules.svc, port: 7801 }
runtimes:
  k3s:
    type: kubernetes
    rpc: upstream
    kubeconfig: /app/k8s/k3s.kubeconfig
    namespace: orbito-modules

Nasłuch wskazany routerowi musi być bez TLS — router mówi do członka gołym TCP po WireGuard. Zmiana advertise odtwarza kontenery modułów raz (adres RPC jest zapisany w etykiecie kontenera); przejście firmy między członkami — nie.

Konfiguracja routera

Plik YAML (CORE_ROUTER_CONFIG, domyślnie ./config.yaml), ten sam na każdym serwerze — router nie musi wiedzieć, gdzie stoi:

mongo:
  uri: mongodb://orbito-router:<hasło>@10.8.0.1:27020,10.8.0.2:27020,10.8.0.3:27020/orbitov2?replicaSet=rs0&authSource=admin
http:
  port: 8080                # REST, WS i /health routera
  # stripPrefix: /api       # domyślnie; '' gdy nginx sam zdejmuje /api
rpc:
  plain:
    port: 7801              # moduły z lokalnego Dockera
  upstreamListener: upstream
jwtPublicKeyFile: /app/keys/jwt-public.pem

Zalecany układ: nginx przekazuje /api i /ws do routera bez przepisywania ścieżki, a prefiks /api zdejmuje router. Konfiguracja jest sprawdzana przy starcie — błąd zatrzymuje proces z nazwą pola. W k3s konfiguracja routera jest sekretem (zawiera hasło do bazy).

Odpowiedzi routera, które warto znać przy diagnozie:

OdpowiedźZnaczenie
503 COMPANY_UNASSIGNED + Retry-After: 2firma nie ma w tej chwili żywego właściciela (trwa przejęcie); panel ponawia odczyt
503 NO_CORE_MEMBERpula domyślna nie ma żywego, niedrenowanego członka
502 CORE_MEMBER_UNREACHABLEczłonek nie odpowiada
GET /health → 200 / 503router czyta bazę i pula domyślna ma żywego członka — na tym opiera się monitor Cloudflare

Cloudflare

  • Rekord panelu proxowany (pomarańczowa chmurka), load balancer z originem każdego serwera aplikacyjnego, równe wagi, bez session affinity — router i tak kieruje po firmie.
  • Monitor: HTTPS GET /health z nagłówkiem Host panelu, oczekiwany kod 200.
  • SSL Full (strict); ten sam certyfikat originu na każdym serwerze (Cloudflare Origin CA albo Let's Encrypt z wyzwaniem DNS-01 — HTTP-01 za load balancerem trafia na losowy origin).
  • WebSockets włączone. Proxy Cloudflare zrywa połączenie po ~100 s ciszy; panel łączy się wtedy ponownie.
  • Prawdziwy adres klienta: CF-Connecting-IP (real_ip_header w nginx).

WireGuard i Mongo

  • Mesh ze statycznymi kluczami (np. 10.8.0.1–3, port 51820/udp). Publicznie zostają tylko nginx (443) i — opcjonalnie — port TLS routera. Kontenery modułów lokalnego Dockera nie mogą sięgać do adresów tunelu (firewall w DOCKER-USER, bo porty Dockera omijają INPUT).
  • Replica set: dwa węzły z danymi (priorytety 2 i 1) i arbiter na trzecim serwerze; connection string zawiera wszystkie trzy adresy, więc klient łączy się, nawet gdy jeden serwer leży. Wszyscy klienci bazy (rdzeń, router, repozytorium modułów) dostają nowe URI z użytkownikiem.

Praca z pulą

Wdrożenie nowej wersji rdzenia bez przestoju — po kolei dla każdego członka:

docker compose -f app2.yml pull core-app2-a
# panel „Pule” → drain członka app2-a; poczekaj, aż liczba jego firm spadnie do 0
docker compose -f app2.yml up -d core-app2-a   # nowy proces; pierwszy heartbeat zdejmuje drain

Drain ma sens, gdy pula ma innego żywego członka. Zatrzymanie kontenera (SIGTERM) też oddaje firmy od razu, bez czekania na wygaśnięcie dzierżaw.

Przeniesienie firmy do innej puli — panel „Firmy” albo node scripts/set-company-pool.js <companyId> <pula> --apply. Rdzeń odrzuci pulę, która nie istnieje, nie ma żywego członka albo nie obsługuje środowiska modułów firmy. Przepięcie wykonują członkowie w ciągu kilku sekund.

Diagnostyka — node scripts/pools-doctor.js (tylko odczyt z bazy, działa także, gdy żaden rdzeń nie odpowiada): pule, członkowie żywi / martwi / drenowani z ich firmami i firmy bez właściciela (kod wyjścia 2). GET /health członka zwraca 503 przy braku bazy albo w trakcie drainu.

Wycofanie — rdzeń bez CORE_POOL działa jak pojedynczy proces obsługujący wszystkie firmy. Zatrzymaj członków i router, uruchom jeden rdzeń bez CORE_POOL i skieruj nginx z powrotem na jego porty. Kontenery lokalnego Dockera zostają, o ile moduły mają ten sam adres RPC co przed zmianą.

Ograniczenia

  • Czas przejęcia po awarii członka to ~10–15 s (wygaśnięcie dzierżawy 6 s, przejęcie, ponowne połączenie modułów do 5 s). W tym czasie żądania do firmy dostają 503 COMPANY_UNASSIGNED.
  • Zdarzenia automatyzacji w locie giną przy przejęciu firmy (jak przy restarcie rdzenia); wstrzymane biegi przeżywają.
  • Firmy na lokalnym Dockerze nie przetrwają awarii swojego serwera.
  • Zegar — rozjechany czas serwera fałszuje ocenę, który członek żyje; NTP jest obowiązkowy.
  • Pamięć — każdy członek to ~150–300 MB, router ~50–80 MB.
  • Pierwszy start rdzenia z adopcją kontenerów (wersja z pulami) odtwarza kontenery modułów raz — wybierz okno niskiego ruchu. Kolejne restarty i przejęcia firm modułów nie restartują.
Ostatnio zaktualizowano:: 16.10.2026, 14:08
Contributors: Wiktor Koźmiński, Claude Fable 5.1
Prev
Pakowanie modułu