Last Updated: Jan 12, 2026
Introduction
Using local-path-provisioner (https://github.com/rancher/local-path-provisioner), you can use the local disk mounted to worker node on Crusoe Managed Kubernetes (CMK) cluster to dynamically provision volumes for the replicas in a deployment. This may be needed in situations where static provisioning is not viable, and is intended for pods to use local NVMe volumes as scratch pad as an ephemeral storage.
Prerequisites
- Crusoe Managed Kubernetes (CMK) cluster, and a Node Pool with Crusoe Compute Instance type that has Local NVMe storage
- kubectl configured to access the CMK cluster
- Local NVMe in RAID0 configurations mounted to the compute node (We will use /mnt/raid0 for this example)
- All pods in a workload deployment is to be placed on a single node (i.e. if each replica is to use a distinct GPU on its workload on a multi-GPU instance like Crusoe's H100)
Step-by-Step Instructions
-
Install local-path-provisioner: We will closely follow the Stable installation setup provided by using
kubectl apply, with one exception: we need to revise ConfigMap so that it uses the mounted NVMe (/mnt/raid0):local-path-provisioner.yaml
apiVersion: v1 kind: Namespace metadata: name: local-path-storage --- apiVersion: v1 kind: ServiceAccount metadata: name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: local-path-provisioner-role namespace: local-path-storage rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: local-path-provisioner-role rules: - apiGroups: [""] resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: local-path-provisioner-bind namespace: local-path-storage roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-path-provisioner-bind roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: apps/v1 kind: Deployment metadata: name: local-path-provisioner namespace: local-path-storage spec: replicas: 1 selector: matchLabels: app: local-path-provisioner template: metadata: labels: app: local-path-provisioner spec: serviceAccountName: local-path-provisioner-service-account containers: - name: local-path-provisioner image: rancher/local-path-provisioner:v0.0.32 imagePullPolicy: IfNotPresent command: - local-path-provisioner - --debug - start - --config - /etc/config/config.json volumeMounts: - name: config-volume mountPath: /etc/config/ env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: CONFIG_MOUNT_PATH value: /etc/config/ volumes: - name: config-volume configMap: name: local-path-config --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-path provisioner: rancher.io/local-path volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete --- kind: ConfigMap apiVersion: v1 metadata: name: local-path-config namespace: local-path-storage data: config.json: |- { "nodePathMap":[ { "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":["/mnt/raid0"] } ] } setup: |- #!/bin/sh set -eu mkdir -m 0777 -p "$VOL_DIR" teardown: |- #!/bin/sh set -eu rm -rf "$VOL_DIR" helperPod.yaml: |- apiVersion: v1 kind: Pod metadata: name: helper-pod spec: priorityClassName: system-node-critical tolerations: - key: node.kubernetes.io/disk-pressure operator: Exists effect: NoSchedule containers: - name: helper-pod image: busybox imagePullPolicy: IfNotPresentkubectl apply -f local-path-storage.yaml -
Create PersistentVolumeClaim using local volume type
pyc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-volume-pvc annotations: volumeType: local spec: accessModes: - ReadWriteOnce storageClassName: local-path resources: requests: storage: 800Gikubectl apply -f pvc.yamlThe PVC will be pending because there are no volumes mounted yet.
-
Create a Deployment using the name of the replicas for individual subpaths (you may need to add nodeSelector to deploy all replicas to a single node for multi-GPU workloads in case there are multiple nodes in a NodePool):
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: local-path-app spec: replicas: 8 selector: matchLabels: app: local-path-app template: metadata: labels: app: local-path-app spec: containers: - name: app image: nginx:stable-alpine imagePullPolicy: IfNotPresent env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name volumeMounts: - name: volv mountPath: /data subPathExpr: $(POD_NAME) ports: - containerPort: 80 volumes: - name: volv persistentVolumeClaim: claimName: local-volume-pvckubectl apply -f deployment.yaml
Results
You should see all 8 replicas running:
kubectl get pods -l local-path-app
NAME READY STATUS RESTARTS AGE
local-path-app-b6488c8dc-4kbkz 1/1 Running 0 3m7s
local-path-app-b6488c8dc-658jc 1/1 Running 0 3m7s
local-path-app-b6488c8dc-7tdhh 1/1 Running 0 3m7s
local-path-app-b6488c8dc-btlk2 1/1 Running 0 3m7s
local-path-app-b6488c8dc-fhk5n 1/1 Running 0 3m7s
local-path-app-b6488c8dc-g2jtt 1/1 Running 0 3m7s
local-path-app-b6488c8dc-mfg9w 1/1 Running 0 3m7s
local-path-app-b6488c8dc-w6kb8 1/1 Running 0 3m7s
You can start a shell in one of the pods and see that the volume is mounted to /data:
kubectl exec -it local-path-app-b6488c8dc-4kbkz -- sh
/ # df -Th
Filesystem Type Size Used Available Use% Mounted on
overlay overlay 879.1G 21.2G 813.2G 3% /
tmpfs tmpfs 64.0M 0 64.0M 0% /dev
/dev/md127 xfs 6.1T 43.7G 6.1T 1% /data
/dev/vda1 ext4 123.9G 4.2G 119.6G 3% /etc/hosts
/dev/vda1 ext4 123.9G 4.2G 119.6G 3% /dev/termination-log
/dev/nvme0n1 ext4 879.1G 21.2G 813.2G 3% /etc/hostname
/dev/nvme0n1 ext4 879.1G 21.2G 813.2G 3% /etc/resolv.conf
shm tmpfs 64.0M 0 64.0M 0% /dev/shm
tmpfs tmpfs 942.8G 12.0K 942.8G 0% /run/secrets/kubernetes.io/serviceaccount
tmpfs tmpfs 472.4G 0 472.4G 0% /proc/acpi
tmpfs tmpfs 64.0M 0 64.0M 0% /proc/kcore
tmpfs tmpfs 64.0M 0 64.0M 0% /proc/keys
tmpfs tmpfs 64.0M 0 64.0M 0% /proc/timer_list
tmpfs tmpfs 472.4G 0 472.4G 0% /proc/scsi
tmpfs tmpfs 472.4G 0 472.4G 0% /sys/firmware
tmpfs tmpfs 472.4G 0 472.4G 0% /sys/devices/virtual/powercap
You can also SSH into the node to check that individual pod volumes are being created in the local NVMe mount:
ssh -i <your ssh key> ubuntu@<IP address>
df -Th
...
/dev/md127 xfs 6.2T 44G 6.1T 1% /mnt/raid0
...
ls /mnt/raid0
pvc-073c79df-bdb5-41b3-8041-57451d214fac_default_local-volume-pvc
ls /mnt/raid0/pvc-073c79df-bdb5-41b3-8041-57451d214fac_default_local-volume-pvc
local-path-app-b6488c8dc-4kbkz local-path-app-b6488c8dc-7tdhh local-path-app-b6488c8dc-fhk5n local-path-app-b6488c8dc-mfg9w
local-path-app-b6488c8dc-658jc local-path-app-b6488c8dc-btlk2 local-path-app-b6488c8dc-g2jtt local-path-app-b6488c8dc-w6kb8
Additional Resources
- Local Path Provisioner: https://github.com/rancher/local-path-provisioner
- How-To Setup RAID0 Storage on Crusoe Managed Kubernetes: https://support.crusoecloud.com/hc/en-us/articles/35590026497563-How-To-Setup-Raid0-Storage-on-Crusoe-Managed-Kubernetes-CMK