Skip to content

Commit e574986

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 f257ed0 commit e574986

7 files changed

Lines changed: 66 additions & 8 deletions

File tree

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

Lines changed: 25 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,23 @@ 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!(cfg.workspace_default_storage_size, DEFAULT_WORKSPACE_STORAGE_SIZE);
111+
}
112+
113+
#[test]
114+
fn serde_override_workspace_storage_size() {
115+
let json = serde_json::json!({
116+
"workspace_default_storage_size": "10Gi"
117+
});
118+
let cfg: KubernetesComputeConfig = serde_json::from_value(json).unwrap();
119+
assert_eq!(cfg.workspace_default_storage_size, "10Gi");
120+
}
121+
}

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

Lines changed: 27 additions & 6 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,8 +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";
111111

112112
/// Sentinel file written by the init container after copying the image's
113113
/// `/sandbox` contents. Subsequent pod starts skip the copy.
@@ -327,6 +327,7 @@ impl KubernetesComputeDriver {
327327
client_tls_secret_name: &self.config.client_tls_secret_name,
328328
host_gateway_ip: &self.config.host_gateway_ip,
329329
enable_user_namespaces: self.config.enable_user_namespaces,
330+
workspace_default_storage_size: &self.config.workspace_default_storage_size,
330331
};
331332
obj.data = sandbox_to_k8s_spec(sandbox.spec.as_ref(), &params);
332333
let api = self.api();
@@ -1025,7 +1026,12 @@ fn apply_workspace_persistence(
10251026
///
10261027
/// Provides a single PVC named "workspace" that backs the `/sandbox`
10271028
/// directory. The init container seeds it from the image on first use.
1028-
fn default_workspace_volume_claim_templates() -> serde_json::Value {
1029+
fn default_workspace_volume_claim_templates(storage_size: &str) -> serde_json::Value {
1030+
let size = if storage_size.is_empty() {
1031+
DEFAULT_WORKSPACE_STORAGE_SIZE
1032+
} else {
1033+
storage_size
1034+
};
10291035
serde_json::json!([{
10301036
"metadata": {
10311037
"name": WORKSPACE_VOLUME_NAME
@@ -1034,7 +1040,7 @@ fn default_workspace_volume_claim_templates() -> serde_json::Value {
10341040
"accessModes": ["ReadWriteOnce"],
10351041
"resources": {
10361042
"requests": {
1037-
"storage": WORKSPACE_DEFAULT_STORAGE
1043+
"storage": size
10381044
}
10391045
}
10401046
}
@@ -1056,6 +1062,7 @@ struct SandboxPodParams<'a> {
10561062
client_tls_secret_name: &'a str,
10571063
host_gateway_ip: &'a str,
10581064
enable_user_namespaces: bool,
1065+
workspace_default_storage_size: &'a str,
10591066
}
10601067

10611068
fn spec_pod_env(spec: Option<&SandboxSpec>) -> std::collections::HashMap<String, String> {
@@ -1112,7 +1119,7 @@ fn sandbox_to_k8s_spec(
11121119
if inject_workspace {
11131120
root.insert(
11141121
"volumeClaimTemplates".to_string(),
1115-
default_workspace_volume_claim_templates(),
1122+
default_workspace_volume_claim_templates(params.workspace_default_storage_size),
11161123
);
11171124
}
11181125

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

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

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

8-
pub use config::{KubernetesComputeConfig, SupervisorSideloadMethod};
8+
pub use config::{DEFAULT_WORKSPACE_STORAGE_SIZE, KubernetesComputeConfig, SupervisorSideloadMethod};
99
pub use driver::{KubernetesComputeDriver, KubernetesDriverError};
1010
pub use grpc::ComputeDriverService;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ 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("OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE")
97+
.unwrap_or_else(|_| openshell_driver_kubernetes::DEFAULT_WORKSPACE_STORAGE_SIZE.to_string()),
9698
})
9799
.await
98100
.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
@@ -95,6 +95,10 @@ server:
9595
# (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev
9696
# clusters so new images are picked up without manual eviction.
9797
sandboxImagePullPolicy: ""
98+
# Default storage size for the workspace PVC in sandbox pods.
99+
# Uses Kubernetes quantity syntax (e.g. "2Gi", "10Gi", "500Mi").
100+
# Empty = built-in default (2Gi).
101+
workspaceDefaultStorageSize: ""
98102
# gRPC endpoint sandboxes call back into the gateway. Leave empty to derive
99103
# it from the chart fullname, release namespace, service port, and
100104
# disableTls flag (i.e. <scheme>://<fullname>.<namespace>.svc.cluster.local:<port>).

0 commit comments

Comments
 (0)