Skip to content

Commit 44c07da

Browse files
authored
feat(helm): make gateway PVC size and StorageClass configurable (#3854)
Signed-off-by: Yuedong Wu <dwcn22@outlook.com>
1 parent 10241ab commit 44c07da

8 files changed

Lines changed: 133 additions & 2 deletions

File tree

‎deploy/helm/openshell/README.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,36 @@ server:
203203
dbUrl: "sqlite:/var/openshell/openshell.db"
204204
```
205205
206+
#### Data PVC storage
207+
208+
Size the gateway data claim, `openshell-data`, and select its StorageClass with
209+
`server.dataStorageSize` and `server.dataStorageClass`. Omitting both
210+
requests `1Gi` from the cluster's default StorageClass. Set
211+
`server.dataStorageClass` on clusters with no default StorageClass,
212+
otherwise the gateway PVC stays Pending. Both values are ignored under
213+
`workload.kind=deployment`, which mounts no PVC.
214+
215+
```yaml
216+
server:
217+
dataStorageSize: 20Gi
218+
dataStorageClass: fast-ssd
219+
```
220+
221+
`volumeClaimTemplates` is immutable, so set both at install time. To grow an
222+
existing claim, expand it in place when its StorageClass allows expansion, then
223+
recreate the StatefulSet so its template matches:
224+
225+
```shell
226+
kubectl -n openshell patch pvc openshell-data-openshell-0 \
227+
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
228+
kubectl -n openshell delete statefulset openshell --cascade=orphan
229+
helm upgrade openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <installed-version> \
230+
--reuse-values -n openshell --set server.dataStorageSize=20Gi
231+
```
232+
233+
A recreated StatefulSet re-adopts existing claims unchanged, so changing
234+
StorageClass means copying the data to a new claim.
235+
206236
#### External PostgreSQL
207237

208238
Use external PostgreSQL when the gateway should connect to a database managed
@@ -482,6 +512,8 @@ discovery endpoint or its TLS CA.
482512
| server.credentialDrivers.vault.timeoutSecs | string | `""` | HTTP request timeout in seconds. Empty = driver default. |
483513
| server.credentialDrivers.vault.tokenPath | string | `""` | Mounted token file path when authMethod is token_file. |
484514
| server.credentialStorage.existingSecret | string | `""` | Name of a pre-existing Secret containing the key-encryption key. When set, the chart does NOT generate a new Secret; it references this one instead. The Secret must contain a key named "key-encryption-key" with a base64-encoded 32-byte value. Required for GitOps workflows that render manifests with `helm template` (where `lookup` is unavailable). |
515+
| server.dataStorageClass | string | `""` | Kubernetes StorageClass for the gateway data PVC (StatefulSet workload only). Empty (default) = omit storageClassName, using the cluster's default StorageClass. Set this on clusters with no default StorageClass, otherwise the gateway PVC stays Pending. Use a block-backed class, not NFS or other network filesystems. Immutable after install. |
516+
| server.dataStorageSize | string | `""` | Storage request for the gateway data PVC, `openshell-data` (StatefulSet workload only). Uses Kubernetes quantity syntax and requires an explicit unit ("20Gi", not "20"). Empty = built-in default (1Gi). Growing an existing release's claim is PVC expansion, not a values change. |
485517
| server.dbUrl | string | `"sqlite:/var/openshell/openshell.db"` | Gateway database URL (used for the default SQLite backend). SQLite runs in WAL mode and needs a local block-backed volume, not NFS or other network filesystems. |
486518
| server.defaultRuntimeClassName | string | `""` | Default Kubernetes runtimeClassName for sandbox pods. Applied when a CreateSandbox request does not specify one. Empty (default) = omit the field, using the cluster's default RuntimeClass. Set to a RuntimeClass name (e.g. "kata-containers", "nvidia") to apply it to all sandboxes that don't explicitly override it. |
487519
| server.disableTls | bool | `false` | Disable TLS entirely - the server listens on plaintext HTTP. Set to true when a reverse proxy / tunnel terminates TLS at the edge. |

‎deploy/helm/openshell/README.md.gotmpl‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,36 @@ server:
204204
dbUrl: "sqlite:/var/openshell/openshell.db"
205205
```
206206

207+
#### Data PVC storage
208+
209+
Size the gateway data claim, `openshell-data`, and select its StorageClass with
210+
`server.dataStorageSize` and `server.dataStorageClass`. Omitting both
211+
requests `1Gi` from the cluster's default StorageClass. Set
212+
`server.dataStorageClass` on clusters with no default StorageClass,
213+
otherwise the gateway PVC stays Pending. Both values are ignored under
214+
`workload.kind=deployment`, which mounts no PVC.
215+
216+
```yaml
217+
server:
218+
dataStorageSize: 20Gi
219+
dataStorageClass: fast-ssd
220+
```
221+
222+
`volumeClaimTemplates` is immutable, so set both at install time. To grow an
223+
existing claim, expand it in place when its StorageClass allows expansion, then
224+
recreate the StatefulSet so its template matches:
225+
226+
```shell
227+
kubectl -n openshell patch pvc openshell-data-openshell-0 \
228+
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
229+
kubectl -n openshell delete statefulset openshell --cascade=orphan
230+
helm upgrade openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <installed-version> \
231+
--reuse-values -n openshell --set server.dataStorageSize=20Gi
232+
```
233+
234+
A recreated StatefulSet re-adopts existing claims unchanged, so changing
235+
StorageClass means copying the data to a new claim.
236+
207237
#### External PostgreSQL
208238

209239
Use external PostgreSQL when the gateway should connect to a database managed

‎deploy/helm/openshell/templates/_helpers.tpl‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,4 +624,8 @@ Validate chart values that Helm would otherwise accept silently.
624624
{{- if kindIs "invalid" .Values.server.tls.clientCaSecretName -}}
625625
{{- fail "server.tls.clientCaSecretName cannot be null; omit the key to use the chart default (openshell-server-client-ca), or set to \"\" to disable client certificate verification for HTTPS-only mode" -}}
626626
{{- end -}}
627+
{{- $dataStorageSize := .Values.server.dataStorageSize -}}
628+
{{- if and (eq $workloadKind "statefulset") $dataStorageSize (not (regexMatch "^[0-9]+(\\.[0-9]+)?([KMGTPE]i|[kMGTPE])$" (toString $dataStorageSize))) -}}
629+
{{- fail "server.dataStorageSize must be a Kubernetes quantity with a unit, such as 20Gi" -}}
630+
{{- end -}}
627631
{{- end }}

‎deploy/helm/openshell/templates/statefulset.yaml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ spec:
2323
name: openshell-data
2424
spec:
2525
accessModes: ["ReadWriteOnce"]
26+
{{- with .Values.server.dataStorageClass }}
27+
storageClassName: {{ . | quote }}
28+
{{- end }}
2629
resources:
2730
requests:
28-
storage: 1Gi
31+
storage: {{ .Values.server.dataStorageSize | default "1Gi" | quote }}
2932
{{- end }}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
suite: gateway data persistence
5+
templates:
6+
- templates/gateway-config.yaml
7+
- templates/statefulset.yaml
8+
release:
9+
name: openshell
10+
namespace: my-namespace
11+
12+
tests:
13+
- it: requests 1Gi from the cluster default StorageClass by default
14+
template: templates/statefulset.yaml
15+
asserts:
16+
- equal:
17+
path: spec.volumeClaimTemplates[0].metadata.name
18+
value: openshell-data
19+
- equal:
20+
path: spec.volumeClaimTemplates[0].spec.resources.requests.storage
21+
value: 1Gi
22+
- notExists:
23+
path: spec.volumeClaimTemplates[0].spec.storageClassName
24+
25+
- it: renders an explicitly set data storage size and StorageClass
26+
template: templates/statefulset.yaml
27+
set:
28+
server.dataStorageSize: 20Gi
29+
server.dataStorageClass: fast-ssd
30+
asserts:
31+
- equal:
32+
path: spec.volumeClaimTemplates[0].spec.resources.requests.storage
33+
value: 20Gi
34+
- equal:
35+
path: spec.volumeClaimTemplates[0].spec.storageClassName
36+
value: fast-ssd
37+
38+
- it: rejects a data storage size without a unit
39+
template: templates/statefulset.yaml
40+
set:
41+
server.dataStorageSize: 20
42+
asserts:
43+
- failedTemplate:
44+
errorMessage: "server.dataStorageSize must be a Kubernetes quantity with a unit, such as 20Gi"

‎deploy/helm/openshell/values.yaml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,17 @@ server:
363363
# in WAL mode and needs a local block-backed volume, not NFS or other network
364364
# filesystems.
365365
dbUrl: "sqlite:/var/openshell/openshell.db"
366+
# -- Storage request for the gateway data PVC, `openshell-data` (StatefulSet
367+
# workload only). Uses Kubernetes quantity syntax and requires an explicit unit
368+
# ("20Gi", not "20"). Empty = built-in default (1Gi). Growing an existing
369+
# release's claim is PVC expansion, not a values change.
370+
dataStorageSize: ""
371+
# -- Kubernetes StorageClass for the gateway data PVC (StatefulSet workload
372+
# only). Empty (default) = omit storageClassName, using the cluster's default
373+
# StorageClass. Set this on clusters with no default StorageClass, otherwise
374+
# the gateway PVC stays Pending. Use a block-backed class, not NFS or other
375+
# network filesystems. Immutable after install.
376+
dataStorageClass: ""
366377
# -- Name of a pre-existing Opaque Secret containing a PostgreSQL
367378
# connection URI (key: uri). When set, the gateway reads OPENSHELL_DB_URL
368379
# from this Secret instead of using dbUrl. The Secret must contain a

‎docs/kubernetes/setup.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ The most commonly changed values are:
294294
| `server.sandboxNamespace` | Namespace where sandbox pods are created. Defaults to the Helm release namespace when left empty. |
295295
| `workspaceResources.enabled` | Create namespace-scoped sandbox prerequisites from the gateway chart. Disable when installing the workspace chart separately. |
296296
| `server.externalDbSecret` | Secret containing a PostgreSQL connection URI in the `uri` key. Use when the database is managed outside the chart. |
297+
| `server.dataStorageSize` | Storage request for the gateway data PVC under `workload.kind=statefulset`. Defaults to `1Gi`. Requires an explicit unit, such as `20Gi`. |
298+
| `server.dataStorageClass` | `StorageClass` for the gateway data PVC under `workload.kind=statefulset`. Empty (default) omits `storageClassName` and uses the cluster's default `StorageClass`. Use a block-backed class, not NFS. |
297299
| `server.telemetryEnabled` | Enable anonymous OpenShell telemetry from the gateway and its sandbox supervisors. Set to `false` to opt out. |
298300
| `sandbox.image.repository` / `sandbox.image.tag` / `sandbox.image.digest` | Default sandbox image used when a sandbox does not specify one. |
299301
| `sandboxRuntime.image.registry` / `sandboxRuntime.image.repository` / `sandboxRuntime.image.tag` / `sandboxRuntime.image.digest` | Trusted workload-side image that provides the `openshell-sandbox` binary. A digest takes precedence over the tag. |
@@ -317,6 +319,10 @@ helm upgrade --install openshell \
317319
--values my-values.yaml
318320
```
319321

322+
<Warning>
323+
`server.dataStorageSize` and `server.dataStorageClass` feed the gateway StatefulSet's immutable `volumeClaimTemplates`. Set them at install time. Changing them later fails the upgrade and does not move existing data; refer to the [Helm chart README](https://github.com/NVIDIA/OpenShell/blob/main/deploy/helm/openshell/README.md#data-pvc-storage) for migration steps.
324+
</Warning>
325+
320326
To use private sandbox images, create a `kubernetes.io/dockerconfigjson` Secret
321327
in the sandbox namespace and reference its name:
322328

‎skills/debug-openshell-cluster/SKILL.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,8 @@ credential failures.
11761176
| Docker or Podman sandbox never registers | Wrong gateway endpoint, unavailable host networking, or supervisor startup failure | Gateway logs and supervisor container logs |
11771177
| CPU-only VM startup reports that read-only `/proc` cannot be removed | Older supervisors can infer GPU requirements from host devices | Check gateway, VM driver, and bundled supervisor versions; use matching updated artifacts. CPU-only VMs do not require read-write `/proc`. |
11781178
| Docker GPU sandbox fails before startup | NVIDIA CDI specs are missing or Docker has not discovered them | `docker info --format '{{json .DiscoveredDevices}}'`, `/etc/cdi`, `/var/run/cdi`, `nvidia-cdi-refresh.service` |
1179-
| Kubernetes gateway pod pending | PVC unbound, taint, selector, or insufficient resources | `kubectl -n openshell describe pod <pod>` |
1179+
| Kubernetes gateway pod pending | PVC unbound, taint, selector, or insufficient resources | `kubectl -n openshell describe pod <pod>`; if the gateway PVC is unbound because the cluster has no default `StorageClass`, set `server.dataStorageClass` to a valid one, and size the claim with `server.dataStorageSize` |
1180+
| `helm upgrade` fails on a forbidden update to the gateway StatefulSet after changing `server.dataStorageSize` or `server.dataStorageClass` | `volumeClaimTemplates` is immutable on an existing StatefulSet | `kubectl -n openshell delete statefulset openshell --cascade=orphan`, then re-upgrade; the orphaned PVC keeps its original size and class, so grow it with in-place PVC expansion or copy the data to a new claim |
11801181
| Kubernetes sandbox pod stuck pending, workspace PVC unbound | Cluster has no default `StorageClass` and OpenShell does not set `storageClassName` on the workspace PVC (clusters with a default `StorageClass` bind fine without it) | `kubectl -n openshell describe pvc`; set `server.workspaceStorageClass` (gateway config `workspace_storage_class`) to a valid `StorageClass` |
11811182
| Kubernetes gateway pod crash loops | Missing secret, bad DB URL, bad TLS config | `kubectl -n openshell logs deployment/openshell -c openshell-gateway` or `kubectl -n openshell logs statefulset/openshell -c openshell-gateway` |
11821183
| Mutating RPCs return `UNAVAILABLE` with "timed out waiting for a concurrent mutation" | Advisory-lock contention, a full lock pool on one replica (4 connections unless `database_lock_max_connections` is set), a slow PostgreSQL, or older gateways still running during an upgrade | `openshell_server_mutation_lock_timeouts_total`, `openshell_server_mutation_lock_connections_in_use` against its capacity, the `detail` field of `mutation lock acquisition timed out` in gateway logs, `mutation lock held longer than the lock wait timeout` warnings and `openshell_server_mutation_lock_hold_seconds`, the `pg_locks` query in Step 6 |

0 commit comments

Comments
 (0)