From ee59918082683c00035a2e7061795cd0bbbf0c02 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 cbb8cdca..fdb930d8 100644 --- a/docs/deployment_patterns.md +++ b/docs/deployment_patterns.md @@ -163,3 +163,40 @@ To stop a single deployment without affecting the cluster: ```bash laconic-so deployment --dir my-deployment stop --skip-cluster-management ``` + +## 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 870410f8..511445be 100644 --- a/stack_orchestrator/deploy/deployment_create.py +++ b/stack_orchestrator/deploy/deployment_create.py @@ -690,10 +690,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 22c3ccf4..7c706125 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(