Skip to content

Overview

How rivers' components fit together on a Kubernetes cluster.

What gets deployed

Component Image Purpose
rivers-operator ghcr.io/ion-elgreco/rivers-operator Reconciles CodeLocation CRs into Deployments; runs admission webhooks
rivers-ui ghcr.io/ion-elgreco/rivers-ui Web UI (Leptos SSR + WASM hydration), reads run state from SurrealDB
SurrealDB surrealdb/surrealdb:v3 (subchart) Shared state — runs, events, asset materializations

The rivers Helm chart wires all three together; the rivers-crds Helm chart ships the cluster-scoped CustomResourceDefinitions (CodeLocation, Run) that the operator watches.

How it fits together

   ┌────────────────┐                                ┌─────────────┐
   │  User Browser  │                                │   kubectl   │
   └────────┬───────┘                                └──────┬──────┘
            │ HTTP + SSE                                    │ apply
            │                                               │ CodeLocation
            │                                               │ CR
            ▼                                               ▼
   ┌────────────────┐                              ┌────────────────┐
   │   rivers-ui    │── gRPC :50052 (registry) ───▶│ rivers-operator│
   │  Leptos SSR    │   discover CodeLocations &   │ • registry     │
   │     :3000      │   their grpc endpoints       │       :50052   │
   └─┬──────────┬───┘                              │ • webhook      │
     │          │                                  │       :9443    │
     │          │                                  │ • watches CL   │
     │          │  gRPC :3001                      └───────┬────────┘
     │          │  (trigger run,                           │ creates
     │          │   materialize asset)                     │ Deployment
     │          │                                          │ + Service
     │          │                                          ▼
     │          │                                 ┌────────────────┐
     │          └────────────────────────────────▶│ CodeLocation   │
     │                                            │ Pod(s)         │
     │                                            │ rivers serve   │
     │                                            │     :3001      │
     │  reads runs /                              │ user pipeline  │
     │  asset state                               │ image          │
     │                                            └───────┬────────┘
     │                                                    │ writes
     │                                                    │ run events
     ▼                                                    ▼
   ┌──────────────────────────────────────────────────────────────┐
   │                       SurrealDB :8000                        │
   └──────────────────────────────────────────────────────────────┘

kubectl apply lands a CodeLocation CR (the mutating webhook stamps spec.identity; the validating webhook rejects changes to it). The operator watches the CR, resolves spec.image:spec.tag to a digest (or trusts spec.digest directly), and reconciles a Deployment + Service running rivers serve on the user's pipeline image; it then registers the resulting gRPC endpoint in its in-process CodeLocationRegistry.

The UI, on every page load, queries that registry over gRPC :50052 to discover what CodeLocations exist and where to reach them. Read paths (run history, asset materializations, event logs) go straight to SurrealDB. Write paths (trigger run, force-materialize an asset, evaluate a sensor) dial the relevant CodeLocation pod's gRPC :3001 directly — the UI never proxies compute. The CodeLocation pod streams run progress back into SurrealDB, which the UI relays to the browser via Server-Sent Events (/api/events, consumed by EventSource after page hydration).

Reconciliation sequence

A CodeLocation going from kubectl apply to Ready:

   kubectl     k8s API      operator      img registry    Pod      SurrealDB
      │           │             │              │            │            │
      │           │             │              │            │            │
      │─ apply CodeLocation CR ▶│              │            │            │
      │           │             │              │            │            │
      │           │─ webhook ──▶│              │            │            │
      │           │             │              │            │            │
      │           │◀─ stamp id ─┤              │            │            │
      │           │             │              │            │            │
      │           │─ watch evt ▶│              │            │            │
      │           │             │              │            │            │
      │           │             │── HEAD ─────▶│            │            │
      │           │             │              │            │            │
      │           │             │◀── digest ───┤            │            │
      │           │             │              │            │            │
      │           │◀── create Deployment + Service ─────────┤            │
      │           │             │              │            │            │
      │           │── schedule pod ────────────────────────▶│            │
      │           │             │              │            │            │
      │           │             │  register endpoint in     │            │
      │           │             │  CodeLocationRegistry     │            │
      │           │             │  (in-process, :50052)     │            │
      │           │             │              │            │            │
      │           │◀ patch status: phase=Ready,│            │            │
      │           │  grpcEndpoint=<svc>:3001   │            │            │
      │           │             │              │            │            │
      │           │             │              │            │ start      │
      │           │             │              │            │ rivers     │
      │           │             │              │            │ serve      │
      │           │             │              │            │ :3001      │
      │           │             │              │            │            │

The mutating admission webhook stamps spec.identity (UUID) on create; the validating webhook rejects changes to it on update. digestRefreshInterval causes the operator to re-poll the registry periodically — semver-looking tags are cached after the first resolve since they're treated as immutable.

Run sequence

A user opens the UI and triggers an asset materialization:

   Browser     rivers-ui      operator       Pod       SurrealDB
      │            │             │            │             │
      │            │             │            │             │
      │─ HTTP ────▶│             │            │             │
      │            │             │            │             │
      │            │─ list CLs ─▶│            │             │
      │            │             │            │             │
      │            │◀── CLs ─────┤            │             │
      │            │  (incl. grpcEndpoint per CL)           │
      │            │             │            │             │
      │            │─ read runs / assets ──────────────────▶│
      │            │             │            │             │
      │            │◀───────────────────────────────────────┤
      │            │             │            │             │
      │◀── HTML ───┤             │            │             │
      │            │             │            │             │
      │── open EventSource /api/events ──▶    │             │
      │            │             │            │             │
      │            │             │            │             │
      │── click "materialize" ──▶│            │             │
      │            │             │            │             │
      │            │── gRPC: materialize ────▶│             │
      │            │             │            │             │
      │            │             │            │─ write ────▶│
      │            │             │            │  run events │
      │            │             │            │             │
      │            │◀ stream events from SurrealDB ─────────┤
      │            │             │            │             │
      │◀ SSE event ┤             │            │             │

The UI never proxies asset compute — it discovers the CodeLocation's Service from the operator's registry, then calls the pod's gRPC server (rivers serve on spec.grpcPort, default 3001) directly. Run/event state is read from SurrealDB and pushed to the browser via Server-Sent Events.

Next steps

Ready to install? See Kubernetes.