Skip to main content
Crusoe Support Help Center home page
Crusoe

How-To dynamically provision volumes using Local NVMe on Crusoe Managed Kubernetes

Young Jeong
Young Jeong
Updated

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

  1. 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: IfNotPresent
    kubectl apply -f local-path-storage.yaml
  2. 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: 800Gi
    kubectl apply -f pvc.yaml

    The PVC will be pending because there are no volumes mounted yet.

  3. 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-pvc
    kubectl 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

 

Related to

Was this article helpful?

0 out of 0 found this helpful

Still need help?

Our support team is ready to assist you with any questions.

Have more questions? Submit a request

Recently Viewed

Comments

0 comments

Article is closed for comments.