Skip to main content

helm-dev-environment

Start up, tear down, and configure the local Kubernetes development environment for OpenShell. Uses k3d (Docker-backed k3s) + Skaffold + Helm. Covers cluster lifecycle, optional add-ons (Keycloak OIDC, Envoy Gateway), HA testing, and port mappings. Trigger keywords - local k8s, local cluster, k3d, skaffold, helm dev, start cluster, stop cluster, tear down cluster, delete cluster, create cluster, helm:k3s, helm:skaffold, local dev environment, dev cluster, k8s dev, envoy gateway local, keycloak local, high availability, HA.

معلومات المصدر

المستودع
NVIDIA/OpenShell
آخر نشاط في المصدر
٤ نوفمبر ٢٠٢٦ في ٢١:٣٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢١٬١٣٠
التفرعات
٢٬٠٦٩

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
helm-dev-environment
description
Start up, tear down, and configure the local Kubernetes development environment for OpenShell. Uses k3d (Docker-backed k3s) + Skaffold + Helm. Covers cluster lifecycle, optional add-ons (Keycloak OIDC, Envoy Gateway), HA testing, and port mappings. Trigger keywords - local k8s, local cluster, k3d, skaffold, helm dev, start cluster, stop cluster, tear down cluster, delete cluster, create cluster, helm:k3s, helm:skaffold, local dev environment, dev cluster, k8s dev, envoy gateway local, keycloak local, high availability, HA.
metadata
{"internal":true}
# Helm Dev Environment Set up, run, and tear down the local Kubernetes development environment for OpenShell. The stack is: **k3d** (Docker-backed k3s) for the cluster, **Skaffold** for image builds and Helm deploys, and the **OpenShell Helm chart** (`deploy/helm/openshell/`). --- ## Prerequisites - Docker Desktop (macOS) or Docker Engine (Linux) running - `mise install` completed (provides `k3d`, `kubectl`, `skaffold`, `helm`) --- ## Startup ### 1. Create the cluster ```bash mise run helm:k3s:create ``` Creates a k3d cluster and merges its kubeconfig into the worktree-local `kubeconfig` file. When the named cluster already exists, the task starts any stopped containers and refreshes same-named kubeconfig entries so a recreated load balancer's current API port takes effect. Also applies the upstream agent-sandbox CRDs/controller (pinned via `AGENT_SANDBOX_VERSION` in `tasks/scripts/helm-k3s-local.sh`, fetched from `github.com/kubernetes-sigs/agent-sandbox` releases), enables its OTLP tracing on v0.5 and later, installs an OTLP trace collector and UI in the `observability` namespace, and preloads the default sandbox image into k3d so the first sandbox create does not wait on a large registry pull. Traefik is disabled at cluster creation time. **Multi-worktree support:** the cluster name is derived from the last component of the current git branch (e.g. branch `chore/1234-local-dev/octocat` → cluster `openshell-dev-octocat`). Each worktree therefore gets its own isolated cluster and its own `kubeconfig` file. Override with `HELM_K3S_CLUSTER_NAME` to force a specific name or share one cluster across worktrees. Port mappings created at cluster time (cannot be changed without recreating): | Host port | Target | Used by | |-----------|--------|---------| | `8080` | Port `80` via k3d load balancer | Envoy Gateway LoadBalancer service (`values-gateway.yaml`) | Override with env vars before running `helm:k3s:create`: - `HELM_K3S_LB_HOST_PORT` (default: `8080`) - `HELM_K3S_PRELOAD_SANDBOX_IMAGE` (default: `nvcr.io/nvidia/base/ubuntu:24.04`; set to an empty value to skip) - `HELM_K3S_COLLECTOR_IMAGE` (default: `mcr.microsoft.com/dotnet/aspire-dashboard:latest`) - `HELM_K3S_COLLECTOR_HEALTH_TIMEOUT` (default: `120` seconds) ### Shared supervisor plumbing Set `gatewayConfig.openshell.drivers.kubernetes.supervisor_topology` using a values file (the table key is literally `openshell.drivers.kubernetes`): ```yaml gatewayConfig: openshell.drivers.kubernetes: supervisor_topology: gateway ``` The gateway chart creates a shared supervisor Deployment (`supervisor.replicas`, default one), Service, NetworkPolicy, and an empty `<release>-supervisor-token` Secret in the release namespace, regardless of shared/managed/operator workspace mode. The gateway mints a supervisor token from its sandbox-JWT signing key, writes it into that Secret (the gateway Role may `get`/`patch` only that Secret), and renews it at half its lifetime; the supervisor mounts the Secret and presents the token on the private `openshell.supervisor.host.v1` assignment stream, which accepts nothing else. The topology requires gateway TLS and the signing key (`server.disableTls` is rejected; the gateway refuses to start without `gateway_jwt`). New sandbox generations run as sandbox instances in that process. The chart starts `--mode=shared`, sets `--gateway-endpoint` to the gateway gRPC URL, mounts the token at `--token-file`, and binds `--address` (5501) to a plain TCP health listener for the `tcpSocket` probes; nothing dials the supervisor but sandbox runtimes (5500) and the kubelet. Build the gateway, supervisor, and sandbox images from the same branch: all three must support the routed runtime connection and the assignment stream. `workspace` is reserved and rejected. Omit the field to disable it. `mise run e2e:kubernetes:shared-supervisor` runs the focused e2e (`ci/values-supervisor.yaml`). The task prepends that overlay to any colon-separated `OPENSHELL_E2E_KUBE_EXTRA_VALUES` list, so layer a workspace-mode overlay to cover managed namespaces: ```bash OPENSHELL_E2E_KUBE_EXTRA_VALUES=deploy/helm/openshell/ci/values-workspace-managed.yaml \ OPENSHELL_E2E_KUBE_IMAGE_PULL_SECRET=e2e-regcred \ mise run e2e:kubernetes:shared-supervisor ``` ### Shared supervisor high availability The shared supervisor is always a pool of `supervisor.replicas` members (default 1), or an HPA-managed pool when `supervisor.autoscaling.enabled=true`. `ci/values-supervisor-autoscaling.yaml` enables the memory HPA (default `512Mi` average per Pod, two to four members, one removal every two minutes after five minutes of stabilization). It requires the resource metrics API, usually Metrics Server. The HPA omits the Deployment's fixed replica count and ignores `supervisor.replicas`; top-level `autoscaling` still controls only the gateway. Do not combine the HPA overlay with the manual 1→2→1 scaling E2E: that test expects Helm to own replicas. Changing HPA bounds or target must leave the gateway config and supervisor Pod template unchanged, as checked by `deploy/helm/test-supervisor-pool-scaling.sh`. With the gateway topology the chart renders the gateway's `[openshell.gateway.supervisor_pool]` tuning table (only `recovery_timeout_secs` from `supervisor.recoveryTimeoutSeconds`, default 60; the driver's `shared_supervisor` table stays unchanged), a RollingUpdate Deployment (`maxSurge: 1`, `maxUnavailable: 0`) with a 60-second grace period, no affinity unless `supervisor.affinity` is set (top-level `affinity` applies only to the gateway), the pool identity env vars (`OPENSHELL_SUPERVISOR_POOL_MEMBER_ID` and `_ADVERTISE_IP` from the Pod name and IP), and a `maxUnavailable: 1` PodDisruptionBudget unless `supervisor.podDisruptionBudget.enabled=false` (default `true`; toggling it changes only the PodDisruptionBudget). Each supervisor resource has its own template: `supervisor.yaml` (Deployment), `supervisor-service.yaml`, `supervisor-networkpolicy.yaml`, `supervisor-token-secret.yaml`, `supervisor-pdb.yaml`, and `supervisor-hpa.yaml`. `ci/values-supervisor-ha.yaml` selects two replicas and a 60-second recovery timeout. The topology needs gateway TLS: the e2e harness keeps it on with `ci/values-tls-port-forward.yaml` and `OPENSHELL_E2E_KUBE_TLS=1`, which reaches the gateway over mTLS through its port-forward. `mise run e2e:kubernetes:shared-supervisor-ha` sets both, creates a task-scoped k3d cluster, kills the supervisor Pod that owns a sandbox, and checks that the sandbox keeps its Pod, generation, main process, and in-memory state. The rendered gateway configuration and supervisor Pod template do not depend on `supervisor.replicas`; `deploy/helm/test-supervisor-pool-scaling.sh` (part of `mise run helm:test`) renders a pool at 1, 2, and 3 replicas and fails if anything but the replica count differs. `mise run e2e:kubernetes:shared-supervisor-scaling` installs a one-member pool (`ci/values-supervisor-ha-scaling.yaml`), then the test runs `helm upgrade --reuse-values --set supervisor.replicas=2` and back to 1 against the chart the harness installed (`OPENSHELL_E2E_KUBE_CHART`), probing exec and policy-mediated networking throughout. It checks that scaling up moves nothing, that the member removed by scaling down hands its sandboxes to the other (`cause=leaving`), that no workload, gateway Pod, or remaining member restarts, and that a new sandbox runs after each step. It upgrades its release, so it runs on a release of its own; the two pool tests also share a nextest test group that never runs them at once. Against an existing cluster whose kubeconfig the shell environment may override, set `OPENSHELL_E2E_KUBECONFIG` (passed as `--kubeconfig` to kubectl and helm). On k3d, deploy the pool with the Skaffold profile, which layers the overlay and turns gateway TLS back on over `ci/values-skaffold.yaml`: ```bash cd deploy/helm/openshell KUBECONFIG=../../../kubeconfig skaffold run -p shared-supervisor-high-availability cd ../../.. ``` The plaintext registration from `helm:skaffold:run` does not apply. Keep `mise run helm:k3s:forward` running and set up the client certificate as in "With mTLS enabled" under [Connecting through the forwarding task](#connecting-through-the-forwarding-task). Both members share the single k3d node, so exercise Pod failures: ```bash export KUBECONFIG=kubeconfig OPENSHELL_GATEWAY_ENDPOINT=https://localhost:8090 openshell sandbox create --detach --name ha-probe -- sh -c 'echo $$ > /sandbox/main.pid; exec sleep infinity' openshell sandbox exec --name ha-probe --no-tty -- cat /sandbox/main.pid openshell sandbox get ha-probe # note the sandbox ID # Supervisor logs name the instance by its route, not its sandbox ID. kubectl get sandboxes.agents.x-k8s.io -A -l openshell.ai/sandbox-name=ha-probe \ -o jsonpath='{.items[0].metadata.annotations.openshell\.ai/sandbox-runtime-supervisor-session}' # The member whose latest admission, removal, or release line names the route owns it. kubectl -n openshell logs -l app.kubernetes.io/name=openshell-supervisor --prefix --tail=-1 \ | grep -E '(admitted assigned|removing withdrawn|relinquishing a) sandbox instance.* <route>$' # Force-delete the owner, the closest local stand-in for a crash. kubectl -n openshell delete pod <owner-pod> --grace-period=0 --force kubectl -n openshell logs -l app.kubernetes.io/name=openshell,app.kubernetes.io/instance=openshell \ -c openshell-gateway --prefix --tail=200 | grep 'taking over a shared sandbox instance' kubectl -n openshell logs -l openshell.ai/sandbox-id=<sandbox-id>,openshell.ai/boundary-role=workload \ --tail=200 | grep -E 'sandbox-supervisor-|Supervisor takeover confirmed' openshell sandbox exec --name ha-probe --no-tty -- cat /sandbox/main.pid # same PID ``` A plain `kubectl delete pod` exercises the graceful leave instead: the member announces it is leaving and the gateway hands its instances over at once. A member that stops without announcing it, as on node loss, is declared dead only after the 15-second membership TTL. `kubectl -n openshell rollout restart deployment/openshell-supervisor` replaces every member one at a time. The PID must survive each case, while an open `sandbox connect` or `exec` stream breaks and must reconnect. A `sandbox-supervisor-recovery-expired` finding in the workload log means no replacement confirmed within the recovery timeout, so the runtime terminated the workload. For RFC 0017 app-listener validation, enable the listener and supply the app TLS Secret on the shared Deployment, plus a separate NetworkPolicy allowing the test client/proxy to reach its app port. Runtime-port access does not grant app access. Discovery returns the acknowledged owner's Pod IP and app port; no headless app Service is needed. Exercise HTTP/WebSocket reconnection through graceful downscale and abrupt loss, preserving workload generation/PID/in-memory state and the public hostname. Verify stale-owner rejection independently of the replacement's success. Shared serving leases expire within fifteen seconds of issuance and do not renew from configuration alone. Do not claim seamless socket migration or infer external ingress readiness from an advertisement. ### 2. Deploy OpenShell **Iterative dev** (rebuilds on file changes, recommended during active development): ```bash mise run helm:skaffold:dev ``` **One-shot deploy** (build once and leave running): ```bash mise run helm:skaffold:run ``` Resource admission defaults to enabled and caller driver config to disabled. Driver-config scenarios need an explicit `allowDriverConfig` opt-in; external attachments also need administrator-controlled approval labels in the target namespace. GPU attachments and operator-selected image-pull Secrets are exempt from labels. Managed workspace image-pull Secrets are copied from the configured source in the gateway namespace; do not grant approval to the gateway database PVC or disable admission to make tests pass. The Skaffold flow builds distinct `gateway`, `sandbox`, and `supervisor` images and deploys the OpenShell Helm chart. The Kubernetes driver creates a capability-free workload Pod and a directly managed capability-free supervisor Pod. One namespace-wide NetworkPolicy denies direct egress from every OpenShell workload Pod. New sandbox generations use `runtime-initiated` control: the sandbox runtime dials its supervisor through `os-supervisor-<id>`. The legacy `os-boundary-<id>` Service keeps selecting the workload for old gateways during rolling upgrades. New gateways create both Services and bind the control-mode annotation to its runtime generation. The driver adds a per-sandbox `os-control-<id>` NetworkPolicy for that path only. Existing running generations keep their connection until replaced; stop/start and gateway recovery that recreates both Pods upgrade legacy generations automatically. Use compatible gateway, supervisor, and sandbox runtime images together; there is no legacy rollout flag. The `pkiInitJob` hook (a pre-install Job that runs `openshell-gateway generate-certs`) generates gateway and CLI TLS secrets on first install. Supervisor Pods project only `ca.crt` and authenticate gateway RPCs with sandbox bearer tokens. User client certificates and private keys remain outside supervisor and workload Pods. The default Skaffold values export gateway and Kubernetes-driver traces to the collector service installed by `helm:k3s:create`. Envoy Gateway is opt-in; see the Optional Add-ons section. The gateway Service uses ClusterIP. Access is via Envoy Gateway (port `8080`) or the unified local forwarding task: ```bash mise run helm:k3s:forward ``` The task forwards OTLP/gRPC to `http://127.0.0.1:4317` and the trace UI to `http://127.0.0.1:18888`. When Skaffold has deployed a Kubernetes gateway, it also forwards the gateway to `http://127.0.0.1:8090`; otherwise it continues with the collector ports only. A successful plaintext `helm:skaffold:run` registers the gateway under the worktree-specific k3d cluster name and selects it as the active gateway. Keep the forwarding task running while using those endpoints. ### Viewing local traces The gateway exports OTLP/gRPC to `http://openshell-collector.observability.svc.cluster.local:4317` through the default Skaffold values. Forward OTLP/gRPC and the trace UI to the host: ```bash mise run helm:k3s:forward ``` Open `http://127.0.0.1:18888` and exercise the gateway to inspect gateway and Kubernetes compute-driver spans under their distinct service names, along with Agent Sandbox controller reconciliation spans linked through the Sandbox trace-context annotation. The same command exposes OTLP/gRPC on `http://127.0.0.1:4317` and, when deployed, the Kubernetes gateway on `http://127.0.0.1:8090`. The local `gateway:docker`, `gateway:podman`, and `gateway:vm` tasks detect the collector listener at startup and enable trace export, OCSF log export, and the agent trace relay only while it is reachable. Spans an agent exports from inside a sandbox arrive through the supervisor's relay with `openshell.telemetry.source=agent` and share `openshell.sandbox.id` with that sandbox's supervisor spans. The Skaffold profile for HA reverse-proxy development is available from `deploy/helm/openshell/`: ```bash # Two gateway replicas + external PostgreSQL Secret + Envoy Gateway + Gateway API route. KUBECONFIG=../../../kubeconfig skaffold run -p high-availability ``` The `high-availability` profile expects a Secret named `openshell-ha-pg` in the `openshell` namespace with a `uri` key. For local manual testing, either create your own PostgreSQL Secret or use the e2e PostgreSQL fixture manifest in `e2e/kubernetes/postgres-fixture.yaml`. For the `high-availability` profile, return to the repository root and apply the GatewayClass and BackendTrafficPolicy manifest after Skaffold has installed Envoy Gateway: ```bash KUBECONFIG=kubeconfig mise run helm:gateway:apply ``` The BackendTrafficPolicy disables Envoy request and stream-duration timeouts for OpenShell's `GRPCRoute`. Keep that policy in `deploy/kube/manifests/envoy-gateway-openshell.yaml`, not in the Helm chart; it is required for long-lived gRPC create/watch/exec/relay streams during gateway rollouts and scale events. ### TLS behaviour `ci/values-skaffold.yaml` sets `server.disableTls: true`, so Skaffold-based deploys run plaintext by default. Override `server.disableTls=false` to exercise TLS/mTLS. | Mode | `server.disableTls` | Gateway scheme | |------|---------------------|----------------| | Skaffold dev (default) | `true` | `http://` | | TLS enabled | `false` (or omitted) | `https://` | ### Connecting through the forwarding task Port `8080` is already bound by the k3d load balancer when Envoy Gateway is active, so the forwarding task uses local port `8090` for the gateway. In a second terminal, confirm that the gateway is registered and active: ```bash openshell gateway list ``` **Plaintext (default Skaffold deploy):** ```bash openshell sandbox list ``` **With mTLS enabled** — extract the client cert the PKI hook wrote to the cluster, then place it where the CLI expects it. Run once after each fresh install: ```bash mkdir -p ~/.config/openshell/gateways/openshell/mtls KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \ -o jsonpath='{.data.ca\.crt}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/ca.crt KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \ -o jsonpath='{.data.tls\.crt}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/tls.crt KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \ -o jsonpath='{.data.tls\.key}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/tls.key ``` The server cert SANs include `localhost` and `127.0.0.1`, so hostname verification passes over a port-forward without any extra flags: ```bash openshell sandbox list --gateway-endpoint https://localhost:8090 ``` --- ## Teardown ### Remove the Helm releases (keep cluster) ```bash mise run helm:skaffold:delete ``` ### Delete the cluster entirely ```bash mise run helm:k3s:delete ``` This removes the k3d cluster and all resources. Kubeconfig context is left behind
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub