Sometimes, you don’t have block volumes provisioning available on your Kubernetes cluster. In particular when your cluster is running on premise / outside the clouds providers. In this article i‘m gonna introduce you to one great feature called “local” volume and how to provision automatically local Pv with the local volume static provisionner.
- Creating the local volume persistent volume claim
- Attach the local claim
- Persistent Storage Model
- Local Persistent Volumes
- Volume Access Mode
- Volume state
- Volume Reclaim Policy
- Manual volumes provisioning
- Volume selectors
- Storage Classes
- Dynamic volumes provisioning
- Dynamically Provision a GlusterFS volume
- Redis benchmark
- Create a persistent volume claim
- Create a Redis Master
- Run benchmark
- Provisioning local volumes without the Local Storage Operator
- Prepare your local disk on all workers nodes
- Why Local-volume-static-provisionner ?
- Hostpath vs local volume
- Deploy local-static-provisionner
- What is local volume ?
- A simple use case example
- Using tolerations with Local Storage Operator pods
- Provisioning local volumes by using the Local Storage Operator
- Installing the Local Storage Operator
- Deleting the Local Storage Operator resources
- Removing a local volume or local volume set
- Uninstalling the Local Storage Operator
- Automating discovery and provisioning for local storage devices
- Conclusion
Creating the local volume persistent volume claim
Local volumes must be statically created as a persistent volume claim (PVC)
to be accessed by the pod.
Prerequisites
Persistent volumes have been created using the local volume provisioner.
Procedure
Create the PVC using the corresponding storage class:
kind: PersistentVolumeClaimapiVersion: v1metadata: name: local-pvc-name (1)spec: accessModes: - ReadWriteOnce volumeMode: Filesystem (2) resources: requests: storage: 100Gi (3) storageClassName: local-sc (4)1 Name of the PVC. 2 The type of the PVC. Defaults to Filesystem.3 The amount of storage available to the PVC. 4 Name of the storage class required by the claim. Create the PVC in the OpenShift Container Platform cluster, specifying the file
you just created:$ oc create -f <local-pvc>.yaml
Attach the local claim
After a local volume has been mapped to a persistent volume claim
it can be specified inside of a resource.
Prerequisites
A persistent volume claim exists in the same namespace.
Procedure
Include the defined claim in the resource spec. The following example
declares the persistent volume claim inside a pod:apiVersion: v1kind: Podspec: ... containers: volumeMounts: - name: local-disks (1) mountPath: /data (2) volumes: - name: localpvc persistentVolumeClaim: claimName: local-pvc-name (3)1 The name of the volume to mount. 2 The path inside the pod where the volume is mounted. Do not mount to the container root, /, or any path that is the same in the host and the container. This can corrupt your host system if the container is sufficiently privileged, such as the host/dev/ptsfiles. It is safe to mount the host by using/host.3 The name of the existing persistent volume claim to use. Create the resource in the OpenShift Container Platform cluster, specifying the file
you just created:$ oc create -f <local-pod>.yaml
- Installing the Local Storage Operator
- Provisioning local volumes by using the Local Storage Operator
- Provisioning local volumes without the Local Storage Operator
- Creating the local volume persistent volume claim
- Attach the local claim
- Automating discovery and provisioning for local storage devices
- Using tolerations with Local Storage Operator pods
- Deleting the Local Storage Operator resources
- Removing a local volume or local volume set
- Uninstalling the Local Storage Operator
OpenShift Container Platform can be provisioned with persistent storage by using
local volumes. Local persistent volumes allow you to access local storage
devices, such as a disk or partition, by using the standard
persistent volume claim interface.
Local volumes can be used without manually scheduling pods to nodes
because the system is aware of the volume node constraints. However,
local volumes are still subject to the availability of the underlying node
and are not suitable for all applications.
Local volumes can only be used as a statically created persistent volume. |
Persistent Storage Model
Containers are ephemeral, meaning the container file system only lives as long as the container does. Volumes are simplest way to achieve data persistance. In kubernetes, a more flexible and powerful model is available.
This model is based on the following abstractions:
PersistentVolume: it models shared storage that has been provisioned by the cluster administrator. It is a resource in the cluster just like a node is a cluster resource. Persistent volumes are like standard volumes, but having a lifecycle independent of any individual pod. Also they hide to the users the details of the implementation of the storage, e.g. NFS, iSCSI, or other cloud storage systems.
PersistentVolumeClaim: it is a request for storage by a user. It is similar to a pod. Pods consume node resources and persistent volume claims consume persistent volume objects. As pods can request specific levels of resources like cpu and memory, volume claimes claims can request the access modes like read-write or read-only and stoarage capacity.
Kubernetes provides two different ways to provisioning storage:
- Manual Provisioning: the cluster administrator has to manually make calls to the storage infrastructure to create persisten volumes and then users need to create volume claims to consume storage volumes.
- Dynamic Provisioning: storage volumes are automatically created on-demand when users claim for storage avoiding the cluster administrator to pre-provision storage.
In this section we’re going to introduce this model by using simple examples. Please, refer to official documentation for more details.
- Local Persistent Volumes
- Volume Access Mode
- Volume State
- Volume Reclaim Policy
- Manual volumes provisioning
- Storage Classes
- Dynamic volumes provisioning
- Redis benchmark
- Stateful Applications
- Configure GlusterFS as Storage backend
- Configure Ceph as Storage backend
Local Persistent Volumes
Start by defining a persistent volume local-persistent-volume-recycle.yaml configuration file
kind: PersistentVolumeapiVersion: v1metadata: name: local labels: type: localspec: storageClassName: "" capacity: storage: 2Gi accessModes: - ReadWriteOnce hostPath: path: "/data" persistentVolumeReclaimPolicy: Recycle
The configuration file specifies that the volume is at /data on the the cluster’s node. The volume type is hostPath meaning the volume is local to the host node. The configuration also specifies a size of 2GB and the access mode of ReadWriteOnce, meanings the volume can be mounted as read write by a single pod at time. The reclaim policy is Recycle meaning the volume can be used many times. It defines the Storage Class name manual for the persisten volume, which will be used to bind a claim to this volume.
Create the persistent volume
kubectl create -f local-persistent-volume-recycle.yamland view information about it
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
local 2Gi RWO Recycle Available 33mNow, we’re going to use the volume above by creating a claiming for persistent storage. Create the following volume-claim.yaml configuration file
kind: PersistentVolumeClaimapiVersion: v1metadata: name: volume-claimspec: storageClassName: "" accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
Note the claim is for 1GB of space where the the volume is 2GB. The claim will bound any volume meeting the minimum requirements specified into the claim definition.
Create the claim
kubectl create -f volume-claim.yamlCheck the status of persistent volume to see if it is bound
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
local 2Gi RWO Recycle Bound project/volume-claim 37mCheck the status of the claim
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
volume-claim Bound local 2Gi RWO 1mCreate a nginx-pod-pvc.yaml configuration file for a nginx pod using the above claim for its html content directory
---kind: PodapiVersion: v1metadata: name: nginx namespace: default labels:spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 name: "http-server" volumeMounts: - mountPath: "/usr/share/nginx/html" name: html volumes: - name: html persistentVolumeClaim: claimName: volume-claim
Note that the pod configuration file specifies a persistent volume claim, but it does not specify a persistent volume. From the pod point of view, the claim is the volume. Please note that a claim must exist in the same namespace as the pod using the claim.
Create the nginx pod
kubectl create -f nginx-pod-pvc.yamlAccessing the nginx will return 403 Forbidden since there are no html files to serve in the data volume
kubectl get pod nginx -o yaml | grep IP hostIP: 10.10.10.86 podIP: 172.30.5.2
curl 172.30.5.2:80
403 ForbiddenLet’s login to the worker node and populate the data volume
echo "Welcome to $(hostname)" > /data/index.htmlNow try again to access the nginx application
curl 172.30.5.2:80 Welcome to kubew05To test the persistence of the volume and related claim, delete the pod and recreate it
kubectl delete pod nginx
pod "nginx" deleted
kubectl create -f nginx-pod-pvc.yaml
pod "nginx" createdLocate the IP of the new nginx pod and try to access it
kubectl get pod nginx -o yaml | grep podIP podIP: 172.30.5.2
curl 172.30.5.2
Welcome to kubew05Volume Access Mode
A persistent volume can be mounted on a host in any way supported by the resource provider. Different storage providers have different capabilities and access modes are set to the specific modes supported by that particular volume. For example, NFS can support multiple read write clients, but an iSCSI volume can be support only one.
The access modes are:
- ReadWriteOnce: the volume can be mounted as read-write by a single node
- ReadOnlyMany: the volume can be mounted read-only by many nodes
- ReadWriteMany: the volume can be mounted as read-write by many nodes
Claims and volumes use the same conventions when requesting storage with specific access modes. Pods use claims as volumes. For volumes which support multiple access modes, the user specifies which mode desired when using their claim as a volume in a pod.
A volume can only be mounted using one access mode at a time, even if it supports many. For example, a NFS volume can be mounted as ReadWriteOnce by a single node or ReadOnlyMany by many nodes, but not at the same time.
Block based volumes, e.g. iSCSI and Fibre Channel cannot be mounted as ReadWriteMany at same type. The iSCSI and Fibre Channel volumes do not have any fencing mechanisms yet, so you must ensure the volumes are only used by one node at a time. In certain situations, such as draining a node, the volumes may be used simultaneously by two nodes. Before draining the node, first ensure the pods that use these volumes are deleted.
Volume state
When a pod claims for a volume, the cluster inspects the claim to find the volume meeting claim requirements and mounts that volume for the pod. Once a pod has a claim and that claim is bound, the bound volume belongs to the pod.
A volume will be in one of the following state:
- Available: a volume that is not yet bound to a claim
- Bound: the volume is bound to a claim
- Released: the claim has been deleted, but the volume is not yet available
- Failed: the volume has failed
The volume is considered released when the claim is deleted, but it is not yet available for another claim. Once the volume becomes available again then it can bound to another other claim.
In our example, delete the volume claim
kubectl delete pvc volume-claimSee the status of the volume
kubectl get pv persistent-volume
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
local 2Gi RWO Recycle Available 57mVolume Reclaim Policy
When deleting a claim, the volume becomes available to other claims only when the volume claim policy is set to Recycle. Volume claim policies currently supported are:
- Retain: the content of the volume still exists when the volume is unbound and the volume is released
- Recycle: the content of the volume is deleted when the volume is unbound and the volume is available
- Delete: the content and the volume are deleted when the volume is unbound.
Please note that, currently, only NFS and HostPath support recycling.
When the policy is set to Retain the volume is released but it is not yet available for another claim because the previous claimant’s data are still on the volume.
Define a persistent volume local-persistent-volume-retain.yaml configuration file
kind: PersistentVolumeapiVersion: v1metadata: name: local-retain labels: type: localspec: storageClassName: "" capacity: storage: 2Gi accessModes: - ReadWriteOnce hostPath: path: "/data" persistentVolumeReclaimPolicy: Retain
Create the persistent volume and the claim
kubectl create -f local-persistent-volume-retain.yaml
kubectl create -f volume-claim.yamlLogin to the pod using the claim and create some data on the volume
root@nginx:/# exit»>
kubectl exec -it nginx bash
root@nginx:/# echo "Hello World" > /usr/share/nginx/html/index.html
root@nginx:/# exitDelete the claim
kubectl delete pvc volume-claimand check the status of the volume
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS AGE
local-retain 2Gi RWO Retain Released project/volume-claim 3mWe see the volume remain in the released status and not becomes available since the reclaim policy is set to Retain. Now login to the worker node and check data are still there.
An administrator can manually reclaim the volume by deleteting the volume and creating a another one.
Manual volumes provisioning
In this section we’re going to use a Network File System storage backend for manual provisioning of shared volumes. Main limit of local storage for container volumes is that storage area is tied to the host where it resides. If kubernetes moves the pod from another host, the moved pod is no more to access the data since local storage is not shared between multiple hosts of the cluster. To achieve a more useful storage backend we need to leverage on a shared storage technology like NFS.
We’ll assume a simple external NFS server fileserver sharing some folders. To make worker nodes able to consume these NFS shares, install the NFS client on all the worker nodes by yum install -y nfs-utils command.
Define a persistent volume as in the nfs-persistent-volume.yaml configuration file
apiVersion: v1kind: PersistentVolumemetadata: name: nfs-volumespec: storageClassName: "" capacity: storage: 1Gi accessModes: - ReadWriteOnce nfs: path: "/mnt/nfs" server: fileserver persistentVolumeReclaimPolicy: Recycle
Create the persistent volume
kubectl create -f nfs-persistent-volume.yaml
persistentvolume "nfs" created
kubectl get pv nfs -o wide
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-volume 1Gi RWO Recycle Available 7sThanks to the persistent volume model, kubernetes hides the nature of storage and its complex setup to the applications. An user need only to claim volumes for their pods without deal with storage configuration and operations.
Create the claim
kubectl create -f volume-claim.yamlCheck the bound
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-volume 1Gi RWO Recycle Bound project/volume-claim 5m
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
volume-claim Bound nfs-volume 1Gi RWO 9sNow we are going to create more nginx pods using the same claim.
For example, create the nginx-pvc-template.yaml template for a nginx application having the html content folder placed on the shared storage
apiVersion: extensions/v1beta1kind: Deploymentmetadata: generation: 1 labels: run: nginx name: nginx-pvcspec: replicas: 3 selector: matchLabels: run: nginx strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate template: metadata: labels: run: nginx spec: containers: - image: nginx:latest imagePullPolicy: IfNotPresent name: nginx ports: - containerPort: 80 protocol: TCP name: "http-server" volumeMounts: - mountPath: "/usr/share/nginx/html" name: html volumes: - name: html persistentVolumeClaim: claimName: volume-claim dnsPolicy: ClusterFirst restartPolicy: Always
The template above defines a nginx application based on a nginx deploy of 3 replicas. The nginx application requires a shared volume for its html content. The application does not have to deal with complexity of setup and admin an NFS share.
Deploy the application
kubectl create -f nginx-pvc-template.yamlCheck all pods are up and running
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-pvc-3474572923-3cxnf 1/1 Running 0 2m 10.38.5.89 kubew05
nginx-pvc-3474572923-6cr28 1/1 Running 0 6s 10.38.3.140 kubew03
nginx-pvc-3474572923-z17ls 1/1 Running 0 2m 10.38.5.90 kubew05Login to one of these pods and create some html content
root@nginx-pvc-3474572923-3cxnf:/# exit»>
kubectl exec -it nginx-pvc-3474572923-3cxnf bash
root@nginx-pvc-3474572923-3cxnf:/# echo 'Hello from NFS!' > /usr/share/nginx/html/index.html
root@nginx-pvc-3474572923-3cxnf:/# exitSince all three pods mount the same shared folder on the NFS, the just created html content is placed on the NFS share and it is accessible from any of the three pods
curl 10.38.5.89
Hello from NFS!
curl 10.38.5.90
Hello from NFS!
curl 10.38.3.140
Hello from NFS!Volume selectors
A volume claim can define a label selector to bound a specific volume. For example, define a claim as in the pvc-volume-selector.yaml configuration file
kind: PersistentVolumeClaimapiVersion: v1metadata: name: pvc-volume-selectorspec: storageClassName: "" accessModes: - ReadWriteMany resources: requests: storage: 1Gi selector: matchLabels: volumeName: "share01"
Create the claim
kubectl create -f pvc-volume-selector.yamlThe claim remains pending because there are no matching volumes
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-volume-selector Pending 5s
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
share01 1Gi RWX Recycle Available
share02 1Gi RWX Recycle Available Pick the volume named share01 and label it
kubectl label pv share00 volumeName="share01"
persistentvolume "share01" labeledAnd check if the claim bound the volume
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
share01 1Gi RWX Recycle Bound project/pvc-volume-selector
share02 1Gi RWX Recycle Available Storage Classes
A Persistent Volume uses a given storage class specified into its definition file. A claim can request a particular class by specifying the name of a storage class in its definition file. Only volumes of the requested class can be bound to the claim requesting that class.
If the storage class is not specified in the persistent volume definition, the volume has no class and can only be bound to claims that do not require any class.
Multiple storage classes can be defined specifying the volume provisioner to use when creating a volume of that class. This allows the cluster administrator to define multiple type of storage within a cluster, each with a custom set of parameters.
For example, the following gluster-storage-class.yaml configuration file defines a storage class for a GlusterFS backend
kind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata: name: glusterfs labels:provisioner: kubernetes.io/glusterfsreclaimPolicy: Deleteparameters: resturl: "http://heketi:8080" volumetype: "replicate:3"
Create the storage class
kubectl create -f gluster-storage-class.yaml
kubectl get sc
NAME PROVISIONER
glusterfs-storage-class kubernetes.io/glusterfsThe cluster administrator can define a class as default storage class by setting an annotation in the class definition file
kind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata: name: default-storage-class labels: annotations: storageclass.kubernetes.io/is-default-class: "true"provisioner: kubernetes.io/glusterfsreclaimPolicy: Deleteparameters: resturl: "http://heketi:8080"
Check the storage classes
kubectl get sc
NAME PROVISIONER
default-storage-class (default) kubernetes.io/glusterfs
glusterfs-storage-class kubernetes.io/glusterfsIf the cluster administrator defines a default storage class, all claims that do not require any class will be dynamically bound to volumes having the default storage class.
Dynamic volumes provisioning
In this section we’re going to use a GlusterFS distributed storage backend for dynamic provisioning of shared volumes. We’ll assume an external GlusterFS cluster made of three nodes providing a distributed and high available file system.
Dynamically Provision a GlusterFS volume
Define a storage class for the gluster provisioner
kind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata: name: glusterfs-storage-class labels:provisioner: kubernetes.io/glusterfsreclaimPolicy: Deleteparameters: resturl: "http://heketi:8080" volumetype: "replicate:3"
Make sure the resturl parameter is reporting the Heketi server and port.
Create the storage class
kubectl create -f gluster-storage-class.yamlTo make the kubernetes worker nodes able to consume GlusterFS volumes, install the gluster client on all worker nodes
yum install -y glusterfs-fuseDefine a volume claim in the gluster storage class
kind: PersistentVolumeClaimapiVersion: v1metadata: name: apache-volume-claimspec: accessModes: - ReadWriteMany resources: requests: storage: 500Mi storageClassName: glusterfs-storage-class
and an apache pod that is using that volume claim for its static html repository
apiVersion: v1kind: Podmetadata: name: apache-gluster-pod labels: name: apache-gluster-podspec: containers: - name: apache-gluster-pod image: centos/httpd:latest ports: - name: web containerPort: 80 protocol: TCP volumeMounts: - mountPath: "/var/www/html" name: html volumes: - name: html persistentVolumeClaim: claimName: apache-volume-claim dnsPolicy: ClusterFirst restartPolicy: Always
Create the storage class, the volume claim and the apache pod
kubectl create -f glusterfs-storage-class.yaml
kubectl create -f pvc-gluster.yaml
kubectl create -f apache-pod-pvc.yamlCheck the volume claim
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
apache-volume-claim Bound pvc-4af76e0f 1Gi RWX glusterfs-storage-class 7mThe volume claim is bound to a dynamically created volume on the gluster storage backend
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
pvc-4af76e0f 1Gi RWX Delete Bound apache-volume-claim glusterfs-storage-classCross check through the Heketi
heketi-cli --server http://heketi:8080 volume list
Id:7ce4d0cbc77fe36b84ca26a5e4172dbe Name:vol_7ce4d0cbc77fe36b84ca26a5e4172dbe ...In the same way, the gluster volume is dynamically removed when the claim is removed
kubectl delete pvc apache-volume-claim
kubectl get pvc,pv
No resources found.Redis benchmark
In this section, we are going to use persistent storage as a backend for a Redis server. Redis is an open source, in-memory data structure store, used as a database, cache and message broker. Redis provides different levels of on-disk persistence. Redis is famous for its performances and, therefore, we are going to run a Redis benchmark having persistence on a persistence volume.
Create a persistent volume claim
Make sure a default Storage Class is defined and create a claim for data as in the redis-data-claim.yaml configuration file
kind: PersistentVolumeClaimapiVersion: v1metadata: name: redis-data-claimspec: storageClassName: default accessModes: - ReadWriteOnce resources: requests: storage: 10Gi
Create the claim and check the dynamic volume creation and the binding
kubectl create -f redis-data-claim.yaml
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
redis-data-claim Bound pvc-eaab62e3 10Gi RWO default 1mCreate a Redis Master
Define a Redis Master deployment as in the redis-deployment.yaml configuration file
apiVersion: apps/v1beta1kind: Deploymentmetadata: name: redis-deploymentspec: replicas: 1 template: metadata: labels: name: redis role: master name: redis spec: containers: - name: redis image: kubernetes/redis:v1 env: - name: MASTER value: "true" ports: - containerPort: 6379 volumeMounts: - mountPath: /redis-master-data name: redis-data volumes: - name: redis-data persistentVolumeClaim: claimName: redis-data-claim
Define a Redis service as in the redis-service.yaml configuration file
apiVersion: v1kind: Servicemetadata: name: redisspec: ports: - port: 6379 targetPort: 6379 nodePort: 31079 name: http type: NodePort selector: name: redis
Deploy the Redis Master and create the service
kubectl create -f redis-deployment.yaml
kubectl create -f redis-service.yamlWait the Redis pod is ready
kubectl get pods -a -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-deployment-75466795f6-thtx4 1/1 Running 0 30s 10.38.5.62 kubew05To verify Redis, install the netcat utility and connect to the Redis Master
yum install -y nmap-ncat
nc -v kubew05 31079
Ncat: Version 6.40
Ncat: Connected to kubew05:31079.
ping
+PONG
set greetings "Hello from Redis!"
+OK
get greetings
$17
Hello from Redis!
logoutRun benchmark
Define a job running the Redis performances benchmark as in the redis-benchmark.yaml configuration file
apiVersion: batch/v1kind: Jobmetadata: name: redis-benchspec: template: metadata: name: bench spec: containers: - name: bench image: clue/redis-benchmark restartPolicy: Never
Create a batch job to run the benchmark
kubectl create -f redis-benchmark.yamlWait the job completes
kubectl get job
NAME DESIRED SUCCESSFUL AGE
redis-bench 1 1 48sCheck the bench pod name and display the results
kubectl get pods -a
NAME READY STATUS RESTARTS AGE
redis-bench-jgbj8 0/1 Completed 0 1m
redis-deployment-75466795f6-thtx4 1/1 Running 0 16m
kubectl logs redis-bench-jgbj8Provisioning local volumes without the Local Storage Operator
Local volumes cannot be created by dynamic provisioning. Instead, persistent volumes can be created by defining the persistent volume (PV) in an object definition. The local volume provisioner looks for any file system or block volume devices at the paths specified in the defined resource.
Manual provisioning of PVs includes the risk of potential data leaks across PV reuse when PVCs are deleted. |
Prerequisites
Local disks are attached to the OpenShift Container Platform nodes.
Procedure
Define the PV. Create a file, such as
example-pv-filesystem.yamlorexample-pv-block.yaml, with thePersistentVolumeobject definition. This resource must define the nodes and paths to the local volumes.Do not use different storage class names for the same device. Doing so will create multiple PVs.
example-pv-filesystem.yaml
apiVersion: v1kind: PersistentVolumemetadata: name: example-pv-filesystemspec: capacity: storage: 100Gi volumeMode: Filesystem (1) accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage (2) local: path: /dev/xvdf (3) nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - example-node1 The volume mode, either FilesystemorBlock, that defines the type of PVs.2 The name of the storage class to use when creating PV resources. Use a storage class that uniquely identifies this set of PVs. 3 The path containing a list of local storage devices to choose from, or a directory. You can only specify a directory with FilesystemvolumeMode.A raw block volume (
volumeMode: block) is not formatted with a file system. Use this mode only if any application running on the pod can use raw block devices.example-pv-block.yaml
apiVersion: v1kind: PersistentVolumemetadata: name: example-pv-blockspec: capacity: storage: 100Gi volumeMode: Block (1) accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage (2) local: path: /dev/xvdf (3) nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - example-node1 The volume mode, either FilesystemorBlock, that defines the type of PVs.2 The name of the storage class to use when creating PV resources. Be sure to use a storage class that uniquely identifies this set of PVs. 3 The path containing a list of local storage devices to choose from. Create the PV resource in your OpenShift Container Platform cluster. Specify the file you just created:
$ oc create -f <example-pv>.yamlVerify that the local PV was created:
Example output
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE example-pv-filesystem 100Gi RWO Delete Available local-storage 3m47s example-pv1 1Gi RWO Delete Bound local-storage/pvc1 local-storage 12h example-pv2 1Gi RWO Delete Bound local-storage/pvc2 local-storage 12h example-pv3 1Gi RWO Delete Bound local-storage/pvc3 local-storage 12h
Prepare your local disk on all workers nodes
In my case i’m using lvm to create 1 disk volume called prometheus with an ext4 filesystem on each workers nodes. This disk is mounted on /disks/local-provsionner by the Fstab at boot. You can use standard partitions also.
On each workers nodes :
$ lvcreate -n prometheus -L 60G vg0
$ mkfs.ext4 /dev/vg0/prometheus
$ vi /etc/fstab
/dev/vg0/prometheus /disks/local-provisioner/prometheus ext4 defaults 0 0
$ mkdir -p /disks/local-provisioner/prometheus
$ mount -a
Why Local-volume-static-provisionner ?
The local volume static provisioner manages the PersistentVolume lifecycle for pre-allocated disks by detecting and creating PVs for each local disk on the host, and cleaning up the disks when released. It does not support dynamic provisioning.
Hostpath vs local volume
Local persistent volume ensure that a pod with persistent data is always scheduled to the same worker node :
The biggest difference is that the Kubernetes scheduler understands which node a Local Persistent Volume belongs to. With HostPath volumes, a pod referencing a HostPath volume may be moved by the scheduler to a different node resulting in data loss. But with Local Persistent Volumes, the Kubernetes scheduler ensures that a pod using a Local Persistent Volume is always scheduled to the same node.
Deploy local-static-provisionner
Create a storage class called local-prometheus :
$ git clone https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner.git
$ cp deployment/kubernetes/example/default_example_storageclass.yaml prometheus-storageclass.yaml
$ vi prometheus-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-prometheus
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
$ kubectl apply -f prometheus-storageclass.yaml
Customize Helm local-provisioner configuration :
$ cp helm/provisioner/values.yaml values.yaml
$ vi values.yaml
[...]
classes:
- name: local-prometheus
hostDir: /disks/local-provisioner
volumeMode: Filesystem
fsType: ext4
namePattern: “*"
[...]
In this example I’m using 1 storage class but you can add other storage class for different type of configuration (hd / ssd / etc..) if needed.
Deploy local-volume-static-provisioner with Helm :
$ helm install -f values.yaml -n kube-system localprovi ./helm/provisioner/
$ kubectl get pods -n kube-system | grep localprovi
localprovi-provisioner-8klbn 1/1 Running 0 40s
localprovi-provisioner-lvchq 1/1 Running 0 40s
localprovi-provisioner-ndwjd 1/1 Running 0 40s
Check volumes discovery :
$ kubectl logs localprovi-provisioner-8klbn -n kube-systemFound new volume at host path “/disks/local-provisioner/prometheus” with capacity 63145029632, creating Local PV “local-pv-1d3f8952”, required volumeMode “Filesystem”
Created PV “local-pv-1d3f8952” for volume at “/disks/local-provisioner/prometheus”
$ kubectl get pv | grep local-prom
local-pv-1d3f8952 58Gi RWO Delete Available local-prometheus
local-pv-4de2a9e7 58Gi RWO Delete Available local-prometheus
local-pv-73241d24 58Gi RWO Delete Available local-prometheus
Configure Prometheus-operator helm values to use our new local provisionner storageclass
$ vi values.yaml
prometheus
replicas: 3storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-prometheus
accessModes: [“ReadWriteOnce”]
resources:
requests:
storage: 58Gi
$ helm install -n monitoring prometheus stable/prometheus-operator -f values.yaml
$ kubectl get pvc -n monitoring
prometheus-oper-db-0 Bound local-pv-4de2a9e7 58Gi RWO local-prometheus 40s
prometheus-oper-db-1 Bound local-pv-4de2a9e7 58Gi RWO local-prometheus 40s
prometheus-oper-db-2 Bound local-pv-4de2a9e7 58Gi RWO local-prometheus 40s
Awesome ! each replicas of Prometheus is bounded to a local Pv 🙂
If you delete a replica, the scheduler automatically reschedule the replica to the same node.
What is local volume ?
A local volume is a mounted storage device like a disk, a partition or even a simple directory. It allow us to use static PV in Kubernetes. Local volumes are stable since Kubernetes 1.14 but the local static provisionner is supported by the Kubernetes SIG.
A simple use case example
To demonstrate how to implement a dynamic local provisioner, i will use a simple common use case : adding persistent volumes to a Prometheus-operator helm deployment composed of 3 prometheus-server statefullset replicas. What we want here is that the local provisioner create 1 local volume for each replicas on each nodes and keep the pods assigned to the same node during all the lifecycle of the deployment. That mean if the pod die and is recreated, the scheduler will assign the same worker node for the pods to maintain data consistency.
Using tolerations with Local Storage Operator pods
Taints can be applied to nodes to prevent them from running general workloads. To allow the Local Storage Operator to use tainted nodes, you must add tolerations to the Pod or DaemonSet definition. This allows the created resources to run on these tainted nodes.
You apply tolerations to the Local Storage Operator pod through the LocalVolume resource
and apply taints to a node through the node specification. A taint on a node instructs the node to repel all pods that do not tolerate the taint. Using a specific taint that is not on other pods ensures that the Local Storage Operator pod can also run on that node.
Taints and tolerations consist of a key, value, and effect. As an argument, it is expressed as |
Prerequisites
The Local Storage Operator is installed.
Local disks are attached to OpenShift Container Platform nodes with a taint.
Tainted nodes are expected to provision local storage.
Procedure
To configure local volumes for scheduling on tainted nodes:
Modify the YAML file that defines the
Podand add theLocalVolumespec, as shown in the following example:apiVersion: "local.storage.openshift.io/v1" kind: "LocalVolume" metadata: name: "local-disks" namespace: "openshift-local-storage" spec: tolerations: - key: localstorage (1) operator: Equal (2) value: "localstorage" (3) storageClassDevices: - storageClassName: "localblock-sc" volumeMode: Block (4) devicePaths: (5) - /dev/xvdg1 Specify the key that you added to the node. 2 Specify the Equaloperator to require thekey/valueparameters to match. If operator isExists, the system checks that the key exists and ignores the value. If operator isEqual, then the key and value must match.3 Specify the value localof the tainted node.4 The volume mode, either FilesystemorBlock, defining the type of the local volumes.5 The path containing a list of local storage devices to choose from. Optional: To create local persistent volumes on only tainted nodes, modify the YAML file and add the
LocalVolumespec, as shown in the following example:spec: tolerations: - key: node-role.kubernetes.io/master operator: Exists
The defined tolerations will be passed to the resulting daemon sets, allowing the diskmaker and provisioner pods to be created for nodes that contain the specified taints.
Provisioning local volumes by using the Local Storage Operator
Local volumes cannot be created by dynamic provisioning. Instead, persistent volumes can be created by the Local Storage Operator. The local volume provisioner looks for any file system or block volume devices at the paths specified in the defined resource.
Prerequisites
The Local Storage Operator is installed.
You have a local disk that meets the following conditions:
It is attached to a node.
It is not mounted.
It does not contain partitions.
Procedure
Create the local volume resource. This resource must define the nodes and paths to the local volumes.
Do not use different storage class names for the same device. Doing so will create multiple persistent volumes (PVs).
Example: Filesystem
apiVersion: "local.storage.openshift.io/v1"kind: "LocalVolume"metadata: name: "local-disks" namespace: "openshift-local-storage" (1)spec: nodeSelector: (2) nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - ip-10-0-140-183 - ip-10-0-158-139 - ip-10-0-164-33 storageClassDevices: - storageClassName: "local-sc" (3) volumeMode: Filesystem (4) fsType: xfs (5) devicePaths: (6) - /path/to/device (7)1 The namespace where the Local Storage Operator is installed. 2 Optional: A node selector containing a list of nodes where the local storage volumes are attached. This example uses the node hostnames, obtained from oc get node. If a value is not defined, then the Local Storage Operator will attempt to find matching disks on all available nodes.3 The name of the storage class to use when creating persistent volume objects. The Local Storage Operator automatically creates the storage class if it does not exist. Be sure to use a storage class that uniquely identifies this set of local volumes. 4 The volume mode, either FilesystemorBlock, that defines the type of local volumes.5 The file system that is created when the local volume is mounted for the first time. 6 The path containing a list of local storage devices to choose from. 7 Replace this value with your actual local disks filepath to the LocalVolumeresourceby-id, such as/dev/disk/by-id/wwn. PVs are created for these local disks when the provisioner is deployed successfully.A raw block volume (
volumeMode: block) is not formatted with a file system. You should use this mode only if any application running on the pod can use raw block devices.Example: Block
apiVersion: "local.storage.openshift.io/v1"kind: "LocalVolume"metadata: name: "local-disks" namespace: "openshift-local-storage" (1)spec: nodeSelector: (2) nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - ip-10-0-136-143 - ip-10-0-140-255 - ip-10-0-144-180 storageClassDevices: - storageClassName: "localblock-sc" (3) volumeMode: Block (4) devicePaths: (5) - /path/to/device (6)1 The namespace where the Local Storage Operator is installed. 2 Optional: A node selector containing a list of nodes where the local storage volumes are attached. This example uses the node hostnames, obtained from oc get node. If a value is not defined, then the Local Storage Operator will attempt to find matching disks on all available nodes.3 The name of the storage class to use when creating persistent volume objects. 4 The volume mode, either FilesystemorBlock, that defines the type of local volumes.5 The path containing a list of local storage devices to choose from. 6 Replace this value with your actual local disks filepath to the LocalVolumeresourceby-id, such asdev/disk/by-id/wwn. PVs are created for these local disks when the provisioner is deployed successfully.Create the local volume resource in your OpenShift Container Platform cluster. Specify the file you just created:
$ oc create -f <local-volume>.yamlVerify that the provisioner was created and that the corresponding daemon sets were created:
$ oc get all -n openshift-local-storageExample output
NAME READY STATUS RESTARTS AGE pod/diskmaker-manager-9wzms 1/1 Running 0 5m43s pod/diskmaker-manager-jgvjp 1/1 Running 0 5m43s pod/diskmaker-manager-tbdsj 1/1 Running 0 5m43s pod/local-storage-operator-7db4bd9f79-t6k87 1/1 Running 0 14m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/local-storage-operator-metrics ClusterIP 172.30.135.36 <none> 8383/TCP,8686/TCP 14mNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGEdaemonset.apps/diskmaker-manager 3 3 3 3 3 <none> 5m43sNAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/local-storage-operator 1/1 1 1 14m NAME DESIRED CURRENT READY AGE replicaset.apps/local-storage-operator-7db4bd9f79 1 1 1 14mNote the desired and current number of daemon set processes. A desired count of
0indicates that the label selectors were invalid.Verify that the persistent volumes were created:
Example output
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE local-pv-1cec77cf 100Gi RWO Delete Available local-sc 88m local-pv-2ef7cd2a 100Gi RWO Delete Available local-sc 82m local-pv-3fa1c73 100Gi RWO Delete Available local-sc 48m
Editing the |
Installing the Local Storage Operator
The Local Storage Operator is not installed in OpenShift Container Platform by default. Use the following procedure to install and configure this Operator to enable local volumes in your cluster.
Prerequisites
Access to the OpenShift Container Platform web console or command-line interface (CLI).
Procedure
Create the
openshift-local-storageproject:$ oc adm new-project openshift-local-storageOptional: Allow local storage creation on infrastructure nodes.
You might want to use the Local Storage Operator to create volumes on infrastructure nodes in support of components such as logging and monitoring.
You must adjust the default node selector so that the Local Storage Operator includes the infrastructure nodes, and not just worker nodes.
To block the Local Storage Operator from inheriting the cluster-wide default selector, enter the following command:
$ oc annotate project openshift-local-storage openshift.io/node-selector=''
From the UI
To install the Local Storage Operator from the web console, follow these steps:
Log in to the OpenShift Container Platform web console.
Navigate to Operators → OperatorHub.
Type Local Storage into the filter box to locate the Local Storage Operator.
Click Install.
On the Install Operator page, select A specific namespace on the cluster. Select openshift-local-storage from the drop-down menu.
Adjust the values for Update Channel and Approval Strategy to the values that you want.
Click Install.
Once finished, the Local Storage Operator will be listed in the Installed Operators section of the web console.
From the CLI
Install the Local Storage Operator from the CLI.
Run the following command to get the OpenShift Container Platform major and minor version. It is required for the
channelvalue in the next
step.$ OC_VERSION=$(oc version -o yaml | grep openshiftVersion | \ grep -o '[0-9]*[.][0-9]*' | head -1)Create an object YAML file to define an Operator group and subscription for the Local Storage Operator,
such asopenshift-local-storage.yaml:Example openshift-local-storage.yaml
apiVersion: operators.coreos.com/v1kind: OperatorGroupmetadata: name: local-operator-group namespace: openshift-local-storagespec: targetNamespaces: - openshift-local-storage---apiVersion: operators.coreos.com/v1alpha1kind: Subscriptionmetadata: name: local-storage-operator namespace: openshift-local-storagespec: channel: "${OC_VERSION}" installPlanApproval: Automatic (1) name: local-storage-operator source: redhat-operators sourceNamespace: openshift-marketplace1 The user approval policy for an install plan.
Create the Local Storage Operator object by entering the following command:
$ oc apply -f openshift-local-storage.yamlAt this point, the Operator Lifecycle Manager (OLM) is now aware of the Local Storage Operator. A ClusterServiceVersion (CSV) for the Operator should appear in the target namespace, and APIs provided by the Operator should be available for creation.
Verify local storage installation by checking that all pods and the Local Storage Operator have been created:
Check that all the required pods have been created:
$ oc -n openshift-local-storage get podsExample output
NAME READY STATUS RESTARTS AGE local-storage-operator-746bf599c9-vlt5t 1/1 Running 0 19mCheck the ClusterServiceVersion (CSV) YAML manifest to see that the Local Storage Operator is available in the
openshift-local-storageproject:$ oc get csvs -n openshift-local-storageExample output
NAME DISPLAY VERSION REPLACES PHASE local-storage-operator.4.2.26-202003230335 Local Storage 4.2.26-202003230335 Succeeded
After all checks have passed, the Local Storage Operator is installed successfully.
Deleting the Local Storage Operator resources
Removing a local volume or local volume set
Occasionally, local volumes and local volume sets must be deleted. While removing the entry in the resource and deleting the persistent volume is typically enough, if you want to reuse the same device path or have it managed by a different storage class, then additional steps are needed.
The following procedure outlines an example for removing a local volume. The same procedure can also be used to remove symlinks for a local volume set custom resource. |
Prerequisites
The persistent volume must be in a
ReleasedorAvailablestate.Deleting a persistent volume that is still in use can result in data loss or corruption.
Procedure
Edit the previously created local volume to remove any unwanted disks.
Edit the cluster resource:
$ oc edit localvolume <name> -n openshift-local-storageNavigate to the lines under
devicePaths, and delete any representing unwanted disks.
Delete any persistent volumes created.
Delete any symlinks on the node.
The following step involves accessing a node as the root user. Modifying the state of the node beyond the steps in this procedure could result in cluster instability.
Create a debug pod on the node:
$ oc debug node/<node-name>Change your root directory to
/host:Navigate to the directory containing the local volume symlinks.
$ cd /mnt/openshift-local-storage/<sc-name> (1)1 The name of the storage class used to create the local volumes. Delete the symlink belonging to the removed device.
Uninstalling the Local Storage Operator
To uninstall the Local Storage Operator, you must remove the Operator and all created resources in the openshift-local-storage project.
Uninstalling the Local Storage Operator while local storage PVs are still in use is not recommended. While the PVs will remain after the Operator’s removal, |
Prerequisites
Access to the OpenShift Container Platform web console.
Procedure
Delete any local volume resources installed in the project, such as
localvolume,localvolumeset, andlocalvolumediscovery:$ oc delete localvolume --all --all-namespaces$ oc delete localvolumeset --all --all-namespaces$ oc delete localvolumediscovery --all --all-namespacesUninstall the Local Storage Operator from the web console.
Log in to the OpenShift Container Platform web console.
Navigate to Operators → Installed Operators.
Type Local Storage into the filter box to locate the Local Storage Operator.
Click the Options menu
at the end of the Local Storage Operator.
Click Uninstall Operator.
Click Remove in the window that appears.
The PVs created by the Local Storage Operator will remain in the cluster until deleted. After these volumes are no longer in use, delete them by running the following command:
Delete the
openshift-local-storageproject:$ oc delete project openshift-local-storage
Automating discovery and provisioning for local storage devices
The Local Storage Operator automates local storage discovery and provisioning. With this feature, you can simplify installation when dynamic provisioning is not available during deployment, such as with bare metal, VMware, or AWS store instances with attached devices.
Automatic discovery and provisioning is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process. |
Use the following procedure to automatically discover local devices, and to automatically provision local volumes for selected devices.
Use the |
Prerequisites
You have cluster administrator permissions.
You have installed the Local Storage Operator.
You have attached local disks to OpenShift Container Platform nodes.
You have access to the OpenShift Container Platform web console and the
occommand-line interface (CLI).
Procedure
To enable automatic discovery of local devices from the web console:
In the Administrator perspective, navigate to Operators → Installed Operators and click on the Local Volume Discovery tab.
Click Create Local Volume Discovery.
Select either All nodes or Select nodes, depending on whether you want to discover available disks on all or specific nodes.
Only worker nodes are available, regardless of whether you filter using All nodes or Select nodes.
Click Create.
A local volume discovery instance named auto-discover-devices is displayed.
To display a continuous list of available devices on a node:
Log in to the OpenShift Container Platform web console.
Navigate to Compute → Nodes.
Click the node name that you want to open. The «Node Details» page is displayed.
Select the Disks tab to display the list of the selected devices.
The device list updates continuously as local disks are added or removed. You can filter the devices by name, status, type, model, capacity, and mode.
To automatically provision local volumes for the discovered devices from the web console:
Navigate to Operators → Installed Operators and select Local Storage from the list of Operators.
Select Local Volume Set → Create Local Volume Set.
Enter a volume set name and a storage class name.
Choose All nodes or Select nodes to apply filters accordingly.
Only worker nodes are available, regardless of whether you filter using All nodes or Select nodes.
Select the disk type, mode, size, and limit you want to apply to the local volume set, and click Create.
A message displays after several minutes, indicating that the «Operator reconciled successfully.»
Alternatively, to provision local volumes for the discovered devices from the CLI:
Create an object YAML file to define the local volume set, such as
local-volume-set.yaml, as shown in the following example:apiVersion: local.storage.openshift.io/v1alpha1kind: LocalVolumeSetmetadata: name: example-autodetectspec: nodeSelector: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-0 - worker-1 storageClassName: example-storageclass (1) volumeMode: Filesystem fsType: ext4 maxDeviceCount: 10 deviceInclusionSpec: deviceTypes: (2) - disk - part deviceMechanicalProperties: - NonRotational minSize: 10G maxSize: 100G models: - SAMSUNG - Crucial_CT525MX3 vendors: - ATA - ST2000LM1 Determines the storage class that is created for persistent volumes that are provisioned from discovered devices. The Local Storage Operator automatically creates the storage class if it does not exist. Be sure to use a storage class that uniquely identifies this set of local volumes. 2 When using the local volume set feature, the Local Storage Operator does not support the use of logical volume management (LVM) devices. Create the local volume set object:
$ oc apply -f local-volume-set.yamlVerify that the local persistent volumes were dynamically provisioned based on the storage class:
Example output
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE local-pv-1cec77cf 100Gi RWO Delete Available example-storageclass 88m local-pv-2ef7cd2a 100Gi RWO Delete Available example-storageclass 82m local-pv-3fa1c73 100Gi RWO Delete Available example-storageclass 48m
Results are deleted after they are removed from the node. Symlinks must be manually removed. |
Conclusion
Local volume provisionner is a great tool for static Kubernetes cluster and allow us to not care about node affinity.
Keep in mind that this type of setup is not a good choice for autoscaling or dynamic cluster because if a dead node is replaced by a healthy node you lose all the data.
If you don’t care about losing data for exemple because you’re using a native clustered or fault tolerant technology it can work great and allow you to not care about an external volume provisioner.

