From 5a0f573b0ef6661f23e7c8ece27f5821fed8275d Mon Sep 17 00:00:00 2001 From: "A. F. Dudley" Date: Mon, 2 Feb 2026 23:26:13 -0500 Subject: [PATCH] Allow relative volume paths for k8s-kind deployments For k8s-kind, relative paths (e.g., ./data/rpc-config) are resolved to $DEPLOYMENT_DIR/path by _make_absolute_host_path() during kind config generation. This provides Docker Host persistence that survives cluster restarts. Previously, validation threw an exception before paths could be resolved, making it impossible to use relative paths for persistent storage. Changes: - deployment_create.py: Skip relative path check for k8s-kind - cluster_info.py: Allow relative paths to reach PV generation - docs/deployment_patterns.md: Document volume persistence patterns Co-Authored-By: Claude Opus 4.5 --- docs/deployment_patterns.md | 37 +++++++++++++++++++ .../deploy/deployment_create.py | 12 ++++-- stack_orchestrator/deploy/k8s/cluster_info.py | 14 ++++--- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/docs/deployment_patterns.md b/docs/deployment_patterns.md index fb2e0063..b8a7b5ec 100644 --- a/docs/deployment_patterns.md +++ b/docs/deployment_patterns.md @@ -75,3 +75,40 @@ This overwrites your customizations with defaults from the stack's `commands.py` git pull # Get latest spec.yml from your operator repo laconic-so deployment --dir my-deployment restart ``` + +## Volume Persistence in k8s-kind + +k8s-kind has 3 storage layers: + +- **Docker Host**: The physical server running Docker +- **Kind Node**: A Docker container simulating a k8s node +- **Pod Container**: Your workload + +For k8s-kind, volumes with paths are mounted from Docker Host → Kind Node → Pod via extraMounts. + +| spec.yml volume | Storage Location | Survives Pod Restart | Survives Cluster Restart | +|-----------------|------------------|---------------------|-------------------------| +| `vol:` (empty) | Kind Node PVC | ✅ | ❌ | +| `vol: ./data/x` | Docker Host | ✅ | ✅ | +| `vol: /abs/path`| Docker Host | ✅ | ✅ | + +**Recommendation**: Always use paths for data you want to keep. Relative paths +(e.g., `./data/rpc-config`) resolve to `$DEPLOYMENT_DIR/data/rpc-config` on the +Docker Host. + +### Example + +```yaml +# In spec.yml +volumes: + rpc-config: ./data/rpc-config # Persists to $DEPLOYMENT_DIR/data/rpc-config + chain-data: ./data/chain # Persists to $DEPLOYMENT_DIR/data/chain + temp-cache: # Empty = Kind Node PVC (lost on cluster delete) +``` + +### The Antipattern + +Empty-path volumes appear persistent because they survive pod restarts (data lives +in Kind Node container). However, this data is lost when the kind cluster is +recreated. This "false persistence" has caused data loss when operators assumed +their data was safe. diff --git a/stack_orchestrator/deploy/deployment_create.py b/stack_orchestrator/deploy/deployment_create.py index ec15362f..12277043 100644 --- a/stack_orchestrator/deploy/deployment_create.py +++ b/stack_orchestrator/deploy/deployment_create.py @@ -522,10 +522,14 @@ def _check_volume_definitions(spec): for volume_name, volume_path in spec.get_volumes().items(): if volume_path: if not os.path.isabs(volume_path): - raise Exception( - f"Relative path {volume_path} for volume {volume_name} not " - f"supported for deployment type {spec.get_deployment_type()}" - ) + # For k8s-kind: allow relative paths, they'll be resolved + # by _make_absolute_host_path() during kind config generation + if not spec.is_kind_deployment(): + deploy_type = spec.get_deployment_type() + raise Exception( + f"Relative path {volume_path} for volume " + f"{volume_name} not supported for {deploy_type}" + ) @click.command() diff --git a/stack_orchestrator/deploy/k8s/cluster_info.py b/stack_orchestrator/deploy/k8s/cluster_info.py index 42c41b4b..89529b4e 100644 --- a/stack_orchestrator/deploy/k8s/cluster_info.py +++ b/stack_orchestrator/deploy/k8s/cluster_info.py @@ -352,11 +352,15 @@ class ClusterInfo: continue if not os.path.isabs(volume_path): - print( - f"WARNING: {volume_name}:{volume_path} is not absolute, " - "cannot bind volume." - ) - continue + # For k8s-kind, allow relative paths: + # - PV uses /mnt/{volume_name} (path inside kind node) + # - extraMounts resolve the relative path to Docker Host + if not self.spec.is_kind_deployment(): + print( + f"WARNING: {volume_name}:{volume_path} is not absolute, " + "cannot bind volume." + ) + continue if self.spec.is_kind_deployment(): host_path = client.V1HostPathVolumeSource(