Repository navigation
feat(supervisor): add independent application listener and endpoints - #4936
TaylorMutch wants to merge 5 commits into
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
🌿 Preview your docs: https://nvidia-preview-pr-4936.docs.buildwithfern.com/openshell |
6969959 to
a1cae53
Compare
dfb34da to
d94fb6a
Compare
Compatibility review (advisory)New or updated breaking changes; see the full report for all findings. 1. rust-sdk — No explicit risk acceptance foundThe SDK-reachable raw::proto::SandboxConfigSnapshot and GetSandboxConfigResponse structs gain a required service_routes field, invalidating previously exhaustive literals and patterns.
2. typescript-sdk — No explicit risk acceptance foundRegenerated SandboxConfigSnapshot and GetSandboxConfigResponse types gain a required serviceRoutes array in the public raw entry point.
Review details
Advisory review; publication gates are unchanged. |
|
/ok to test d46a19f |
|
Label |
d46a19f to
8b7552b
Compare
|
Can we add a shared-mode ingress example showing how to reach the owning supervisor before discovery lands? |
|
What happens if a sandbox already has over 1,024 services when we enable the listener? |
|
Can we automate checking that rejected configuration closes existing streams? |
|
Do raw SDK users need a migration note for the new service_routes field? |
f0678f7 to
e3628be
Compare
|
@FrostGod, addressed the test/documentation follow-ups in 5759d05. Decisions on all four comments:
Validation: all 14 supervisor application-listener tests passed locally, including the new regression; |
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Reject excess service declarations before persistence, fence registration against shutdown, and require the expected application Service count in listener E2E checks. Add focused regression coverage. Signed-off-by: divesh <dgude@nvidia.com> Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Verify rejected application configuration closes existing WebSockets without affecting another sandbox, and accepted recovery cannot revive old streams. Document raw SDK message migration and shared-owner selection before discovery. Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
5759d05 to
e4990bb
Compare
Compatibility review (advisory)New or updated breaking changes; see the full report for all findings. 1. api-behavior — Risk explicitly acceptedGetSandboxConfig and configuration push previously worked independently of service count but now fail with ResourceExhausted for sandboxes exceeding 1,024 declarations when supervisor.app_listener.enabled is true.
Review details
Advisory review; publication gates are unchanged. |
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Summary
Add an opt-in supervisor TLS listener for declared HTTP and WebSocket services. Application traffic connects directly to the owning supervisor and sandbox loopback connector, bypassing the gateway.
Related Issue
Closes #4666. Part of RFC 0017.
Changes
Release Notes
The application listener remains opt-in and disabled by default.
Application listener request flow
This PR adds an opt-in TLS listener on the supervisor. The gateway supplies
configuration, while application traffic reaches declared sandbox services
through the selected sandbox instance's connector.
flowchart TB subgraph Configuration["Configuration and deployment"] SETTINGS["Operator enables app listener<br/>Domain, port, TLS Secret"] KUBE["Kubernetes driver / Helm<br/>Configure supervisor port and mount TLS material"] DECLARE["Sandbox service declaration<br/>Example: web → port 8080"] GW["OpenShell gateway<br/>Include service allowlist in sandbox configuration"] SETTINGS --> KUBE DECLARE --> GW end subgraph HOST["Supervisor process — dedicated or shared"] TLS["One process-level TLS listener<br/>Example: Pod IP:8443"] ROUTER["SNI dispatcher<br/>Select sandbox instance"] subgraph INSTANCE["Selected sandbox instance"] CONFIG["Apply accepted configuration<br/>Install routes and renew freshness"] CHECK["Validate installed route and freshness<br/>Require HTTP Host to match TLS SNI"] PROXY["HTTP/1.1 or WebSocket proxy<br/>Apply limits and sanitize headers"] CONNECTOR["This instance's<br/>BoundaryLoopbackConnector"] CONFIG -.->|"Route table and validity"| CHECK CHECK -->|"Accepted"| PROXY PROXY --> CONNECTOR end TLS --> ROUTER ROUTER --> CHECK OTHER["Other sandbox instances<br/>Separate routes, connectors and lifetimes"] ROUTER -->|"Other SNI hostname"| OTHER REVOKE["Route removed or changed,<br/>configuration rejected, freshness expires,<br/>or instance stops"] CONFIG -.-> REVOKE REVOKE -.->|"Reject affected traffic<br/>Close affected active streams"| PROXY end subgraph SANDBOX["Selected sandbox"] APP["Application<br/>127.0.0.1:8080"] end CLIENT["Client<br/>team--demo--web.apps.example.com"] EDGE["Externally configured ingress / proxy<br/>or direct connection"] KUBE -.->|"Startup configuration"| TLS GW -->|"Existing configuration push / poll"| CONFIG CLIENT --> EDGE EDGE -->|"TLS with application SNI"| TLS CONNECTOR -->|"Connect to declared loopback port"| APPinstances. Each retains its own connector, limits, and lifetime.
through successful configuration delivery or an accepted live push session.
Withdrawal or expiry also closes affected streams.
dedicated and shared supervisors expose Pod-IP endpoints without application
Services; callers must target the owning supervisor.
ingress for authentication. Gateway-mediated authentication is separate.
Advertisements, discovery, authoritative public URLs, the sandbox proxy, and
HTTPRoute reconciliation are outside this PR. The ingress box represents
infrastructure configured externally at this stage. Addresses and ports are
illustrative.
Automated Kubernetes listener checks
mise run e2e:kubernetes:app-listenerdeploys an isolated Helm release withmatching branch images, ephemeral TLS material and in-cluster clients, then
removes its test resources. It needs neither discovery nor the reference proxy.
The
test:e2eCI lane now runs four cases: dedicated push, dedicated poll,shared graceful takeover and shared abrupt takeover. These jobs use the existing
Kubernetes reusable workflow, enable pinned Calico in kind to verify NetworkPolicy
denial, upload failure evidence, and contribute to
Core E2E result.Coverage includes HTTP/WebSockets, verified application TLS, SNI/Host validation,
header handling, named/unnamed routes, Pod-IP ingress without app Services,
quiet-session freshness, selective withdrawal, gateway-outage expiry/recovery,
workload identity/memory preservation through shared-owner changes, and disabled
listener compatibility with gateway-mediated application access.
Local k3d validation covered dedicated push/poll and abrupt shared takeover,
including disabled-listener gateway compatibility. The combined stack also
exercised Go SDK discovery and two proxy replicas routing directly to supervisor
Pod IPs, including owner handover and route withdrawal. This live validation
preceded integration of the latest route-limit and shutdown-fencing hardening;
focused regression tests cover that integration. Current-head remote CI results
are tracked in this PR's checks.
Testing
Live Kubernetes validation preceded integration of the latest listener hardening;
focused Rust regressions validate that final integration. New-head CI is pending.
Qualification limits
Live HA validation exercised supervisor process/Pod loss on a local k3d cluster. Whole-node failure, real network partitions, metric-driven HPA, sustained production load, and published release artifacts remain unqualified. Grant-file projection contributes to revocation propagation time. Main now includes #4842 for gateway-mediated hashed hostnames. Direct application-listener/discovery hostnames remain readable; extending hashed naming to that path and installation-status presentation (#4670) remain separate work.
The example Kubernetes HTTPRoute controller is a deferred compatibility POC outside this stack.
Checklist
PR dependency order: #4936 → #4940 → #4941 → #4956 → #4963 → #4964 → #4957 → #4958.