Skip to main content

Use Hetzner Cloud volumes

Inspect 1.36

Hetzner Cloud volumes are the zero-setup default for a single-writer app on cloud nodes. The standard StorageClass is already in your cluster, backed by the hcloud CSI driver, so you create a PVC and mount it without installing anything.

Confirm the driver is running #

The hcloud CSI driver ships built in and Syself operates it. Confirm it is registered and the class is present:

		$ kubectl get csidrivers csi.hetzner.cloud
NAME                ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES        AGE
csi.hetzner.cloud   true             true             false             <unset>         false               Persistent   12d
$ kubectl get storageclass standard
NAME                 PROVISIONER         RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   csi.hetzner.cloud   Retain          WaitForFirstConsumer   true                   12d
	

The standard class is ReadWriteOnce, binds on first use, and keeps data on delete. Each volume sits on Ceph with three copies, so a failed disk does not lose the data. Hetzner caps a single server at 16 attached volumes. ReadWriteOnce is per node: pods on the same node can share one volume, but pods on different nodes need . See the for the full properties.

Note

The standard class only works on cloud nodes. A pod that requests it on a bare metal node stays Pending, because the hcloud volume cannot attach there. See .

Create a PVC and mount it #

This manifest creates a 10Gi claim and a pod that mounts it. The node affinity keeps the pod off bare metal, where the volume cannot attach.

pv-demo.yamlyaml
		apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pv-claim
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pv-pod
spec:
  containers:
    - name: app
      image: nginx
      volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: pv-storage
  volumes:
    - name: pv-storage
      persistentVolumeClaim:
        claimName: pv-claim
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: instance.hetzner.cloud/is-root-server
                operator: NotIn
                values:
                  - "true"
	

Apply it:

		$ kubectl apply -f pv-demo.yaml
	

Watch it bind #

Look at the PVC right after applying, and it reads Pending:

		$ kubectl get pvc pv-claim
NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
pv-claim   Pending                                      standard       <unset>                 5s
	

That is WaitForFirstConsumer doing its job. Once the pod lands on a node, the driver creates the volume and the PVC flips to Bound:

		$ kubectl get pvc pv-claim
NAME       STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
pv-claim   Bound    pvc-8f3c...   10Gi       RWO            standard       <unset>                 30s
	

Verify the mount #

Write a file through the pod, then confirm it is there:

		$ kubectl exec -it pv-pod -- sh -c 'echo "stored on a Hetzner volume" > /usr/share/nginx/html/hi.txt && cat /usr/share/nginx/html/hi.txt'
stored on a Hetzner volume
	

Anything written under /usr/share/nginx/html now lives on the volume and survives the pod. Because the class reclaim policy is Retain, deleting the PVC keeps the Hetzner volume, so a mistaken delete cannot wipe your data. The flip side is that the volume keeps costing money until you delete it yourself, as a separate and intentional step.

Related: , .