feat(k8s): support ephemeral emptyDir-backed sandbox workspaces as an alternative to the default PVC
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 8.7k
- Forks
- 1.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 253
Description
User Story
As a platform operator running OpenShell on a managed Kubernetes cluster (ROSA, EKS, AKS, GKE, Gardener) or on a short-lived CI cluster, I want to opt sandboxes into an ephemeral, emptyDir-backed workspace instead of the per-sandbox PVC, so that I can run OpenShell in environments where PVC provisioning is unavailable, unreliable, expensive, or disallowed by cluster policy — without giving up sandbox lifecycle correctness.
Problem Statement
The Kubernetes compute driver unconditionally injects a workspace PVC into every sandbox pod via volumeClaimTemplates on the agents.x-k8s.io/Sandbox CR (crates/openshell-driver-kubernetes/src/driver.rs:2041-2176, constants at :413-431). After PR #2088 removed SandboxTemplate.volume_claim_templates from the public API, there is no supported way to run a sandbox pod without a PVC. The only knobs available today are workspace_default_storage_size (#1436) and workspace_storage_class (#2442, PR #2463) — both change which PVC gets created, not whether one is created.
An emergent workaround exists (has_explicit_sandbox_data_mount disables default PVC injection when the operator supplies any driver_config subPath mount under /sandbox), but it is undocumented, requires supplying an unrelated PVC just to trigger the branch, and does not deliver emptyDir semantics. It is an implementation side-effect, not a contract.
The agents.x-k8s.io/Sandbox CRD does not require the workspace to be a PVC — volumeClaimTemplates is optional in api/v1beta1. This is entirely an OpenShell-side rendering assumption, so the change is scoped to this repo.
Impact / Why This Matters
Without an ephemeral option, users of the Kubernetes driver today must:
- Provide a StorageClass in every target cluster, even when the sandbox does not need pod-reschedule survival. On clusters without a default StorageClass and without
workspace_storage_classset, the workspace PVC staysPendingand the sandbox never starts. This blocks OpenShell adoption on short-lived CI clusters, edge clusters, restricted managed clusters, and Gardener-style shoots where dynamic provisioning is not always installed. - Pay the provisioning cost and lifecycle risk of a PVC per sandbox, even for short-lived agent tasks that never restart. The PVC + init-container path adds first-start latency (called out as a stopgap in the code comment at
driver.rs:399-411) and, as documented in #1879, leaves orphaned PVCs holding written user data on teardown paths that skip the finalizer (default agent-sandboxshutdownPolicy: Retain). - Give up the fail-safe property
emptyDirprovides — kubelet-guaranteed reclaim, no external object to orphan, no dependence on cleanup being correct. #1879 already identifies this as a valid workspace model, but scopes it to warm-pooled sandboxes only, leaving cold-path sandboxes without the option. - Rely on an undocumented driver-config side-effect (
has_explicit_sandbox_data_mount) to skip the PVC — not a stable contract and requires supplying a foreign PVC just to disable default injection.
The maintainer-authored design for the current PVC path (#743) explicitly anticipated this: "Opt-out mechanism: some users may want ephemeral sandboxes. Consider --no-persist flag or server env var. Can be a follow-up." No follow-up has been filed.
Proposed Design
Add a workspace backend selector to the Kubernetes driver configuration, mirroring the shape of workspace_storage_class (#2442) exactly.
Gateway-wide default (crates/openshell-driver-kubernetes/src/config.rs):
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WorkspaceBackend {
/// Per-sandbox PVC via `volumeClaimTemplates` on the Sandbox CR.
/// Survives pod rescheduling. Requires a StorageClass.
#[default]
Pvc,
/// `emptyDir` volume mounted at `/sandbox`. No PVC created.
/// Ephemeral — deleted with the pod.
Ephemeral,
}
pub struct KubernetesComputeConfig {
// ...existing fields...
pub workspace_backend: WorkspaceBackend, // new
pub workspace_default_storage_size: String, // ignored when Ephemeral
pub workspace_storage_class: String, // ignored when Ephemeral
// ...
}
- Same driver-config → gateway TOML → Helm value fan-out that the existing storage-class field uses (
server.workspaceBackend). - Default is
Pvc, preserving current behavior byte-for-byte.
Per-sandbox override — no new proto or CLI surface required. The existing SandboxTemplate.driver_config envelope (proto/openshell.proto:862) is already the driver-keyed opaque config the gateway forwards to the compute driver, and the CLI already exposes it as --driver-config-json. The only change is: the Kubernetes driver reads workspace_backend from the per-request struct as it already does for other overridable fields.
# Ephemeral sandbox on a gateway defaulted to pvc:
openshell sandbox create --name ephemeral-task \
--driver-config-json '{"kubernetes":{"workspace_backend":"ephemeral"}}' \
-- claude
# Persistent sandbox on a gateway defaulted to ephemeral:
openshell sandbox create --name durable-task \
--driver-config-json '{"kubernetes":{"workspace_backend":"pvc","workspace_default_storage_size":"10Gi"}}' \
-- claude
Rendering (crates/openshell-driver-kubernetes/src/driver.rs, apply_workspace_persistence):
Pvc→ existing path unchanged.Ephemeral→ skipvolumeClaimTemplatesentirely; add a pod-specemptyDirvolume namedworkspaceat/sandbox(WORKSPACE_MOUNT_PATH); theworkspace-initinit container still seeds/sandboxfrom the image on every pod start (sentinel unnecessary — volume is always empty at pod start).
Observable behavior
sandbox createinEphemeralmode succeeds on a cluster with no StorageClass installed.sandbox stop+sandbox starton an ephemeral sandbox starts with a fresh/sandbox— documented as the ephemeral contract, complementary to the PVC-backed lifecycle from #2652.- Pod eviction / rescheduling on an ephemeral sandbox loses
/sandboxstate (documented). PVC-backed sandboxes retain today's behavior unchanged. kubectl get pvc -n <ns>shows zero workspace PVCs for ephemeral sandboxes.
Acceptance Criteria
-
driver_config.kubernetes.workspace_backendaccepts"pvc"(default) and"ephemeral"; unknown values rejected at config parse. - With
workspace_backend = "ephemeral", the rendered pod spec contains anemptyDirvolume at/sandboxand theSandboxCR contains novolumeClaimTemplatesentry. - With
workspace_backend = "ephemeral", sandbox creation succeeds on a cluster that has no default StorageClass and no explicitworkspace_storage_class. - The
workspace-initinit container seeds/sandboxfrom the image on every pod start in ephemeral mode. - Default (unset) behavior is byte-for-byte identical to today's PVC path — asserted by a render-diff test.
- Per-sandbox override via
--driver-config-json '{"kubernetes":{"workspace_backend":"..."}}'works and takes precedence over the gateway default. - Combining
workspace_backend = "ephemeral"withworkspace_default_storage_sizeorworkspace_storage_classfails validation with a clear error. - Helm value
server.workspaceBackendand the gateway TOML field render correctly through the deployment path and are documented indocs/reference/gateway-config.mdxanddocs/reference/sandbox-compute-drivers.mdx. - Kubernetes e2e: create an ephemeral sandbox, write a marker to
/sandbox, delete the pod, verify (a) no PVC is left behind and (b) a re-created sandbox with the same identity starts with a fresh/sandbox.
Alternatives Considered
- Do nothing; document the
has_explicit_sandbox_data_mountside-effect. Keeps the emergent workaround as the answer. Requires operators to supply an unrelated PVC just to disable default injection, does not deliveremptyDirsemantics, and remains contract-less. - Generic Ephemeral Volumes (
ephemeral.volumeClaimTemplateon the pod spec). Still requires a StorageClass; does not solve the "cluster has no provisioner" case. Complementary; could be added later as a thirdworkspace_backendvariant. tmpfsfor/sandbox. Bounded by pod memory limits and interacts poorly with the image-seed path (image content can be arbitrarily large). Not a fit for the default case.- Wait for the container-snapshotting replacement referenced by
driver.rs:399-411. There is no tracking issue for that work; the ephemeral option is complementary and useful regardless of when snapshotting lands. - Reintroduce
SandboxTemplate.volume_claim_templates. Explicitly reverted by PR #2088. This proposal instead follows the current direction (customize throughdriver_config, not by opting the whole PVC surface out via raw CRs).
Agent Investigation
- Kubernetes driver injects the workspace PVC unconditionally:
crates/openshell-driver-kubernetes/src/driver.rs:413-431(constants),:2041-2139(apply_workspace_persistence),:2150-2176(default_workspace_volume_claim_templates). - Emergent bypass via subPath mounts:
has_explicit_sandbox_data_mountdisables default injection for any driver-config mount at-or-under/sandbox(noted by @elezar in PR #2034 review). - PR #2088 removed
SandboxTemplate.volume_claim_templatesfrom the public API, closing the previous raw-CR escape hatch and consolidating storage configuration behinddriver_config.kubernetes. - Existing driver-keyed override pipe:
SandboxTemplate.driver_config(proto/openshell.proto:862) is already the opaque per-request envelope the gateway forwards to the compute driver — no proto change required for the per-sandbox override. - CLI already exposes the override generically:
--driver-config-jsononsandbox create(crates/openshell-cli/src/main.rs:1386-1392, parser incrates/openshell-cli/src/run.rs:262) — no CLI change required. - #743 (design spike for the current PVC path) explicitly deferred the ephemeral opt-out as a follow-up that never materialized.
- #1879 (warm-pooled sandboxes) proposes
emptyDiras one of two workspace models — but only on the warm path, leaving cold-path sandboxes without the option. - Precedent for the config-knob shape and rollout: #2442 → PR #2463 (
workspace_storage_class) — identical driver/TOML/Helm/docs fan-out. - Managed-cluster context: #899 (restricted SCC on OpenShift) confirms the managed-Kubernetes audience is present upstream; discussion #2626 (edge devices) is an adjacent audience for the same "PVC undesirable" case.
- The
agents.x-k8s.io/SandboxCRD (kubernetes-sigs/agent-sandbox,api/v1beta1) does not requirevolumeClaimTemplates— the field is optional, so this proposal needs no operator or CRD change.
Checklist
- I've reviewed existing issues and the architecture docs
- This is a design proposal, not a "please build this" request
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with crates/openshell-driver-kubernetes/src/config.rs and driver.rs, especially apply_workspace_persistence and the workspace constants, then trace the existing workspace_storage_class configuration through the gateway TOML and Helm deployment. Review the CLI driver-config entry points and the two documentation files named in the acceptance criteria. Done means the PVC default remains unchanged, ephemeral rendering and overrides are validated by render tests, and the Kubernetes e2e verifies fresh workspace state without a PVC.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes, rust
- Domain
- devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100