Lesson 5.1: Volumes and Persistent Volumes (PV and PVC)


In Kubernetes, a Volume is a way to persist data across container restarts and share data between containers in a pod. Unlike container filesystems, which are ephemeral and tied to the lifecycle of the container, volumes provide a mechanism to store data outside the container. This is particularly useful for stateful applications like databases, caches, and file storage systems.

emptyDir Volume

In the redis.yml file, an emptyDir volume is used to store Redis data:

[root@master kubernetes]# cat redis.yml 
apiVersion: v1
kind: Pod
metadata:
  name: redis-pod
spec: 
  containers: 
  - image: redis 
    name: redis 
    volumeMounts:
    - name: redis-storage
      mountPath: /data/redis
  volumes: 
  - name: redis-storage
    emptyDir: {}
 
[root@master kubernetes]# kubectl apply -f redis.yml 
pod/redis-pod created
  • Volume Name: redis-storage
  • Mount Path: /data/redis
  • Type: emptyDir
  • Behavior: The data stored in /data/redis is temporary and will be deleted when the pod is removed.
[root@master kubernetes]# kubectl exec -it redis-pod -- sh 
# pwd      
/data/redis
# cat hello.txt 
Hello Kubernetes !
# 
# ps -aux 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
redis          1  0.4  0.1 138496  3328 ?        Ssl  22:16   0:25 redis-server *:6379
root         224  0.0  0.0   2316   512 pts/1    Ss   23:56   0:00 sh
root         235  0.0  0.2   8012  3456 pts/1    R+   23:57   0:00 ps -aux
# 
# kill -9 1 
# 
# cat hello.txt
Hello Kubernetes !
# 
# ps -aux 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
redis          1  0.4  0.1 138496  3072 ?        Ssl  22:16   0:25 redis-server *:6379
root         224  0.0  0.0   2316   640 pts/1    Ss   23:56   0:00 sh
root         237  0.0  0.2   8012  3584 pts/1    R+   23:58   0:00 ps -aux

PersistentVolume (PV) and PersistentVolumeClaim (PVC)

In the pv.yml, pvc.yml, and pod.yml files, a PersistentVolume and PersistentVolumeClaim are used to provide persistent storage to an Nginx pod:

PersistentVolume (PV):

[root@master kubernetes]# cat pv.yml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: standard
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/home/kubernetes/storage-drive"
  • Volume Name: task-pv-volume
  • Storage Class: standard
  • Capacity: 1Gi
  • Access Mode: ReadWriteOnce (RWO)
  • Type: hostPath (local storage on the node)

PersistentVolumeClaim (PVC):

[root@master kubernetes]# cat pvc.yml 
apiVersion: v1
kind: PersistentVolumeClaim 
metadata:
  name: task-pv-claim
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce 
  resources:
    requests:
      storage: 500Mi
  • Claim Name: task-pv-claim
  • Storage Class: standard
  • Requested Storage: 500Mi
  • Access Mode: ReadWriteOnce

Pod Using PVC:

[root@master kubernetes]# cat pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage
  • Volume Name: task-pv-storage
  • Mount Path: /usr/share/nginx/html
  • PVC: task-pv-claim
[root@master kubernetes]# kubectl apply -f pv.yml 
persistentvolume/task-pv-volume created
 
[root@master kubernetes]# kubectl apply -f pvc.yml 
persistentvolumeclaim/task-pv-claim created
 
[root@master kubernetes]# kubectl apply -f pod.yml 
pod/task-pv-pod created
 
[root@master kubernetes]# kubectl get pv 
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
task-pv-volume   1Gi        RWO            Retain           Bound    default/task-pv-claim   standard       <unset>                          11s
 
[root@master kubernetes]# kubectl get pvc
NAME            STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
task-pv-claim   Bound    task-pv-volume   1Gi        RWO            standard       <unset>                 10s
 
[root@master kubernetes]# kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
task-pv-pod   1/1     Running   0          9s

Key Points from the Example

  • emptyDir:

    • Used in the redis-pod to store Redis data temporarily.
    • Data is lost when the pod is deleted.
  • PersistentVolume (PV) and PersistentVolumeClaim (PVC):

    • Used in the task-pv-pod to provide persistent storage for Nginx.
    • The PV (task-pv-volume) is backed by a hostPath on the node.
    • The PVC (task-pv-claim) requests 500Mi of storage from the PV.
    • The pod mounts the PVC at /usr/share/nginx/html.
  • Binding:

    • The PVC (task-pv-claim) is bound to the PV (task-pv-volume) because they share the same storageClassName (standard).
  • Persistence:

    • Data written to /usr/share/nginx/html in the task-pv-pod will persist even if the pod is deleted, as long as the PV and PVC exist.

Example of PV and PVC with local-storage

# Create an index.html file on your Node
[root@master pv]# docker exec -it dev-worker bash 
root@dev-worker:/# cat /mnt/disks/ssd1/index.html 
<h1>Welcome to SSD DISK 1 </h1>
[root@master pv]# cat pv1.yml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - dev-worker
[root@master pv]# cat pvc1.yml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-name
spec:
  storageClassName: local-storage
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
[root@master pv]# cat pod.yml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: mypd
  volumes:
  - name: mypd
    persistentVolumeClaim:
      claimName: pvc-name
[root@master pv]# cat svc.yml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
[root@master pv]# kubectl get pv 
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS    VOLUMEATTRIBUTESCLASS   REASON   AGE
example-pv   1Gi        RWO            Retain           Bound    default/pvc-name   local-storage   <unset>                          25m
 
[root@master pv]# kubectl get pvc
NAME       STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS    VOLUMEATTRIBUTESCLASS   AGE
pvc-name   Bound    example-pv   1Gi        RWO            local-storage   <unset>                 25m
 
[root@master pv]# kubectl get svc 
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP        10d
nginx-service   NodePort    10.96.134.181   <none>        80:32483/TCP   16m
 
[root@master pv]# kubectl get pods 
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          17m
 
[root@master pv]# kubectl get nodes -owide 
NAME                STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION           CONTAINER-RUNTIME
dev-control-plane   Ready    control-plane   10d   v1.32.2   172.18.0.5    <none>        Debian GNU/Linux 12 (bookworm)   5.14.0-390.el9.aarch64   containerd://2.0.2
dev-worker          Ready    <none>          10d   v1.32.2   172.18.0.3    <none>        Debian GNU/Linux 12 (bookworm)   5.14.0-390.el9.aarch64   containerd://2.0.2
dev-worker2         Ready    <none>          10d   v1.32.2   172.18.0.2    <none>        Debian GNU/Linux 12 (bookworm)   5.14.0-390.el9.aarch64   containerd://2.0.2
 
[root@master pv]# curl 172.18.0.3:32483
<h1>Welcome to SSD DISK 1 </h1>
All systems normal

© 2025 2023 Sanjeeb KC. All rights reserved.