Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions crates/openshell-driver-kubernetes/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use serde::{Deserialize, Serialize};
/// Default Kubernetes namespace for sandbox resources.
pub const DEFAULT_K8S_NAMESPACE: &str = "openshell";

/// Default storage size for the workspace PVC.
pub const DEFAULT_WORKSPACE_STORAGE_SIZE: &str = "2Gi";

/// How the supervisor binary is delivered into sandbox pods.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -64,6 +67,7 @@ pub struct KubernetesComputeConfig {
pub client_tls_secret_name: String,
pub host_gateway_ip: String,
pub enable_user_namespaces: bool,
pub workspace_default_storage_size: String,
}

impl Default for KubernetesComputeConfig {
Expand All @@ -84,6 +88,7 @@ impl Default for KubernetesComputeConfig {
client_tls_secret_name: String::new(),
host_gateway_ip: String::new(),
enable_user_namespaces: false,
workspace_default_storage_size: DEFAULT_WORKSPACE_STORAGE_SIZE.to_string(),
}
}
}
Expand All @@ -94,3 +99,26 @@ fn default_sandbox_image() -> String {
openshell_core::image::DEFAULT_COMMUNITY_REGISTRY
)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn default_workspace_storage_size_is_2gi() {
let cfg = KubernetesComputeConfig::default();
assert_eq!(
cfg.workspace_default_storage_size,
DEFAULT_WORKSPACE_STORAGE_SIZE
);
}

#[test]
fn serde_override_workspace_storage_size() {
let json = serde_json::json!({
"workspace_default_storage_size": "10Gi"
});
let cfg: KubernetesComputeConfig = serde_json::from_value(json).unwrap();
assert_eq!(cfg.workspace_default_storage_size, "10Gi");
}
}
34 changes: 27 additions & 7 deletions crates/openshell-driver-kubernetes/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

//! Kubernetes compute driver.

use crate::config::{KubernetesComputeConfig, SupervisorSideloadMethod};
use crate::config::{
DEFAULT_WORKSPACE_STORAGE_SIZE, KubernetesComputeConfig, SupervisorSideloadMethod,
};
use futures::{Stream, StreamExt, TryStreamExt};
use k8s_openapi::api::core::v1::{Event as KubeEventObj, Node};
use kube::api::{Api, ApiResource, DeleteParams, ListParams, PostParams};
Expand Down Expand Up @@ -106,9 +108,6 @@ const WORKSPACE_INIT_MOUNT_PATH: &str = "/workspace-pvc";
/// Name of the init container that seeds the workspace PVC.
const WORKSPACE_INIT_CONTAINER_NAME: &str = "workspace-init";

/// Default storage request for the workspace PVC.
const WORKSPACE_DEFAULT_STORAGE: &str = "2Gi";

/// Sentinel file written by the init container after copying the image's
/// `/sandbox` contents. Subsequent pod starts skip the copy.
const WORKSPACE_SENTINEL: &str = ".workspace-initialized";
Expand Down Expand Up @@ -327,6 +326,7 @@ impl KubernetesComputeDriver {
client_tls_secret_name: &self.config.client_tls_secret_name,
host_gateway_ip: &self.config.host_gateway_ip,
enable_user_namespaces: self.config.enable_user_namespaces,
workspace_default_storage_size: &self.config.workspace_default_storage_size,
};
obj.data = sandbox_to_k8s_spec(sandbox.spec.as_ref(), &params);
let api = self.api();
Expand Down Expand Up @@ -1025,7 +1025,12 @@ fn apply_workspace_persistence(
///
/// Provides a single PVC named "workspace" that backs the `/sandbox`
/// directory. The init container seeds it from the image on first use.
fn default_workspace_volume_claim_templates() -> serde_json::Value {
fn default_workspace_volume_claim_templates(storage_size: &str) -> serde_json::Value {
let size = if storage_size.is_empty() {
DEFAULT_WORKSPACE_STORAGE_SIZE
} else {
storage_size
};
serde_json::json!([{
"metadata": {
"name": WORKSPACE_VOLUME_NAME
Expand All @@ -1034,7 +1039,7 @@ fn default_workspace_volume_claim_templates() -> serde_json::Value {
"accessModes": ["ReadWriteOnce"],
"resources": {
"requests": {
"storage": WORKSPACE_DEFAULT_STORAGE
"storage": size
}
}
}
Expand All @@ -1056,6 +1061,7 @@ struct SandboxPodParams<'a> {
client_tls_secret_name: &'a str,
host_gateway_ip: &'a str,
enable_user_namespaces: bool,
workspace_default_storage_size: &'a str,
}

fn spec_pod_env(spec: Option<&SandboxSpec>) -> std::collections::HashMap<String, String> {
Expand Down Expand Up @@ -1112,7 +1118,7 @@ fn sandbox_to_k8s_spec(
if inject_workspace {
root.insert(
"volumeClaimTemplates".to_string(),
default_workspace_volume_claim_templates(),
default_workspace_volume_claim_templates(params.workspace_default_storage_size),
);
}

Expand Down Expand Up @@ -2572,4 +2578,18 @@ mod tests {
assert_eq!(tolerations[0]["operator"], "Exists");
assert_eq!(tolerations[0]["effect"], "NoSchedule");
}

#[test]
fn default_workspace_vct_uses_provided_storage_size() {
let vct = default_workspace_volume_claim_templates("5Gi");
let storage = &vct[0]["spec"]["resources"]["requests"]["storage"];
assert_eq!(storage, "5Gi");
}

#[test]
fn default_workspace_vct_falls_back_to_const_when_empty() {
let vct = default_workspace_volume_claim_templates("");
let storage = &vct[0]["spec"]["resources"]["requests"]["storage"];
assert_eq!(storage, DEFAULT_WORKSPACE_STORAGE_SIZE);
}
}
4 changes: 3 additions & 1 deletion crates/openshell-driver-kubernetes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod config;
pub mod driver;
pub mod grpc;

pub use config::{KubernetesComputeConfig, SupervisorSideloadMethod};
pub use config::{
DEFAULT_WORKSPACE_STORAGE_SIZE, KubernetesComputeConfig, SupervisorSideloadMethod,
};
pub use driver::{KubernetesComputeDriver, KubernetesDriverError};
pub use grpc::ComputeDriverService;
6 changes: 6 additions & 0 deletions crates/openshell-driver-kubernetes/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ async fn main() -> Result<()> {
client_tls_secret_name: args.client_tls_secret_name.unwrap_or_default(),
host_gateway_ip: args.host_gateway_ip.unwrap_or_default(),
enable_user_namespaces: args.enable_user_namespaces,
workspace_default_storage_size: std::env::var(
"OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE",
)
.unwrap_or_else(|_| {
openshell_driver_kubernetes::DEFAULT_WORKSPACE_STORAGE_SIZE.to_string()
}),
})
.await
.into_diagnostic()?;
Expand Down
5 changes: 4 additions & 1 deletion crates/openshell-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,10 @@ async fn build_compute_runtime(

match driver {
ComputeDriverKind::Kubernetes => {
let k8s = kubernetes_config_from_file(file)?;
let mut k8s = kubernetes_config_from_file(file)?;
if let Ok(size) = std::env::var("OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE") {
k8s.workspace_default_storage_size = size;
}
ComputeRuntime::new_kubernetes(
k8s,
store,
Expand Down
1 change: 1 addition & 0 deletions deploy/helm/openshell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ cert-manager alternative.
| server.tls.certSecretName | string | `"openshell-server-tls"` | K8s secret (type kubernetes.io/tls) with tls.crt and tls.key for the server. |
| server.tls.clientCaSecretName | string | `"openshell-server-client-ca"` | K8s secret with ca.crt for client certificate verification (mTLS). Set to "" to disable mTLS and run HTTPS-only (use OIDC for auth instead). |
| server.tls.clientTlsSecretName | string | `"openshell-client-tls"` | K8s secret mounted into sandbox pods for mTLS to the server. |
| server.workspaceDefaultStorageSize | string | `""` | Default storage size for the workspace PVC in sandbox pods. Uses Kubernetes quantity syntax (e.g. "2Gi", "10Gi", "500Mi"). Empty = built-in default (2Gi). |
| service.healthPort | int | `8081` | Gateway health service port. |
| service.metricsPort | int | `9090` | Gateway metrics service port. |
| service.port | int | `8080` | Gateway gRPC/HTTP service port. |
Expand Down
3 changes: 3 additions & 0 deletions deploy/helm/openshell/templates/gateway-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ data:
{{- if .Values.server.sandboxImagePullPolicy }}
image_pull_policy = {{ .Values.server.sandboxImagePullPolicy | quote }}
{{- end }}
{{- if .Values.server.workspaceDefaultStorageSize }}
workspace_default_storage_size = {{ .Values.server.workspaceDefaultStorageSize | quote }}
{{- end }}
{{- if .Values.supervisor.image.pullPolicy }}
supervisor_image_pull_policy = {{ .Values.supervisor.image.pullPolicy | quote }}
{{- end }}
4 changes: 4 additions & 0 deletions deploy/helm/openshell/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ server:
# (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev
# clusters so new images are picked up without manual eviction.
sandboxImagePullPolicy: ""
# -- Default storage size for the workspace PVC in sandbox pods.
# Uses Kubernetes quantity syntax (e.g. "2Gi", "10Gi", "500Mi").
# Empty = built-in default (2Gi).
workspaceDefaultStorageSize: ""
# -- gRPC endpoint sandboxes call back into the gateway. Leave empty to derive
# it from the chart fullname, release namespace, service port, and
# disableTls flag, for example https://openshell.openshell.svc.cluster.local:8080.
Expand Down
Loading