DEV Community

Janak Shrestha
Janak Shrestha

Posted on

Day 60: Persistent Volumes in Kubernetes

The Nautilus DevOps team is working on a Kubernetes template to deploy a web application on the cluster. There are some requirements to create/use persistent volumes to store the application code, and the template needs to be designed accordingly. Please find more details below:

  1. Create a PersistentVolume named as pv-devops. Configure the spec as storage class should be manual, set capacity to 3Gi, set access mode to ReadWriteOnce, volume type should be hostPath and set path to /mnt/itadmin (this directory is already created, you might not be able to access it directly, so you need not to worry about it).

  2. Create a PersistentVolumeClaim named as pvc-devops. Configure the spec as storage class should be manual, request 3Gi of the storage, set access mode to ReadWriteOnce.

  3. Create a pod named as pod-devops, mount the persistent volume you created with claim name pvc-devops at document root of the web server, the container within the pod should be named as container-devops using image httpd with latest tag only (remember to mention the tag i.e httpd:latest).

  4. Create a node port type service named web-devops using node port 30008 to expose the web server running within the pod.


Understanding Persistent Volumes and Claims

What is a Persistent Volume?

A Persistent Volume (PV) is a piece of storage in the cluster that has been provisioned by an administrator. It is a cluster resource, similar to a node, and is independent of any individual pod.

What is a Persistent Volume Claim?

A Persistent Volume Claim (PVC) is a request for storage by a user. It is similar to a pod in that a pod consumes node resources, while a PVC consumes PV resources.

The Relationship Between PV and PVC

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Persistent Volume Workflow                              │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  Administrator: Creates PersistentVolume (PV)                        │ │
│  │  - Defines storage capacity                                          │ │
│  │  - Sets access modes                                                 │ │
│  │  - Specifies storage type (hostPath, NFS, etc.)                    │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  User: Creates PersistentVolumeClaim (PVC)                          │ │
│  │  - Requests storage capacity                                         │ │
│  │  - Specifies access modes                                            │ │
│  │  - Binds to a matching PV                                            │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  Pod: Uses PVC to mount storage                                     │ │
│  │  - References PVC in volumes section                                 │ │
│  │  - Mounts volume at specified path                                   │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

What We Will Build

We are going to deploy a complete web application with persistent storage:

Component Specification
PersistentVolume pv-devops – 3Gi, manual storage class, hostPath
PersistentVolumeClaim pvc-devops – 3Gi request
Pod pod-devops – httpd web server
Service web-devops – NodePort on 30008

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Kubernetes Cluster                                 │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  PersistentVolume: pv-devops                                          │ │
│  │  - Capacity: 3Gi                                                     │ │
│  │  - Access Mode: ReadWriteOnce                                        │ │
│  │  - Storage Class: manual                                             │ │
│  │  - HostPath: /mnt/itadmin                                            │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  PersistentVolumeClaim: pvc-devops                                   │ │
│  │  - Requests: 3Gi                                                     │ │
│  │  - Access Mode: ReadWriteOnce                                        │ │
│  │  - Storage Class: manual                                             │ │
│  │  - Bound to: pv-devops                                               │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  Pod: pod-devops                                                      │ │
│  │  ┌──────────────────────────────────────────────────────────────────┐ │ │
│  │  │  Container: container-devops                                    │ │ │
│  │  │  Image: httpd:latest                                            │ │ │
│  │  │  Port: 80                                                       │ │ │
│  │  │  Volume: pvc-storage → pvc-devops                               │ │ │
│  │  │  Mount: /usr/local/apache2/htdocs                               │ │ │
│  │  └──────────────────────────────────────────────────────────────────┘ │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  Service: web-devops                                                  │ │
│  │  - Type: NodePort                                                    │ │
│  │  - Selector: app=httpd                                               │ │
│  │  - Port: 80                                                          │ │
│  │  - NodePort: 30008                                                   │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                    │                                         │
│                                    ▼                                         │
│                    http://<node-ip>:30008                                  │
└─────────────────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Implementation

Step 1: Create the PersistentVolume

Create a file named pv-devops.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-devops
spec:
  storageClassName: manual
  capacity:
    storage: 3Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /mnt/itadmin
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • storageClassName: manual – Identifies the storage class
  • capacity.storage: 3Gi – Allocates 3 gigabytes of storage
  • accessModes: ReadWriteOnce – Can be mounted as read-write by a single node
  • hostPath.path: /mnt/itadmin – Uses the host node's filesystem

Step 2: Create the PersistentVolumeClaim

Create a file named pvc-devops.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-devops
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The storageClassName must match the PV's storage class
  • The accessModes must match the PV's access modes
  • resources.requests.storage – Requests 3Gi of storage

Step 3: Create the Pod

Create a file named pod-devops.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-devops
  labels:
    app: httpd
spec:
  containers:
  - name: container-devops
    image: httpd:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: pvc-storage
      mountPath: /usr/local/apache2/htdocs
  volumes:
  - name: pvc-storage
    persistentVolumeClaim:
      claimName: pvc-devops
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • labels.app: httpd – Used by the service for selection
  • volumeMounts.mountPath – The document root for httpd
  • persistentVolumeClaim.claimName – References the PVC

Step 4: Create the Service

Create a file named service-devops.yaml:

apiVersion: v1
kind: Service
metadata:
  name: web-devops
spec:
  type: NodePort
  selector:
    app: httpd
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30008
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • type: NodePort – Exposes the service on a static port
  • selector.app: httpd – Routes traffic to pods with this label
  • nodePort: 30008 – The port on each node

Step 5: Apply the Configurations

kubectl apply -f pv-devops.yaml
kubectl apply -f pvc-devops.yaml
kubectl apply -f pod-devops.yaml
kubectl apply -f service-devops.yaml
Enter fullscreen mode Exit fullscreen mode

Output:

persistentvolume/pv-devops created
persistentvolumeclaim/pvc-devops created
pod/pod-devops created
service/web-devops created
Enter fullscreen mode Exit fullscreen mode

Verification

Step 1: Verify the PersistentVolume

kubectl get pv
Enter fullscreen mode Exit fullscreen mode

Output:

NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS
pv-devops   3Gi        RWO            Retain           Bound    default/pvc-devops   manual
Enter fullscreen mode Exit fullscreen mode

Step 2: Verify the PersistentVolumeClaim

kubectl get pvc
Enter fullscreen mode Exit fullscreen mode

Output:

NAME         STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS
pvc-devops   Bound    pv-devops   3Gi        RWO            manual
Enter fullscreen mode Exit fullscreen mode

Step 3: Verify the Pod

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

Output:

NAME         READY   STATUS    RESTARTS   AGE
pod-devops   1/1     Running   0          84s
Enter fullscreen mode Exit fullscreen mode

Step 4: Verify the Service

kubectl get services
Enter fullscreen mode Exit fullscreen mode

Output:

NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.43.0.1      <none>        443/TCP        32m
web-devops   NodePort    10.43.193.63   <none>        80:30008/TCP   90s
Enter fullscreen mode Exit fullscreen mode

Step 5: Verify the Endpoints

kubectl get endpoints web-devops
Enter fullscreen mode Exit fullscreen mode

Output:

NAME         ENDPOINTS      AGE
web-devops   10.22.0.9:80   2m35s
Enter fullscreen mode Exit fullscreen mode

Accessing the Application

The application can now be accessed at:

http://<node-ip>:30008
Enter fullscreen mode Exit fullscreen mode

Default Httpd Page

When accessing the URL, you should see the default Apache HTTP Server welcome page, confirming that the web server is running and the persistent volume is mounted correctly.


Key Concepts

Access Modes

Access Mode Description
ReadWriteOnce (RWO) Volume can be mounted as read-write by a single node
ReadOnlyMany (ROX) Volume can be mounted as read-only by many nodes
ReadWriteMany (RWX) Volume can be mounted as read-write by many nodes

Storage Classes

Storage classes provide a way to define different types of storage:

  • manual – For manually provisioned storage
  • standard – For dynamically provisioned storage
  • Custom classes – For specific storage providers (AWS EBS, GCE PD, etc.)

Reclaim Policies

Policy Description
Retain Volume is retained after PVC deletion (manual cleanup required)
Recycle Volume is scrubbed and made available again (deprecated)
Delete Volume is deleted when PVC is deleted

HostPath Volume

hostPath mounts a directory from the host node's filesystem. This is useful for:

  • Development and testing
  • Accessing host system files
  • Single-node clusters

For production environments, consider using network-attached storage like NFS, or cloud provider storage solutions.


Troubleshooting

PVC Stuck in Pending State

# Check the PVC status
kubectl get pvc

# View detailed PVC information
kubectl describe pvc pvc-devops

# Check for available PVs
kubectl get pv

# Verify storage class and access modes match
Enter fullscreen mode Exit fullscreen mode

Pod Fails to Start

# Check pod status
kubectl get pods

# View pod details
kubectl describe pod pod-devops

# Check pod logs
kubectl logs pod-devops
Enter fullscreen mode Exit fullscreen mode

Service Not Accessible

# Check service status
kubectl get services

# Check endpoints
kubectl get endpoints web-devops

# Verify NodePort
kubectl get service web-devops -o yaml | grep nodePort
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Use Appropriate Storage Classes

Choose the right storage class for your workload:

  • manual – For testing and development
  • standard – For production with dynamic provisioning
  • Custom classes – For specific performance or availability requirements

2. Set Resource Requests

Always set appropriate storage requests:

  • Request only what you need
  • Monitor usage to adjust requests

3. Use Reclaim Policies Wisely

  • Retain – For important data that should not be accidentally deleted
  • Delete – For temporary or disposable data

4. Consider PersistentVolume Snapshots

For production workloads, consider using volume snapshots for backup and disaster recovery.

5. Monitor Storage Usage

Use monitoring tools to track storage usage and capacity.


Summary

In this challenge, we successfully:

  1. Created a PersistentVolume with 3Gi capacity, hostPath storage, and manual storage class
  2. Created a PersistentVolumeClaim requesting 3Gi of storage
  3. Deployed a pod running the httpd web server with the PVC mounted at the document root
  4. Exposed the application using a NodePort service on port 30008

This deployment pattern is foundational for stateful applications in Kubernetes. Understanding how to manage persistent storage is essential for running production workloads like databases, content management systems, and any application that requires data persistence.


Useful Commands Reference

Command Purpose
kubectl get pv List all PersistentVolumes
kubectl get pvc List all PersistentVolumeClaims
kubectl describe pv pv-devops Detailed PV information
kubectl describe pvc pvc-devops Detailed PVC information
kubectl get pods --show-labels List pods with labels
kubectl get endpoints web-devops Check service endpoints
kubectl describe service web-devops Detailed service information
kubectl logs pod-devops View pod logs
kubectl get nodes -o wide Get node IP addresses

Top comments (0)