Skip to content

Commit d713ab9

Browse files
committed
feat(k8s): make default workspace PVC storage size configurable
The Kubernetes driver hardcoded the workspace PVC size to 2Gi. Add a workspace_default_storage_size field to KubernetesComputeConfig so operators can tune it via TOML config, Helm values, or the OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE environment variable.
1 parent 65a3a7c commit d713ab9

7 files changed

Lines changed: 75 additions & 9 deletions

File tree

crates/openshell-driver-kubernetes/src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use serde::{Deserialize, Serialize};
77
/// Default Kubernetes namespace for sandbox resources.
88
pub const DEFAULT_K8S_NAMESPACE: &str = "openshell";
99

10+
/// Default storage size for the workspace PVC.
11+
pub const DEFAULT_WORKSPACE_STORAGE_SIZE: &str = "2Gi";
12+
1013
/// How the supervisor binary is delivered into sandbox pods.
1114
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1215
#[serde(rename_all = "kebab-case")]
@@ -64,6 +67,7 @@ pub struct KubernetesComputeConfig {
6467
pub client_tls_secret_name: String,
6568
pub host_gateway_ip: String,
6669
pub enable_user_namespaces: bool,
70+
pub workspace_default_storage_size: String,
6771
}
6872

6973
impl Default for KubernetesComputeConfig {
@@ -84,6 +88,7 @@ impl Default for KubernetesComputeConfig {
8488
client_tls_secret_name: String::new(),
8589
host_gateway_ip: String::new(),
8690
enable_user_namespaces: false,
91+
workspace_default_storage_size: DEFAULT_WORKSPACE_STORAGE_SIZE.to_string(),
8792
}
8893
}
8994
}
@@ -94,3 +99,26 @@ fn default_sandbox_image() -> String {
9499
openshell_core::image::DEFAULT_COMMUNITY_REGISTRY
95100
)
96101
}
102+
103+
#[cfg(test)]
104+
mod tests {
105+
use super::*;
106+
107+
#[test]
108+
fn default_workspace_storage_size_is_2gi() {
109+
let cfg = KubernetesComputeConfig::default();
110+
assert_eq!(
111+
cfg.workspace_default_storage_size,
112+
DEFAULT_WORKSPACE_STORAGE_SIZE
113+
);
114+
}
115+
116+
#[test]
117+
fn serde_override_workspace_storage_size() {
118+
let json = serde_json::json!({
119+
"workspace_default_storage_size": "10Gi"
120+
});
121+
let cfg: KubernetesComputeConfig = serde_json::from_value(json).unwrap();
122+
assert_eq!(cfg.workspace_default_storage_size, "10Gi");
123+
}
124+
}

crates/openshell-driver-kubernetes/src/driver.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
//! Kubernetes compute driver.
55
6-
use crate::config::{KubernetesComputeConfig, SupervisorSideloadMethod};
6+
use crate::config::{
7+
DEFAULT_WORKSPACE_STORAGE_SIZE, KubernetesComputeConfig, SupervisorSideloadMethod,
8+
};
79
use futures::{Stream, StreamExt, TryStreamExt};
810
use k8s_openapi::api::core::v1::{Event as KubeEventObj, Node};
911
use kube::api::{Api, ApiResource, DeleteParams, ListParams, PostParams};
@@ -106,9 +108,6 @@ const WORKSPACE_INIT_MOUNT_PATH: &str = "/workspace-pvc";
106108
/// Name of the init container that seeds the workspace PVC.
107109
const WORKSPACE_INIT_CONTAINER_NAME: &str = "workspace-init";
108110

109-
/// Default storage request for the workspace PVC.
110-
const WORKSPACE_DEFAULT_STORAGE: &str = "2Gi";
111-
112111
/// Sentinel file written by the init container after copying the image's
113112
/// `/sandbox` contents. Subsequent pod starts skip the copy.
114113
const WORKSPACE_SENTINEL: &str = ".workspace-initialized";
@@ -327,6 +326,7 @@ impl KubernetesComputeDriver {
327326
client_tls_secret_name: &self.config.client_tls_secret_name,
328327
host_gateway_ip: &self.config.host_gateway_ip,
329328
enable_user_namespaces: self.config.enable_user_namespaces,
329+
workspace_default_storage_size: &self.config.workspace_default_storage_size,
330330
};
331331
obj.data = sandbox_to_k8s_spec(sandbox.spec.as_ref(), &params);
332332
let api = self.api();
@@ -1025,7 +1025,12 @@ fn apply_workspace_persistence(
10251025
///
10261026
/// Provides a single PVC named "workspace" that backs the `/sandbox`
10271027
/// directory. The init container seeds it from the image on first use.
1028-
fn default_workspace_volume_claim_templates() -> serde_json::Value {
1028+
fn default_workspace_volume_claim_templates(storage_size: &str) -> serde_json::Value {
1029+
let size = if storage_size.is_empty() {
1030+
DEFAULT_WORKSPACE_STORAGE_SIZE
1031+
} else {
1032+
storage_size
1033+
};
10291034
serde_json::json!([{
10301035
"metadata": {
10311036
"name": WORKSPACE_VOLUME_NAME
@@ -1034,7 +1039,7 @@ fn default_workspace_volume_claim_templates() -> serde_json::Value {
10341039
"accessModes": ["ReadWriteOnce"],
10351040
"resources": {
10361041
"requests": {
1037-
"storage": WORKSPACE_DEFAULT_STORAGE
1042+
"storage": size
10381043
}
10391044
}
10401045
}
@@ -1056,6 +1061,7 @@ struct SandboxPodParams<'a> {
10561061
client_tls_secret_name: &'a str,
10571062
host_gateway_ip: &'a str,
10581063
enable_user_namespaces: bool,
1064+
workspace_default_storage_size: &'a str,
10591065
}
10601066

10611067
fn spec_pod_env(spec: Option<&SandboxSpec>) -> std::collections::HashMap<String, String> {
@@ -1112,7 +1118,7 @@ fn sandbox_to_k8s_spec(
11121118
if inject_workspace {
11131119
root.insert(
11141120
"volumeClaimTemplates".to_string(),
1115-
default_workspace_volume_claim_templates(),
1121+
default_workspace_volume_claim_templates(params.workspace_default_storage_size),
11161122
);
11171123
}
11181124

@@ -2572,4 +2578,18 @@ mod tests {
25722578
assert_eq!(tolerations[0]["operator"], "Exists");
25732579
assert_eq!(tolerations[0]["effect"], "NoSchedule");
25742580
}
2581+
2582+
#[test]
2583+
fn default_workspace_vct_uses_provided_storage_size() {
2584+
let vct = default_workspace_volume_claim_templates("5Gi");
2585+
let storage = &vct[0]["spec"]["resources"]["requests"]["storage"];
2586+
assert_eq!(storage, "5Gi");
2587+
}
2588+
2589+
#[test]
2590+
fn default_workspace_vct_falls_back_to_const_when_empty() {
2591+
let vct = default_workspace_volume_claim_templates("");
2592+
let storage = &vct[0]["spec"]["resources"]["requests"]["storage"];
2593+
assert_eq!(storage, DEFAULT_WORKSPACE_STORAGE_SIZE);
2594+
}
25752595
}

crates/openshell-driver-kubernetes/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pub mod config;
55
pub mod driver;
66
pub mod grpc;
77

8-
pub use config::{KubernetesComputeConfig, SupervisorSideloadMethod};
8+
pub use config::{
9+
DEFAULT_WORKSPACE_STORAGE_SIZE, KubernetesComputeConfig, SupervisorSideloadMethod,
10+
};
911
pub use driver::{KubernetesComputeDriver, KubernetesDriverError};
1012
pub use grpc::ComputeDriverService;

crates/openshell-driver-kubernetes/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ async fn main() -> Result<()> {
9393
client_tls_secret_name: args.client_tls_secret_name.unwrap_or_default(),
9494
host_gateway_ip: args.host_gateway_ip.unwrap_or_default(),
9595
enable_user_namespaces: args.enable_user_namespaces,
96+
workspace_default_storage_size: std::env::var(
97+
"OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE",
98+
)
99+
.unwrap_or_else(|_| {
100+
openshell_driver_kubernetes::DEFAULT_WORKSPACE_STORAGE_SIZE.to_string()
101+
}),
96102
})
97103
.await
98104
.into_diagnostic()?;

crates/openshell-server/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,10 @@ async fn build_compute_runtime(
583583

584584
match driver {
585585
ComputeDriverKind::Kubernetes => {
586-
let k8s = kubernetes_config_from_file(file)?;
586+
let mut k8s = kubernetes_config_from_file(file)?;
587+
if let Ok(size) = std::env::var("OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE") {
588+
k8s.workspace_default_storage_size = size;
589+
}
587590
ComputeRuntime::new_kubernetes(
588591
k8s,
589592
store,

deploy/helm/openshell/templates/gateway-config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ data:
9090
{{- if .Values.server.sandboxImagePullPolicy }}
9191
image_pull_policy = {{ .Values.server.sandboxImagePullPolicy | quote }}
9292
{{- end }}
93+
{{- if .Values.server.workspaceDefaultStorageSize }}
94+
workspace_default_storage_size = {{ .Values.server.workspaceDefaultStorageSize | quote }}
95+
{{- end }}
9396
{{- if .Values.supervisor.image.pullPolicy }}
9497
supervisor_image_pull_policy = {{ .Values.supervisor.image.pullPolicy | quote }}
9598
{{- end }}

deploy/helm/openshell/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ server:
138138
# (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev
139139
# clusters so new images are picked up without manual eviction.
140140
sandboxImagePullPolicy: ""
141+
# -- Default storage size for the workspace PVC in sandbox pods.
142+
# Uses Kubernetes quantity syntax (e.g. "2Gi", "10Gi", "500Mi").
143+
# Empty = built-in default (2Gi).
144+
workspaceDefaultStorageSize: ""
141145
# -- gRPC endpoint sandboxes call back into the gateway. Leave empty to derive
142146
# it from the chart fullname, release namespace, service port, and
143147
# disableTls flag, for example https://openshell.openshell.svc.cluster.local:8080.

0 commit comments

Comments
 (0)