12 minute read

Kubernetes StorageClass

StorageClass

In the previous example, we used a storageClass named manual, but that does not seem to be present.

pradeep@learnk8s$ kubectl get sc
NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  12d

There is a storage class by name standard, and is the default storage class.

pradeep@learnk8s$ kubectl describe sc
Name:            standard
IsDefaultClass:  Yes
Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"labels":{"addonmanager.kubernetes.io/mode":"EnsureExists"},"name":"standard"},"provisioner":"k8s.io/minikube-hostpath"}
,storageclass.kubernetes.io/is-default-class=true
Provisioner:           k8s.io/minikube-hostpath
Parameters:            <none>
AllowVolumeExpansion:  <unset>
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>

:memo: Changing the storage class name to standard also did not help solve the Forbidden issue with the nginx.

Strangely, inside the Pod, we can access the index.html file with cat command, but curl is not working.

pradeep@learnk8s$ kubectl exec -it my-pv-pod -- /bin/bash
root@my-pv-pod:/# cat /usr/share/nginx/html/indext.html
Hello from Kubernetes storage
pradeep@learnk8s$ kubectl explain sc
KIND:     StorageClass
VERSION:  storage.k8s.io/v1

DESCRIPTION:
     StorageClass describes the parameters for a class of storage for which
     PersistentVolumes can be dynamically provisioned.

     StorageClasses are non-namespaced; the name of the storage class according
     to etcd is in ObjectMeta.Name.

FIELDS:
   allowVolumeExpansion	<boolean>
     AllowVolumeExpansion shows whether the storage class allow volume expand

   allowedTopologies	<[]Object>
     Restrict the node topologies where volumes can be dynamically provisioned.
     Each volume plugin defines its own supported topology specifications. An
     empty TopologySelectorTerm list means there is no topology restriction.
     This field is only honored by servers that enable the VolumeScheduling
     feature.

   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata	<Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   mountOptions	<[]string>
     Dynamically provisioned PersistentVolumes of this storage class are created
     with these mountOptions, e.g. ["ro", "soft"]. Not validated - mount of the
     PVs will simply fail if one is invalid.

   parameters	<map[string]string>
     Parameters holds the parameters for the provisioner that should create
     volumes of this storage class.

   provisioner	<string> -required-
     Provisioner indicates the type of the provisioner.

   reclaimPolicy	<string>
     Dynamically provisioned PersistentVolumes of this storage class are created
     with this reclaimPolicy. Defaults to Delete.

   volumeBindingMode	<string>
     VolumeBindingMode indicates how PersistentVolumeClaims should be
     provisioned and bound. When unset, VolumeBindingImmediate is used. This
     field is only honored by servers that enable the VolumeScheduling feature.

Static Binding

Here is the PersistentVolume definition. If we omit the storageClassName, PVC is net getting bound. As you see, there is a default storageClass named standard and PVC is using it by default.

pradeep@learnk8s$ cat pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv
spec:
  storageClassName: standard
  capacity:
    storage: 512m
  accessModes:
    - ReadWriteMany
  hostPath:
    path: /data/config

Create the PV from the YAML file.

pradeep@learnk8s$ kubectl create -f pv.yaml
persistentvolume/pv created

Verify the available PVs. The newly created PV should be in Available state. Currently, there are no Claims for this volume.

pradeep@learnk8s$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv     512m       RWX            Retain           Available           standard                8s

Describe the PV for addtional details.

pradeep@learnk8s$ kubectl describe pv pv
Name:            pv
Labels:          <none>
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Available
Claim:
Reclaim Policy:  Retain
Access Modes:    RWX
VolumeMode:      Filesystem
Capacity:        512m
Node Affinity:   <none>
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /data/config
    HostPathType:
Events:            <none>

Now, create a PVC with same accessModes: ReadWriteMany.

pradeep@learnk8s$ cat pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 256m

Create PVC from the YAML file.

pradeep@learnk8s$ kubectl create -f pvc.yaml
persistentvolumeclaim/pvc created

Verify the newly created PVC. It should be in Bound state. Note the STORAGECLASS column. It is using the default one named standard.

pradeep@learnk8s$ kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc    Bound    pv       512m       RWX            standard       4s

Describe the PVC for addtional details.

pradeep@learnk8s$ kubectl describe pvc
Name:          pvc
Namespace:     default
StorageClass:  standard
Status:        Bound
Volume:        pv
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      512m
Access Modes:  RWX
VolumeMode:    Filesystem
Used By:       <none>
Events:        <none>

Now, create a Pod that uses the newly defined PVC.

pradeep@learnk8s$ cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - image: nginx
    name: app
    volumeMounts:
    - mountPath: "/data/app/config"
      name: configpvc
  volumes:
  - name: configpvc
    persistentVolumeClaim:
      claimName: pvc
  restartPolicy: Never

Create the Pod from the YAML file.

pradeep@learnk8s$ kubectl create -f pod.yaml
pod/app created

Use the -o wide option to check the IP and NODE details.

pradeep@learnk8s$ kubectl get pods app -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE      NOMINATED NODE   READINESS GATES
app    1/1     Running   0          18s   10.244.1.5   k8s-m02   <none>           <none>

Describe the Pod and look for Volumes section.

pradeep@learnk8s$ kubectl describe pods app
Name:         app
Namespace:    default
Priority:     0
Node:         k8s-m02/192.168.177.30
Start Time:   Mon, 28 Feb 2022 09:25:46 +0530
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.244.1.5
IPs:
  IP:  10.244.1.5
Containers:
  app:
    Container ID:   docker://09871149e09a1da02a86a9e38ace82082def59aa38dffbf739e14f7694f0f79e
    Image:          nginx
    Image ID:       docker-pullable://nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Mon, 28 Feb 2022 09:25:50 +0530
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data/app/config from configpvc (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vfx8b (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  configpvc:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  pvc
    ReadOnly:   false
  kube-api-access-vfx8b:
    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  63s   default-scheduler  Successfully assigned default/app to k8s-m02
  Normal  Pulling    62s   kubelet            Pulling image "nginx"
  Normal  Pulled     59s   kubelet            Successfully pulled image "nginx" in 3.044922832s
  Normal  Created    59s   kubelet            Created container app
  Normal  Started    59s   kubelet            Started container app

Login to the Container and create a file in the /data/app/config folder, which is the container MountPath.

pradeep@learnk8s$ kubectl exec -it app -- /bin/sh
# cd /data/app/config
# ls -l
total 0
# touch hello.txt
# echo "Testing PV, PVC, and SC in K8S!" > hello.txt
# exit

Login to the minikube node and check if you are able to view the hello.txt file in the host. On the control plane, there is no such file in the /data/config folder.

pradeep@learnk8s$ minikube ssh -p k8s
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ls /data/config/
$ ls -la /data/config/
total 8
drwxr-xr-x 2 root root 4096 Feb 28 03:33 .
drwxr-xr-x 3 root root 4096 Feb 28 03:33 ..
$ exit
logout

On the other node, k8s-m02, there is the hello.txt file. This is because, the app pod got created in this node.

pradeep@learnk8s$ minikube ssh -n k8s-m02 -p k8s
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ls /data/config
hello.txt
$ ls -la /data/config/
total 12
drwxr-xr-x 2 root root 4096 Feb 28 03:58 .
drwxr-xr-x 3 root root 4096 Feb 28 03:55 ..
-rw-r--r-- 1 root root   32 Feb 28 03:58 hello.txt
$ cat /data/config/hello.txt
Testing PV, PVC, and SC in K8S!
$ exit
logout

We can also view the contents of the hello.txt file.

Verify the PV, PVC, and SC details.

pradeep@learnk8s$ kubectl get pv,pvc,sc
NAME                  CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS   REASON   AGE
persistentvolume/pv   512m       RWX            Retain           Bound    default/pvc   standard                9m35s

NAME                        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc   Bound    pv       512m       RWX            standard       8m17s

NAME                                             PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  12d

Describe the PV one more time, after the Pod creation, to see if there is any change. One change that we notice is that the Claim is showing default/pvc which was empty earlier. Also, Staus changed to Bound from Available.

pradeep@learnk8s$ kubectl describe pv
Name:            pv
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Bound
Claim:           default/pvc
Reclaim Policy:  Retain
Access Modes:    RWX
VolumeMode:      Filesystem
Capacity:        512m
Node Affinity:   <none>
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /data/config
    HostPathType:
Events:            <none>

Similarly describe the PVC and look for the changes. The Used By value has changed to app now, showing that it is used by this Pod.

pradeep@learnk8s$ kubectl describe pvc
Name:          pvc
Namespace:     default
StorageClass:  standard
Status:        Bound
Volume:        pv
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      512m
Access Modes:  RWX
VolumeMode:    Filesystem
Used By:       app
Events:        <none>

Also, look at the StorageClass description. Verify that, IsDefaultClass: Yes .

pradeep@learnk8s$ kubectl describe sc
Name:            standard
IsDefaultClass:  Yes
Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"labels":{"addonmanager.kubernetes.io/mode":"EnsureExists"},"name":"standard"},"provisioner":"k8s.io/minikube-hostpath"}
,storageclass.kubernetes.io/is-default-class=true
Provisioner:           k8s.io/minikube-hostpath
Parameters:            <none>
AllowVolumeExpansion:  <unset>
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>

Now delete the pod.

pradeep@learnk8s$ kubectl delete pod app
pod "app" deleted

After deleting the pod, check if the data is persistent or not. You should still be able to view the contents of the hello.txt file on the k8s-m02 node, even after deleting the Pod, confirming that the storage is persistent now.

pradeep@learnk8s$ minikube ssh -n k8s-m02 -p k8s
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ cd /data/config/
$ cat hello.txt
Testing PV, PVC, and SC in K8S!
$ exit
logout

Dynamic Binding

Define a new storage class named pradeep-sc-demo in the kube-system namespace using a sample YAML file.

pradeep@learnk8s$ cat sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  namespace: kube-system
  name: pradeep-sc-demo
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "false"
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
provisioner: k8s.io/minikube-hostpath

Create the storage class from this YAML file and verify that it was created.

pradeep@learnk8s$ kubectl create -f sc.yaml
storageclass.storage.k8s.io/pradeep-sc-demo created

Now you should see two storageclasses, both with same PROVISIONER details. Also, not the default value next to the standard class, which is not present for the newly defined storage class.

pradeep@learnk8s$ kubectl get storageclass
NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
pradeep-sc-demo      k8s.io/minikube-hostpath   Delete          Immediate           false                  47s
standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  12d

Let us modify the PVC definition file to specify this pradeep-sc-demo StorageClass, instead of the standard. First, let us delete the existing PVC.

pradeep@learnk8s$ kubectl delete pvc pvc
persistentvolumeclaim "pvc" deleted

Modify the PVC definition, to include the new storage class.

pradeep@learnk8s$ cat pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc
spec:
  storageClassName: pradeep-sc-demo
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 256m

Create the PVC from the modified definition file.

pradeep@learnk8s$ kubectl create -f pvc.yaml
persistentvolumeclaim/pvc created

Verify the Status of the new PVC.

pradeep@learnk8s$ kubectl get pvc
NAME   STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS      AGE
pvc    Pending                                      pradeep-sc-demo   64s

It is still showing Pending, to find out why let us describe it.

pradeep@learnk8s$ kubectl describe pvc
Name:          pvc
Namespace:     default
StorageClass:  pradeep-sc-demo
Status:        Pending
Volume:
Labels:        <none>
Annotations:   volume.beta.kubernetes.io/storage-provisioner: k8s.io/minikube-hostpath
               volume.kubernetes.io/storage-provisioner: k8s.io/minikube-hostpath
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode:    Filesystem
Used By:       <none>
Events:
  Type    Reason                Age               From                         Message
  ----    ------                ----              ----                         -------
  Normal  ExternalProvisioning  2s (x8 over 76s)  persistentvolume-controller  waiting for a volume to be created, either by external provisioner "k8s.io/minikube-hostpath" or manually created by system administrator

We can see that the PVC is waiting for a matching volume to be created.

Our existing PV is using the standard storageclass which is not matching with this PVC definition. So let us delete the existing PV and modify it to specify the new SC.

pradeep@learnk8s$ kubectl delete pv pv
persistentvolume "pv" deleted

Here is the modified PV definition.

pradeep@learnk8s$ cat pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv
spec:
  storageClassName: pradeep-sc-demo
  capacity:
    storage: 512m
  accessModes:
    - ReadWriteMany
  hostPath:
    path: /data/config
pradeep@learnk8s$ kubectl get pv,pvc
NAME                  CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS      REASON   AGE
persistentvolume/pv   512m       RWX            Retain           Bound    default/pvc   pradeep-sc-demo            15s

NAME                        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS      AGE
persistentvolumeclaim/pvc   Bound    pv       512m       RWX            pradeep-sc-demo   6m29s

Now that the storageclass is matching, both the PV and PVC are in Bound State.

Create the app pod again using the same manifest file that we used earlier.

pradeep@learnk8s$ cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - image: nginx
    name: app
    volumeMounts:
    - mountPath: "/data/app/config"
      name: configpvc
  volumes:
  - name: configpvc
    persistentVolumeClaim:
      claimName: pvc
  restartPolicy: Never
pradeep@learnk8s$ kubectl create -f pod.yaml
pod/app created
pradeep@learnk8s$ kubectl get pods app -o wide
NAME   READY   STATUS    RESTARTS   AGE     IP           NODE      NOMINATED NODE   READINESS GATES
app    1/1     Running   0          2m19s   10.244.1.6   k8s-m02   <none>           <none>

Shell into the Pod and create a file in the mounted directory.

pradeep@learnk8s$ kubectl exec app -it -- /bin/sh
# cd /data/app/config
# ls -l
total 4
-rw-r--r-- 1 root root 32 Feb 28 03:58 hello.txt
# touch HiAgain.txt
# echo "Hello again from K8s PV,PVC, and SC!" > HiAgain.txt
# cat HiAgain.txt
Hello again from K8s PV,PVC, and SC!
# exit

Becuase of persistent nature, we still see the old hello.txt file in this new container. We have created another file called HiAgain.txt with some sample text.

pradeep@learnk8s$ kubectl get pv,pvc,sc
NAME                  CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS      REASON   AGE
persistentvolume/pv   512m       RWX            Retain           Bound    default/pvc   pradeep-sc-demo            6m43s

NAME                        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS      AGE
persistentvolumeclaim/pvc   Bound    pv       512m       RWX            pradeep-sc-demo   12m

NAME                                             PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/pradeep-sc-demo      k8s.io/minikube-hostpath   Delete          Immediate           false                  18m
storageclass.storage.k8s.io/standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  12d

Let us delete the Pod again and verify the files.

pradeep@learnk8s$ kubectl delete pod app
pod "app" deleted
pradeep@learnk8s$ minikube ssh -n k8s-m02 -p k8s
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ cd /data/config/
$ ls
HiAgain.txt  hello.txt
$ ls -la
total 16
drwxr-xr-x 2 root root 4096 Feb 28 04:50 .
drwxr-xr-x 3 root root 4096 Feb 28 03:55 ..
-rw-r--r-- 1 root root   37 Feb 28 04:50 HiAgain.txt
-rw-r--r-- 1 root root   32 Feb 28 03:58 hello.txt
$ cat HiAgain.txt
Hello again from K8s PV,PVC, and SC!
$ cat hello.txt
Testing PV, PVC, and SC in K8S!
$ exit
logout

This confirms that, the storage is persistent again (even after Pod deletion).

Back to Top ↑