Create encrypted Persistent Volumes on OVHcloud Managed Kubernetes clusters with LUKS

Since this summer, it’s possible to create encrypted OVHcloud Block Storage with OMK (OVHcloud managed key) in RBX, SBG, Paris & BHS regions. More regions will come in the coming months 💪.

And the good news is that you can use encrypted Block Storage using Persistent Volumes in your OVHcloud Managed Kubernetes Service (MKS) clusters 🎉.

In this post, we’ll show you how to encrypt persistent volumes on an OVHcloud Managed Kubernetes (MKS) cluster using a csi-cinder-high-speed-gen2-luks Storage Class. Leveraging LUKS-based encryption at the storage layer, you’ll learn how to protect your data at rest without sacrificing the performance of NVMe-backed volumes.

We’ll guide you step by step: defining the Storage Class, creating a Persistent Volume Claim (PVC), and deploying a Pod that mounts the encrypted volume.

This practical walkthrough is designed for developers and platform engineers looking to secure their Kubernetes workloads on OVHcloud in a straightforward way.

How to

You will create a Persistent Volume Claim (PVC), linked to a Storage Class, that will automatically create a Persistent Volume (PV) that will automatically create an associated encrypted Public Cloud Block Storage volume.
Then you will create a Pod attached to the PVC.

Let’s create an encrypted Persistent Volume in our OVHcloud MKS cluster

Prerequisite: Have an OVHcloud MKS cluster.

First, create a csi-cinder-high-speed-gen2-luks.yaml file with the following content:

💡 Note that if you deploy in on a MKS 1AZ cluster (instead of my 3AZ MKS cluster), you should define the volumeBindingMode to Immediate instead.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-cinder-high-speed-gen2-luks
allowVolumeExpansion: true
parameters:
  fsType: ext4
  type: high-speed-gen2-luks
provisioner: cinder.csi.openstack.org
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer 

This StorageClass is using the same configuration as existing csi-cinder-high-speed-gen2 but with the high-speed-gen2-luks type.

So the result will be the usage of SSD disks with NVMe interfaces encrypted with LUKS (Linux Unified Key Setup) which is a standard on-disk format for hard disk encryption.

Apply the manifest file:

kubectl apply -f csi-cinder-high-speed-gen2-luks.yaml

⚠️ You can’t modify the volumeBindingMode value for an existing Storage Class, you have to delete it and create a new one.

List the Storage Classes in the cluster:

$ kubectl get sc
NAME                              PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
csi-cinder-high-speed (default)   cinder.csi.openstack.org   Delete          WaitForFirstConsumer   true                   33d
csi-cinder-high-speed-gen-2       cinder.csi.openstack.org   Delete          WaitForFirstConsumer   true                   33d
csi-cinder-high-speed-gen2-luks   cinder.csi.openstack.org   Delete          WaitForFirstConsumer   true                   4s

Create a pvc-luks.yaml file with the following content:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-luks
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: csi-cinder-high-speed-gen2-luks

Create a new namespace and apply the manifest file into it:

kubectl create ns test-pvc-luks
kubectl apply -f pvc-luks.yaml -n test-pvc-luks

Check the status of our newly created PVC:

$ kubectl get pvc -n test-pvc-luks
NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS                      VOLUMEATTRIBUTESCLASS   AGE
pvc-luks   Pending                                      csi-cinder-high-speed-gen2-luks   <unset>                 3s


$ kubectl describe pvc pvc-luks test-pvc-luks
Name:          pvc-luks
Namespace:     test-pvc-luks
StorageClass:  csi-cinder-high-speed-gen2-luks
Status:        Pending
Volume:
Labels:        <none>
Annotations:   <none>
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode:    Filesystem
Used By:       <none>
Events:
  Type    Reason                Age                From                         Message
  ----    ------                ----               ----                         -------
  Normal  WaitForFirstConsumer  10s (x2 over 10s)  persistentvolume-controller  waiting for first consumer to be created before binding
$ kubectl describe pvc pvc-luks
Name:          pvc-luks
Namespace:     test-pvc-luks
StorageClass:  csi-cinder-high-speed-gen2-luks
Status:        Pending
Volume:
Labels:        <none>
Annotations:   <none>
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode:    Filesystem
Used By:       <none>
Events:
  Type    Reason                Age                From                         Message
  ----    ------                ----               ----                         -------
  Normal  WaitForFirstConsumer  10s (x2 over 10s)  persistentvolume-controller  waiting for first consumer to be created before binding

As you can see, your PVC have been creating, with the luks Storage Class, and is Pending to be Bound, until the creation of a Pod with a volume (because of the WaitForFirstConsumer value):

Create a pod.yaml file with the following content:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-encrypted-volume
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: encrypted-volume
  volumes:
  - name: encrypted-volume
    persistentVolumeClaim:
      claimName: pvc-luks

Create a new namespace and apply the manifest file into it:

kubectl apply -f pod.yaml -n test-pvc-luks

The PVC should now be Bound and a new PV should be created:

$ kubectl get pvc -n test-pvc-luks
NAME       STATUS   VOLUME                                                                     CAPACITY   ACCESS MODES   STORAGECLASS                      VOLUMEATTRIBUTESCLASS   AGE
pvc-luks   Bound    ovh-managed-kubernetes-siti343p-pvc-3a3b1d2e-ebdf-41a2-8f8f-4ee6984b6149   10Gi       RWO            csi-cinder-high-speed-gen2-luks   <unset>                 3m27s

$ kubectl get pv -n test-pvc-luks
NAME                                                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS                      VOLUMEATTRIBUTESCLASS   REASON   AGE
ovh-managed-kubernetes-siti343p-pvc-3a3b1d2e-ebdf-41a2-8f8f-4ee6984b6149   10Gi       RWO            Delete           Bound    test-pvc-luks/pvc-luks   csi-cinder-high-speed-gen2-luks   <unset>                          32s

First the Pod should be in ContainerCreating state (waiting the creation and the attachment of the volume) and after few seconds it will be Running:

$ kubectl get pod pod-with-encrypted-volume -n test-pvc-luks
NAME                        READY   STATUS              RESTARTS   AGE
pod-with-encrypted-volume   0/1     ContainerCreating   0          44s

# Wait a little...

$ kubectl get pod pod-with-encrypted-volume -n test-pvc-luks
NAME                        READY   STATUS    RESTARTS   AGE
pod-with-encrypted-volume   1/1     Running   0          2m10s

The Pod is now created with an attached volume:

$ kubectl describe pod pod-with-encrypted-volume -n test-pvc-luks
Name:             pod-with-encrypted-volume
Namespace:        test-pvc-luks
Priority:         0
Service Account:  default
Node:             my-pool-zone-c-h5xjf-7n7kt/192.168.142.174
Start Time:       Tue, 19 Aug 2025 10:10:41 +0200
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.240.0.203
IPs:
  IP:  10.240.0.203
Containers:
  nginx:
    Container ID:   containerd://c38c0a0e19970503ad1bfaa0c74b5cc320cb9df08456c7613b9a9a8c908b9190
    Image:          nginx
    Image ID:       docker.io/library/nginx@sha256:33e0bbc7ca9ecf108140af6288c7c9d1ecc77548cbfd3952fd8466a75edefe57
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 19 Aug 2025 10:11:42 +0200
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from encrypted-volume (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vbcnk (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  encrypted-volume:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  pvc-luks
    ReadOnly:   false
  kube-api-access-vbcnk:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason                  Age                    From                     Message
  ----     ------                  ----                   ----                     -------
  Normal   Scheduled               3m48s                  default-scheduler        Successfully assigned test-pvc-luks/pod-with-encrypted-volume to my-pool-zone-c-h5xjf-7n7kt
  Warning  FailedAttachVolume      3m25s (x6 over 3m43s)  attachdetach-controller  AttachVolume.Attach failed for volume "ovh-managed-kubernetes-siti343p-pvc-3a3b1d2e-ebdf-41a2-8f8f-4ee6984b6149" : rpc error: code = Internal desc = [ControllerPublishVolume] Attach Volume failed with error failed to attach b76d1025-9473-4050-86be-4880f0f625cb volume to 516c41cf-9637-4b08-a75e-1d265d1773f4 compute: Bad request with: [POST https://compute.eu-west-par.cloud.ovh.net/v2.1/a212a1e43b614c4ba27a247b890fcf59/servers/516c41cf-9637-4b08-a75e-1d265d1773f4/os-volume_attachments], error message: {"badRequest": {"code": 400, "message": "Invalid input received: Invalid volume: Volume b76d1025-9473-4050-86be-4880f0f625cb status must be available or downloading to reserve, but the current status is creating. (HTTP 400) (Request-ID: req-e94505fd-39d6-496c-bc6d-275cd2604dda)"}}
  Normal   SuccessfulAttachVolume  3m8s                   attachdetach-controller  AttachVolume.Attach succeeded for volume "ovh-managed-kubernetes-siti343p-pvc-3a3b1d2e-ebdf-41a2-8f8f-4ee6984b6149"
  Normal   Pulling                 2m53s                  kubelet                  Pulling image "nginx"
  Normal   Pulled                  2m48s                  kubelet                  Successfully pulled image "nginx" in 5.072s (5.072s including waiting). Image size: 72324501 bytes.
  Normal   Created                 2m48s                  kubelet                  Created container: nginx
  Normal   Started                 2m48s                  kubelet                  Started container nginx

Logging in the OVHcloud Control Panel, you can see that the encrypted volume have been successfully created:

Finally, you can use your volume.

Execute a shell in the Nginx Pod and create an index.html file into it:

$ kubectl exec -it pod-with-encrypted-volume -n test-pvc-luks -- /bin/bash

root@pod-with-encrypted-volume:/# echo "Hello from OVHcloud encrypted Block Storage!" > /usr/share/nginx/html/index.html

And curl the webserver:

root@pod-with-encrypted-volume:/# apt update
root@pod-with-encrypted-volume:/# apt install curl
root@pod-with-encrypted-volume:/# curl http://localhost/
Hello from OVHcloud encrypted Block Storage!

🎉

What’s next?

In this blog post we saw a basic (but concrete) usage of the encrypted Persistent Volume on OVHcloud Kubernetes clusters that just bee released, don’t hesitate to think about it for your sensitive data.

In the coming months, the encrypted Block Storage will be available worldwide. Follow the Encrypted Block Volumes issue on GitHub to stay informed.

And don’t hesitate to take a look to our Cloud Roadmap & Changelog to see the state of all of the coming features in OVHcloud Public Cloud products.

Developer Advocate at OVHcloud, specialized in Cloud Native and Infrastructure as Code (IaC).
She is Docker Captain, CNCF ambassador, GDE, Women techmakers Ambassador & GitPod Hero. She has been working as a Developer and Ops for over 20 years. Cloud enthusiast and advocates DevOps/Cloud/Golang best practices.
Conferences and meetups organizer since 2016. Technical writer, a book author & reviewer, a sketchnoter and a speaker at international conferences.
Mentor and promote diversity and accessibility in technology.
Book author, she created a new visual way for people to learn and understand Cloud technologies: "Understanding Kubernetes / Docker in a visual way" in sketchnotes, books and videos.