Skip to content

Commit 5116cc2

Browse files
authored
feat(helm): add kubernetes local-dev environment (#1158)
1 parent e4b4e92 commit 5116cc2

26 files changed

Lines changed: 1454 additions & 51 deletions
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
name: helm-dev-environment
3+
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), 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.
4+
---
5+
6+
# Helm Dev Environment
7+
8+
Set up, run, and tear down the local Kubernetes development environment for OpenShell.
9+
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/`).
10+
11+
---
12+
13+
## Prerequisites
14+
15+
- Docker Desktop (macOS) or Docker Engine (Linux) running
16+
- `mise install` completed (provides `k3d`, `kubectl`, `skaffold`, `helm`)
17+
18+
---
19+
20+
## Startup
21+
22+
### 1. Create the cluster
23+
24+
```bash
25+
mise run helm:k3s:create
26+
```
27+
28+
Creates a k3d cluster and merges its kubeconfig into the worktree-local `kubeconfig` file.
29+
Also applies base manifests (`deploy/kube/manifests/agent-sandbox.yaml`). Traefik is
30+
disabled at cluster creation time.
31+
32+
**Multi-worktree support:** the cluster name is derived from the last component of the
33+
current git branch (e.g. branch `kube-support/local-dev/tmutch` → cluster
34+
`openshell-dev-tmutch`). Each worktree therefore gets its own isolated cluster and its
35+
own `kubeconfig` file. Override with `HELM_K3S_CLUSTER_NAME` to force a specific name
36+
or share one cluster across worktrees.
37+
38+
Port mappings created at cluster time (cannot be changed without recreating):
39+
40+
| Host port | Target | Used by |
41+
|-----------|--------|---------|
42+
| `8080` | Port `80` via k3d load balancer | Envoy Gateway LoadBalancer service (`values-gateway.yaml`) |
43+
44+
Override with env vars before running `helm:k3s:create`:
45+
- `HELM_K3S_LB_HOST_PORT` (default: `8080`)
46+
47+
### 2. Deploy OpenShell
48+
49+
**Iterative dev** (rebuilds on file changes, recommended during active development):
50+
```bash
51+
mise run helm:skaffold:dev
52+
```
53+
54+
**One-shot deploy** (build once and leave running):
55+
```bash
56+
mise run helm:skaffold:run
57+
```
58+
59+
Both commands build the `gateway` and `supervisor` images and deploy the OpenShell Helm
60+
chart. The `pkiInitJob` hook runs on first install to generate mTLS secrets. Envoy Gateway opt-in; see the Optional Add-ons section below.
61+
62+
The gateway Service uses ClusterIP. Access is via Envoy Gateway (port `8080`) or `kubectl port-forward`.
63+
64+
### TLS behaviour
65+
66+
`values-skaffold.yaml` sets `server.disableTls: true`, so Skaffold-based deploys run
67+
plaintext by default. To test with TLS enabled, comment out that line and redeploy.
68+
69+
| Mode | `server.disableTls` | Gateway scheme |
70+
|------|---------------------|----------------|
71+
| Skaffold dev (default) | `true` | `http://` |
72+
| TLS enabled | `false` (or omitted) | `https://` |
73+
74+
### Connecting via port-forward
75+
76+
Port `8080` is already bound by the k3d load balancer when Envoy Gateway is active, so
77+
the port-forward uses local port `8090` to avoid a collision:
78+
79+
```bash
80+
KUBECONFIG=kubeconfig kubectl port-forward -n openshell svc/openshell 8090:8080
81+
```
82+
83+
**Plaintext (default Skaffold deploy):**
84+
85+
```bash
86+
openshell sandbox list --gateway-endpoint http://localhost:8090
87+
```
88+
89+
**With mTLS enabled** — extract the client cert the PKI hook wrote to the cluster,
90+
then place it where the CLI expects it. Run once after each fresh install:
91+
92+
```bash
93+
mkdir -p ~/.config/openshell/gateways/openshell/mtls
94+
KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \
95+
-o jsonpath='{.data.ca\.crt}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/ca.crt
96+
KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \
97+
-o jsonpath='{.data.tls\.crt}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/tls.crt
98+
KUBECONFIG=kubeconfig kubectl get secret openshell-client-tls -n openshell \
99+
-o jsonpath='{.data.tls\.key}' | base64 -d > ~/.config/openshell/gateways/openshell/mtls/tls.key
100+
```
101+
102+
The server cert SANs include `localhost` and `127.0.0.1`, so hostname verification
103+
passes over a port-forward without any extra flags:
104+
105+
```bash
106+
openshell sandbox list --gateway-endpoint https://localhost:8090
107+
```
108+
109+
---
110+
111+
## Teardown
112+
113+
### Remove the Helm releases (keep cluster)
114+
115+
```bash
116+
mise run helm:skaffold:delete
117+
```
118+
119+
### Delete the cluster entirely
120+
121+
```bash
122+
mise run helm:k3s:delete
123+
```
124+
125+
This removes the k3d cluster and all resources. Kubeconfig context is left behind
126+
but will point to a deleted cluster — safe to ignore or clean up manually.
127+
128+
---
129+
130+
## Optional Add-ons
131+
132+
Each add-on requires uncommenting the corresponding `valuesFiles` entry in
133+
`deploy/helm/openshell/skaffold.yaml` before running `helm:skaffold:dev` or `helm:skaffold:run`.
134+
135+
### Envoy Gateway (Gateway API / GRPCRoute)
136+
137+
Envoy Gateway is already installed by Skaffold (the `envoy-gateway` Helm release in
138+
`skaffold.yaml`). To activate routing:
139+
140+
1. Uncomment `#- values-gateway.yaml` in `skaffold.yaml`
141+
2. Redeploy: `mise run helm:skaffold:run`
142+
3. Apply the GatewayClass: `mise run helm:gateway:apply`
143+
4. Access: `http://127.0.0.1:8080`
144+
145+
`values-gateway.yaml` creates a `Gateway` (listener on port 80, class `eg`) and a
146+
`GRPCRoute` in the `openshell` namespace. Envoy Gateway provisions a LoadBalancer
147+
service for the proxy; klipper-lb binds it to hostPort 80, reachable via the
148+
`8080:80` load balancer port mapping.
149+
150+
### Keycloak OIDC
151+
152+
One-time setup — only needed once per cluster lifetime:
153+
154+
```bash
155+
mise run keycloak:k8s:setup
156+
```
157+
158+
This deploys Keycloak (`quay.io/keycloak/keycloak:24.0`) into the `keycloak` namespace,
159+
imports the openshell realm from `scripts/keycloak-realm.json`, and prints a port-forward
160+
command for acquiring tokens from the CLI.
161+
162+
Then activate OIDC in the OpenShell Helm chart:
163+
1. Uncomment `#- values-keycloak.yaml` in `skaffold.yaml`
164+
2. Redeploy: `mise run helm:skaffold:run`
165+
166+
To remove Keycloak:
167+
```bash
168+
mise run keycloak:k8s:teardown
169+
```
170+
171+
---
172+
173+
## Cluster Lifecycle (suspend/resume)
174+
175+
Stop the cluster without losing state (faster than delete/recreate):
176+
```bash
177+
mise run helm:k3s:stop
178+
mise run helm:k3s:start
179+
```
180+
181+
Check cluster status:
182+
```bash
183+
mise run helm:k3s:status
184+
```
185+
186+
---
187+
188+
## Key Files
189+
190+
| Path | Purpose |
191+
|------|---------|
192+
| `deploy/helm/openshell/skaffold.yaml` | Skaffold config — images, Helm releases, values overlays |
193+
| `deploy/helm/openshell/values.yaml` | Default Helm values |
194+
| `deploy/helm/openshell/values-skaffold.yaml` | Dev overrides (image pull policy, local image names) |
195+
| `deploy/helm/openshell/values-cert-manager.yaml` | cert-manager TLS overlay (opt-in; disables pkiInitJob) |
196+
| `deploy/helm/openshell/values-gateway.yaml` | Envoy Gateway GRPCRoute + Gateway overlay |
197+
| `deploy/helm/openshell/values-keycloak.yaml` | Keycloak OIDC overlay |
198+
| `deploy/kube/manifests/envoy-gateway-openshell.yaml` | GatewayClass for Envoy Gateway (`mise run helm:gateway:apply`) |
199+
| `tasks/scripts/helm-k3s-local.sh` | k3d cluster create/delete/start/stop/status |
200+
| `tasks/scripts/keycloak-k8s-setup.sh` | Keycloak deploy + realm import |

‎deploy/docker/Dockerfile.images‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#
1414
# Rust binaries are built natively before the image build and staged at:
1515
# deploy/docker/.build/prebuilt-binaries/<arch>/openshell-{gateway,sandbox}
16+
#
17+
# For local dev (Skaffold), pass --build-arg BUILD_FROM_SOURCE=1 to compile
18+
# binaries inside Docker instead. BuildKit only executes the selected binary
19+
# staging stage, so missing prebuilt files do not cause a build failure.
1620

1721
# Pin by tag AND manifest-list digest to prevent silent upstream republishes
1822
# from breaking the build. Update both when bumping k3s versions.
@@ -22,22 +26,67 @@ ARG K3S_DIGEST=sha256:4607083d3cac07e1ccde7317297271d13ed5f60f35a78f33fcef84858a
2226
ARG K9S_VERSION=v0.50.18
2327
ARG HELM_VERSION=v3.17.3
2428
ARG NVIDIA_CONTAINER_TOOLKIT_VERSION=1.18.2-1
29+
# Controls binary source: 0 = prebuilt (release), 1 = compile in Docker (local dev).
30+
# Must be declared here (global scope) so it can be used in FROM instructions below.
31+
ARG BUILD_FROM_SOURCE=0
32+
33+
# ---------------------------------------------------------------------------
34+
# Optional in-Docker Rust build (BUILD_FROM_SOURCE=1, local dev only)
35+
# ---------------------------------------------------------------------------
36+
FROM rust:1.95.0-slim-bookworm AS rust-builder
37+
38+
RUN apt-get update && apt-get install -y --no-install-recommends \
39+
build-essential \
40+
cmake \
41+
pkg-config \
42+
libssl-dev \
43+
ca-certificates \
44+
&& rm -rf /var/lib/apt/lists/*
45+
46+
WORKDIR /build
47+
48+
COPY Cargo.toml Cargo.lock ./
49+
COPY crates/ crates/
50+
COPY proto/ proto/
51+
52+
RUN --mount=type=cache,target=/usr/local/cargo/registry \
53+
--mount=type=cache,target=/build/target \
54+
cargo build --release \
55+
--features "openshell-core/dev-settings" \
56+
--bin openshell-gateway \
57+
--bin openshell-sandbox \
58+
&& mkdir -p /build/out \
59+
&& install -m 0755 target/release/openshell-gateway /build/out/openshell-gateway \
60+
&& install -m 0755 target/release/openshell-sandbox /build/out/openshell-sandbox
2561

2662
# ---------------------------------------------------------------------------
2763
# Per-arch binary stages
2864
# ---------------------------------------------------------------------------
29-
FROM scratch AS gateway-binary
65+
66+
# Prebuilt path (release default, BUILD_FROM_SOURCE=0)
67+
FROM scratch AS gateway-binary-0
3068
ARG TARGETARCH
3169
# --chmod=755 preserves the executable bit through actions/upload-artifact +
3270
# download-artifact, which strip exec perms during the roundtrip.
3371
COPY --chmod=755 deploy/docker/.build/prebuilt-binaries/${TARGETARCH}/openshell-gateway /build/out/openshell-gateway
3472

35-
FROM scratch AS supervisor-binary
73+
# Source-built path (local dev, BUILD_FROM_SOURCE=1)
74+
FROM rust-builder AS gateway-binary-1
75+
76+
FROM gateway-binary-${BUILD_FROM_SOURCE} AS gateway-binary
77+
78+
# Prebuilt path (release default, BUILD_FROM_SOURCE=0)
79+
FROM scratch AS supervisor-binary-0
3680
ARG TARGETARCH
3781
# --chmod=755 preserves the executable bit through actions/upload-artifact +
3882
# download-artifact, which strip exec perms during the roundtrip.
3983
COPY --chmod=755 deploy/docker/.build/prebuilt-binaries/${TARGETARCH}/openshell-sandbox /build/out/openshell-sandbox
4084

85+
# Source-built path (local dev, BUILD_FROM_SOURCE=1)
86+
FROM rust-builder AS supervisor-binary-1
87+
88+
FROM supervisor-binary-${BUILD_FROM_SOURCE} AS supervisor-binary
89+
4190
# Minimal extraction stage for fast-deploy: exports only the supervisor
4291
# binary (~20-40 MB) instead of the entire build environment (~968 MB).
4392
FROM scratch AS supervisor-output

‎deploy/helm/openshell/.helmignore‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,11 @@
1616
.idea/
1717
*.tmproj
1818
.vscode/
19+
20+
# Ignore development files
21+
skaffold.yaml
22+
values-keycloak.yaml
23+
values-ingress.yaml
24+
values-gateway.yaml
25+
values-cert-manager.yaml
26+
values-skaffold.yaml

‎deploy/helm/openshell/Chart.yaml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@ apiVersion: v2
55
name: openshell
66
description: runtime environment for autonomous agents
77
type: application
8-
version: 0.1.0
9-
appVersion: "0.1.0"
8+
# Updated to the release version by CI. The appVersion doubles as the default
9+
# image tag (image.tag defaults to appVersion when empty), so a released chart
10+
# automatically pulls the matching gateway and supervisor images.
11+
version: 0.0.0
12+
appVersion: "0.0.0"

0 commit comments

Comments
 (0)