Files
OpenShell/docs/kubernetes/setup.mdx
T
0a770d9173 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>
2026-09-21 21:22:12 +00:00

342 lines
16 KiB
Plaintext

---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Set Up OpenShell on Kubernetes"
sidebar-title: "Setup"
description: "Deploy the OpenShell gateway to a Kubernetes cluster using the official Helm chart from GHCR."
keywords: "Generative AI, Cybersecurity, Kubernetes, Helm, Gateway, Deployment, OCI, GHCR, Installation"
position: 1
---
<Warning>
The OpenShell Helm chart is experimental and under active development. Templates, values, and defaults can change between releases. Do not use it in production.
</Warning>
Use the Kubernetes deployment when the gateway should run on a shared cluster, in a cloud environment, or as part of team infrastructure. The Helm chart handles PKI bootstrap, RBAC, sandbox namespace setup, and the gateway workload. It uses a StatefulSet by default for the SQLite database, and can render a Deployment when `server.externalDbSecret` points at an external database.
## Prerequisites
Make sure the following are in place before you install.
| Prerequisite | Required | Notes |
|---|---|---|
| Kubernetes 1.29+ with RBAC enabled | Yes | No additional notes. |
| Helm 3.x | Yes | No additional notes. |
| Agent Sandbox controller and CRDs | Yes | Install before the OpenShell chart. Refer to [Install Agent Sandbox](#install-agent-sandbox). |
| cert-manager | No | Refer to [Managing Certificates](/kubernetes/managing-certificates). Use cert-manager only if you prefer it over the built-in PKI job. |
| Kubernetes Gateway API | No | Refer to [Ingress](/kubernetes/ingress). Use it only for external access without port-forwarding. |
## Install Agent Sandbox
OpenShell uses the [Agent Sandbox](https://agent-sandbox.sigs.k8s.io) Kubernetes SIG project to provision sandbox pods. Install the Agent Sandbox controller and its CRDs on your cluster before installing the OpenShell Helm chart.
Apply the latest release manifest:
```shell
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/latest/download/manifest.yaml
```
This creates the `agent-sandbox-system` namespace, installs the `sandboxes.agents.x-k8s.io` CRD, and starts the controller.
The Helm chart checks for a supported Agent Sandbox API before it creates
gateway resources. This preflight is enabled by default. Disable it only for
offline `helm template` rendering, where Helm cannot discover cluster APIs:
```shell
helm template openshell oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--set agentSandbox.preflight.enabled=false
```
The chart does not install or upgrade the cluster-scoped Agent Sandbox CRDs or
controller.
<Note>
**Air-gapped clusters:** mirror the manifest above and the `registry.k8s.io/agent-sandbox/agent-sandbox-controller` image referenced inside it to your internal registry, then point the manifest's image reference at your mirror before applying. You will also need to mirror the OpenShell gateway and sandbox images — see the chart's `image.repository` value for the gateway and `server.sandboxImage` / `server.supervisorImage` for the sandbox runtime.
</Note>
Confirm the controller pod is running before proceeding:
```shell
kubectl -n agent-sandbox-system get pods
```
The controller pod should reach `Running` status within a few seconds. For cluster-specific setup instructions, including KinD and GKE walkthroughs, refer to the [Agent Sandbox getting started guide](https://agent-sandbox.sigs.k8s.io/docs/getting_started/).
### Upgrade Agent Sandbox
OpenShell detects the served Agent Sandbox `Sandbox` API when the Kubernetes gateway first needs it and caches that choice for the gateway process. If you upgrade Agent Sandbox in place, restart the OpenShell gateway after the Agent Sandbox controller and CRD rollout completes so the gateway can detect the served API versions again. Existing sandboxes keep running during the upgrade, and the restarted gateway can continue managing them.
## Install OpenShell
<Steps>
## Create the namespace
```shell
kubectl create namespace openshell
```
## Install the chart
Install from the OCI registry on GHCR. Replace `<version>` with the chart version you want to install.
```shell
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set supervisor.sandboxRuntime.networkPolicyEnforced=true
```
To use the latest development build instead of a stable release:
```shell
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version 0.0.0-dev \
--namespace openshell \
--set supervisor.sandboxRuntime.networkPolicyEnforced=true
```
The chart automatically generates PKI secrets on first install using pre-install Helm hooks. No manual secret creation is required.
### Split gateway and workspace releases
For a platform-managed namespace, install the gateway without namespace-scoped
sandbox resources, then install the workspace chart in the sandbox namespace:
```shell
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set supervisor.sandboxRuntime.networkPolicyEnforced=true \
--set workspaceResources.enabled=false \
--set server.sandboxNamespace=app-a
helm upgrade --install openshell-workspace \
oci://ghcr.io/nvidia/openshell/openshell-workspace \
--version <version> \
--namespace app-a \
--set gateway.serviceAccount.name=openshell \
--set gateway.serviceAccount.namespace=openshell
```
The workspace chart does not create the namespace or deploy a gateway. It owns
only the sandbox ServiceAccount, Role, RoleBinding, and NetworkPolicy in its
release namespace. For one pre-provisioned namespace, keep
`server.drivers.kubernetes.workspaceMode=shared` and set
`server.sandboxNamespace=app-a`. To map multiple workspaces to separately
provisioned namespaces, use `workspaceMode=operator`, configure exactly one of
`operatorNamespaceLabel` or `operatorNamespaceFile`, and install the workspace
chart in every allowlisted namespace.
## Wait for the gateway to be ready
```shell
kubectl -n openshell rollout status statefulset/openshell
```
If you set `workload.kind=deployment`, wait on the Deployment instead:
```shell
kubectl -n openshell rollout status deployment/openshell
```
## Connect to the gateway
For local evaluation, use a port-forward:
```shell
kubectl -n openshell port-forward svc/openshell 8080:8080
```
<Warning>
The port-forward is for local evaluation only. For shared environments, expose the gateway through your ingress controller or access proxy. Refer to [Ingress](/kubernetes/ingress) for an external access option.
</Warning>
## Install the TLS client bundle
The chart generates an mTLS bundle for transport security. Kubernetes deployments do not use that bundle as user authentication; configure OIDC or a trusted access proxy as described in [Access Control](/kubernetes/access-control). For local port-forwarded access, copy the generated bundle so the CLI can verify the gateway certificate:
```shell
mkdir -p ~/.config/openshell/gateways/k8s/mtls
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.ca\.crt}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/ca.crt
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/tls.crt
kubectl -n openshell get secret openshell-client-tls \
-o jsonpath='{.data.tls\.key}' | base64 -d > ~/.config/openshell/gateways/k8s/mtls/tls.key
```
The server certificate SANs include `localhost` and `127.0.0.1`, so hostname verification passes over the port-forward without extra flags.
## Register with the CLI
In another terminal, register the gateway with the user authentication mode you configured and verify it is reachable. For example, with OIDC:
```shell
openshell gateway add https://127.0.0.1:8080 --local --name k8s \
--oidc-issuer https://your-idp.example.com/realms/openshell \
--oidc-client-id openshell-cli
openshell status
```
</Steps>
## Configure Chart Values
The most commonly changed values are:
| Value | Purpose |
|---|---|
| `image.repository` / `image.tag` | Gateway container image. Defaults to `ghcr.io/nvidia/openshell/gateway:latest`. |
| `replicaCount` | Number of gateway replicas. Values above `1` require shared PostgreSQL through `server.externalDbSecret`. |
| `workload.kind` | Gateway workload controller. Use `statefulset` for SQLite or `deployment` with `server.externalDbSecret`. |
| `workload.allowMultiReplicaStatefulSet` | Allow `replicaCount > 1` with `workload.kind=statefulset`. Prefer Deployment for external database-backed multi-replica gateways. |
| `server.sandboxNamespace` | Namespace where sandbox pods are created. Defaults to the Helm release namespace when left empty. |
| `workspaceResources.enabled` | Create namespace-scoped sandbox prerequisites from the gateway chart. Disable when installing the workspace chart separately. |
| `server.externalDbSecret` | Secret containing a PostgreSQL connection URI in the `uri` key. Use when the database is managed outside the chart. |
| `server.telemetryEnabled` | Enable anonymous OpenShell telemetry from the gateway and its sandbox supervisors. Set to `false` to opt out. |
| `server.sandboxImage` | Default sandbox image used when a sandbox does not specify one. |
| `server.sandboxImagePullSecrets` | Image pull secrets attached to sandbox pods. Referenced Secrets must exist in the sandbox namespace. |
| `server.grpcEndpoint` | Endpoint that sandbox supervisors use to call back to the gateway. Must be reachable from inside the cluster. |
| `server.disableTls` | Run the gateway over plaintext HTTP. Use only behind a trusted transport. |
| `server.auth.allowUnauthenticatedUsers` | Accept user-facing calls without OIDC or mTLS credentials. Use only for trusted local development or a fully trusted access proxy. |
| `server.enableLoopbackServiceHttp` | Enable local plaintext HTTP for loopback sandbox service URLs. Defaults to `true`. |
| `pkiInitJob.serverDnsNames` / `certManager.serverDnsNames` | Additional gateway server DNS SANs. Wildcard SANs also enable sandbox service URLs under that domain. |
| `supervisor.sandboxRuntime.networkPolicyEnforced` | Required acknowledgement that the cluster CNI enforces ingress and egress `NetworkPolicy` in sandbox namespaces. |
| `supervisor.sandboxRuntime.boundaryPort` | Non-privileged TLS port used between paired supervisor and sandbox Pods. |
| `upstreamProxy` | Operator-owned corporate HTTP forward proxy for policy-approved TLS egress. Refer to [Configure a Corporate Upstream Proxy](#configure-a-corporate-upstream-proxy). |
### Run multiple gateway replicas
Use a Deployment and shared PostgreSQL for high availability:
```yaml
replicaCount: 2
workload:
kind: deployment
server:
externalDbSecret: openshell-postgres
```
The chart creates the peer Service, projected ServiceAccount identity, and RBAC
that gateway replicas use to route exec, forwarding, file-sync, and service
traffic to the replica holding the external supervisor's live session. Peer
traffic uses the chart TLS materials when gateway TLS is enabled. Do not use
the per-pod SQLite database with more than one replica.
Use a values file for repeatable deployments:
```shell
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set supervisor.sandboxRuntime.networkPolicyEnforced=true \
--values my-values.yaml
```
To use private sandbox images, create a `kubernetes.io/dockerconfigjson` Secret
in the sandbox namespace and reference its name:
```shell
kubectl -n openshell create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username="$REGISTRY_USER" \
--docker-password="$REGISTRY_TOKEN"
```
```yaml
server:
sandboxImage: registry.example.com/team/openshell-sandbox:latest
sandboxImagePullSecrets:
- name: regcred
```
## Configure a Corporate Upstream Proxy
Configure a corporate forward proxy when sandbox TLS egress cannot dial the Internet directly. OpenShell evaluates policy and SSRF checks before it opens an HTTP CONNECT tunnel through the proxy. The proxy URL is operator-owned configuration. Sandbox environment variables cannot select, replace, or bypass it.
Create the credential Secret in the sandbox namespace when the proxy requires Basic authentication. The Secret value uses the `user:pass` form.
```shell
kubectl -n openshell create secret generic corporate-proxy-auth \
--from-literal=credentials="$PROXY_USER:$PROXY_PASSWORD"
```
Add the proxy settings to your Helm values file. Replace the DNS suffixes and CIDRs in `noProxy` with values for your cluster. `noProxy` bypasses only the corporate proxy. OpenShell policy evaluation still applies.
```yaml
upstreamProxy:
url: http://proxy.corp.example:8080
noProxy: .svc,.svc.cluster.local,10.96.0.0/12,10.244.0.0/16
authSecret:
name: corporate-proxy-auth
key: credentials
authAllowInsecure: true
```
Use `authAllowInsecure: true` only when you accept that Basic authentication is cleartext on the connection to an `http://` proxy. The initial release supports `http://` proxy endpoints and TLS CONNECT egress. It does not support HTTPS-to-proxy, custom corporate CA bundles, or forwarding plain HTTP egress through the proxy.
The credential mounts only in the separately scheduled supervisor Pod. The
sandbox workload cannot read it through its environment or volumes.
## RBAC
The chart creates the following RBAC resources in the release namespace:
| Resource | Scope | Name |
|---|---|---|
| ServiceAccount | Namespace | `openshell` |
| ServiceAccount | Namespace | `openshell-sandbox` (for sandbox pods) |
| Role + RoleBinding | Namespace | `openshell-sandbox` |
| ClusterRole + ClusterRoleBinding | Cluster | `openshell-node-reader` |
The namespaced Role covers sandbox lifecycle and identity:
| API Group | Resource | Verbs |
|---|---|---|
| `agents.x-k8s.io` | `sandboxes`, `sandboxes/status` | create, delete, get, list, patch, update, watch |
| `""` | `events` | get, list, watch |
| `""` | `pods` | get |
The ClusterRole grants node inspection and token validation:
| API Group | Resource | Verbs |
|---|---|---|
| `authentication.k8s.io` | `tokenreviews` | create |
| `""` | `nodes` | get, list, watch |
To use an existing ServiceAccount instead of creating one, set `serviceAccount.create=false` and supply its name:
```shell
helm upgrade --install openshell \
oci://ghcr.io/nvidia/openshell/helm-chart \
--version <version> \
--namespace openshell \
--set supervisor.sandboxRuntime.networkPolicyEnforced=true \
--set serviceAccount.create=false \
--set serviceAccount.name=my-existing-sa
```
The ServiceAccount must already have the Role and ClusterRole bindings described above.
## Probes
The gateway exposes `/healthz` for process liveness and `/readyz` for dependency-aware readiness on the health port. The Helm chart wires both into Kubernetes probes:
- `startupProbe` and `livenessProbe` use `/healthz`.
- `readinessProbe` uses `/readyz`, which reflects the latest result of an in-process background database check.
## Next Steps
- Kubernetes sandboxes use separate workload and directly managed supervisor Pods; refer to [Sandbox runtime](/kubernetes/sandbox-runtime).
- To enable automatic certificate rotation with cert-manager, refer to [Managing Certificates](/kubernetes/managing-certificates).
- To expose the gateway externally without port-forwarding, refer to [Ingress](/kubernetes/ingress).
- To configure OIDC or reverse-proxy authentication, refer to [Access Control](/kubernetes/access-control).
- To create your first sandbox, refer to [Manage Sandboxes](/sandboxes/manage-sandboxes).