You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0a770d9
Browse filesBrowse the repository at this point in the historyBrowse files
authored
feat(kubernetes): support HA gateway rebalancing (#1868)
* feat(kubernetes): support HA gateway rebalancing
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
* perf(server): cache peer connections, tokens, and owner lookups
Every forwarded relay rebuilt its setup from scratch: an owner lookup, a
blocking read of the peer token, a TLS connect to the owning replica, and
a TokenReview plus Pod GET on the receiving side. Sandbox service routing
does this per HTTP request, so the apiserver calls scaled with traffic.
Cache all of it on ServerState:
- peer channels pooled per endpoint, so relays multiplex over one
connection instead of redialing
- peer tokens keyed by SHA-256, expiring at min(ttl, token exp) so a hit
cannot accept an expired token
- owner records for 3s against a 45s ownership TTL, still freshness
checked before use
Entries are evicted when a relay fails. Also raise HTTP/2
max_concurrent_streams to 1024, since pooling funnels every relay between
two replicas onto one connection and hyper's default of 200 sits below
the 256 pending-relay budget.
Signed-off-by: divesh <dgude@nvidia.com>
* perf(server): pool upstream connections for sandbox services
Each HTTP request to a sandbox service opened its own supervisor relay,
paying a new TCP connection and HTTP/1 handshake every time. Worse, it
counted against the 32 in-flight relay cap, so a service handling more
than 32 concurrent requests failed outright.
Pool idle upstreams per endpoint and port, up to 8 each for 15s. Reuse is
safe because the pool only returns a connection hyper reports as ready,
and HTTP/1 cannot start a request until the previous body has drained.
Upgrades are never pooled since they take the connection over, and a
failed send evicts that endpoint. Pruning is bounded per key, with the
full sweep limited to once per 30s.
Signed-off-by: divesh <dgude@nvidia.com>
* fix(server): address HA gateway review findings (#3449)
- Let a gateway own supervisor sessions without a peer endpoint. Requiring
one whenever the store is PostgreSQL broke every single-instance
PostgreSQL deployment, because no sandbox supervisor could connect.
A cross-replica request to an owner that advertises no endpoint now fails
immediately naming the cause, instead of retrying until the wait timeout.
- Close a supervisor session on heartbeat only when another replica owns it,
or after renewals fail for the ownership TTL. A database error no longer
drops every session heartbeating during an outage.
- Clamp owner record ages at zero so a skewed or corrupt stored timestamp
cannot produce a negative age.
- Bound the cross-object advisory lock with a lock timeout, so a stuck holder
fails instead of blocking every mutation in the fleet.
- Refuse to start when a peer endpoint is configured on a multi-replica
backend but peer authentication is unavailable, and warn when a
multi-replica backend has no peer endpoint at all.
- Reject a plaintext peer endpoint when the gateway serves TLS.
- Skip the sandbox watch poller on single-replica backends, where the local
update bus already sees every write.
- Rate-limit the peer owner cache sweep so an insert no longer scans the
whole map under the lock.
- Retry GET and HEAD on a pooled upstream the sandbox closed, instead of
returning 502, and drop an emptied endpoint from the pool right away.
- Document the gateway peer environment variables and the post-rollout
ownership skew operators should expect.
Signed-off-by: divesh <dgude@nvidia.com>
* fix(server): harden HA supervisor ownership
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
---------
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: divesh <dgude@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: divesh <dgude@nvidia.com>
Co-authored-by: Divesh Chowdary <47188680+FrostGod@users.noreply.github.com>
Copy file name to clipboardExpand all lines: architecture/gateway.md
+72-2Lines changed: 72 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -354,6 +354,70 @@ authenticated sandbox ID with any sandbox ID or name resolved from the request.
354
354
Supervisor control and relay streams require a matching sandbox principal before
355
355
the gateway registers the session or bridges relay bytes.
356
356
357
+
## HA Supervisor Ownership
358
+
359
+
In multi-replica Kubernetes deployments, every gateway pod can accept client
360
+
RPCs, but a sandbox supervisor maintains one active stream to one gateway
361
+
replica at a time. The connected replica publishes a short-lived supervisor
362
+
owner record in the shared Postgres object store with its replica id, peer DNS
363
+
endpoint, supervisor instance id, and connection epoch. Ownership does not move
364
+
because another gateway receives a client request. It changes only when the
365
+
supervisor opens a new control stream, usually after the previous owner pod is
366
+
terminated or the stream breaks. A reconnect from the same supervisor instance
367
+
with a newer epoch can supersede the previous owner before the TTL expires, and
368
+
heartbeats from the active connection renew that current owner record.
369
+
Cleanup from an older connection checks the shared owner record before and
370
+
after changing sandbox readiness. It cannot demote a sandbox after a newer
371
+
replica has published replacement ownership.
372
+
373
+
Session-bound operations such as exec, TCP forwarding, file sync, and sandbox
374
+
service routing first check the local session registry. If the supervisor is
375
+
owned by another gateway replica, the serving gateway opens an internal
376
+
`PeerRelay` stream to that owner and asks it to open the supervisor relay. This
377
+
keeps client traffic working when a Kubernetes Service routes the client to a
378
+
non-owner gateway pod. If a peer owner is stale or unreachable during a rollout,
379
+
the serving gateway retries ownership lookup until the normal relay wait
380
+
deadline. Each retry re-reads the owner record, so a supervisor reconnect or
381
+
heartbeat can surface a new owner; if no fresh reachable owner appears before
382
+
the deadline, the client operation fails rather than electing an owner itself.
383
+
Provider-readiness reports, endpoint-status reports, and provider-status reads
384
+
also follow the durable owner record through unary peer RPCs. The owning replica
385
+
validates the current supervisor session and keeps the in-memory evidence; a
386
+
non-owner never accepts evidence from a stale local session or projects a
387
+
remote session as disconnected.
388
+
389
+
Nothing redistributes established sessions, so after a rolling restart the last
390
+
surviving replica holds most sessions and a new replica serves none until
391
+
sandboxes reconnect. That skew decays only as sandboxes churn. Client traffic
392
+
stays correct throughout because a non-owner relays to the owner.
393
+
394
+
File upload and download use tar-over-SSH through the same relay path. A gateway
395
+
pod termination drops the active SSH proxy byte stream, so the CLI retries the
396
+
whole sync operation with a fresh SSH session instead of attempting mid-stream
397
+
resume.
398
+
399
+
Gateway peer RPCs authenticate with Kubernetes ServiceAccount identity rather
400
+
than a shared secret. Helm mounts a projected, pod-bound token with audience
401
+
`openshell-gateway-peer`; the receiving gateway validates it through
402
+
TokenReview, checks the live pod UID and chart selector labels, and authorizes
403
+
only the internal peer RPC methods. When gateway TLS is enabled, peer clients
404
+
also trust the chart CA, present the chart-generated client certificate for
405
+
mTLS, and verify the stable gateway Service DNS name even when connecting to a
406
+
Deployment pod IP.
407
+
408
+
`WatchSandbox` uses the local update bus for same-replica writes. On
409
+
multi-replica backends one shared poller per gateway observes resource-version
410
+
changes made by other replicas and feeds that bus for all local watchers,
411
+
avoiding a database poll per client stream. SQLite deployments do not run the
412
+
poller because they are single-replica and the local bus already sees every
413
+
write.
414
+
415
+
Mutations whose invariants span sandbox, provider-profile, policy, or provider
416
+
records take a process-local mutex and a shared PostgreSQL advisory lock. The
417
+
database session remains dedicated to the request and closes when the guard is
418
+
dropped, which releases the lock on normal completion, cancellation, or error.
419
+
SQLite deployments use only the local mutex because they are single-replica.
420
+
357
421
## API Surface
358
422
359
423
The gateway API is organized around platform objects and operational streams:
@@ -753,7 +817,7 @@ migrations backfill existing rows with version 1.
753
817
Provider profile imports, updates, and deletes hold the sandbox synchronization
754
818
guard while checking attached-sandbox dynamic token grant ambiguity or in-use
755
819
state and writing the profile record. Sandbox creation with initial providers and
756
-
sandbox provider attach/detach use the same guard, so one gateway process cannot
820
+
sandbox provider attach/detach use the same guard, so gateway replicas cannot
757
821
interleave a profile mutation with a sandbox provider-set mutation that would
758
822
leave an ambiguous final dynamic-token state or a deleted custom profile that is
759
823
still referenced by a sandbox.
@@ -819,7 +883,13 @@ Provider receipts, installation status, and common operations represent absolute
819
883
820
884
Provider installation reports belong to the existing `ConnectSupervisor` session. Each report names that session, has an increasing sequence, and expires unless the supervisor reports again. Reconnection or disconnect invalidates prior observations; stored change records survive a gateway restart, but runtime evidence does not. Replaying an identical report cannot extend its lifetime.
821
885
822
-
Reports and status also compare the supervisor instance with the sandbox's persisted current instance. A different supervisor becoming current invalidates an older connection, including one retained by another gateway replica. Observations stay local to the gateway holding the supervisor session; a status request reaching a replica without that session returns pending. Multi-replica deployments therefore retain the existing supervisor-session routing requirement.
886
+
Reports and status also compare the supervisor instance with the sandbox's
887
+
persisted current instance. A different supervisor becoming current invalidates
888
+
an older connection, including one retained by another gateway replica.
889
+
Observations stay local to the gateway holding the supervisor session. A status
890
+
request or report reaching another replica follows the shared owner record to
891
+
that gateway, which remains the sole authority for accepting and projecting the
892
+
session's evidence.
823
893
824
894
The supervisor reports success only after it installs the matching credentials, activates the effective policy, and receives an acknowledgment from the authenticated workload boundary that it installed the environment for future processes. Environment synchronization shares the process-launch lock, and its acknowledgment identifies the exact publication, including retries at the same provider revision. Failed policy installation cannot reuse evidence for a different installed policy. Ready and revoked statuses also recheck the requested sandbox, provider, attachment and configuration identities; revision fingerprints are compared only for equality. Revocation applies to future credential resolution and future processes. Requests already forwarded upstream can still finish.
0 commit comments