Kubernetes PersistentVolume and PersistentVolumeClaim to storage data

Introduction

In previous articles, I've guided you through using Kubernetes (K8s) to create resources from Docker images, resulting in stateless applications. This means no data is retained during usage, and restarting resources resets the application to its initial state.

If you're familiar with Docker, you might know about mounting volumes to save data externally and reattach it to a Docker container as needed. In Kubernetes, you can achieve a similar result using PersistentVolume (PV) and PersistentVolumeClaim (PVC) to build stateful applications.

Using PV and PVC in Kubernetes is crucial for real-world applications because they allow your data to persist across frequent deployments and restarts. Applications often face crashes or restarts due to issues, and having persistent data ensures seamless operation.

  • PersistentVolume (PV): A storage resource provisioned by an administrator. It exists independently of the pod lifecycle
  • PersistentVolumeClaim (PVC): A request for storage by a user. It binds to a PV and is attached to a pod for data storage.

In Summary:

  1. Create a PV with a specified volume configuration.
  2. Create a PVC to bind to the PV.
  3. Configure your Pod to mount the volume from the PVC for data storage.

Preparing the Docker Image

You can use the following code block to build a NodeJS TypeScript application for this guide:

import express from 'express'
import * as fs from 'fs'

const port = 3000
const title = 'This is NodeJS Typescript Application'
const dataPath = 'data/data.json'
const app = express()

const getData = (): {title: string}[] => {
const fileExists = fs.existsSync(dataPath)
if (fileExists) {
const data = fs.readFileSync(dataPath, 'utf8')
return JSON.parse(data)
}
  fs.writeFileSync(dataPath, '[]')
return []
}

app
.use(express.json())
.get('/', (_, res) => {
res.send((process?.env?.TITLE ?? title) + '! Current time is ' + Date.now())
})
.get('/todos', (_, res) => {
res.json(getData())
})
.post('/addTodo', (req, res) => {
const title = req.body.title
const data = [...getData(), {title}]
const json = JSON.stringify(data, null, 2)

fs.writeFileSync(dataPath, json)
res.json({ok: true})
})
.listen(port, () => {
console.log(`Server is running http://localhost:${port}`)
})

Note: Make sure to create a 'data' folder to mount the volume when starting the Pod.

Next, build the Docker image and push it to Google Container Registry or Docker Hub. You can also use an existing Docker image with similar functionality.


Practice K8s

To create the resources, you'll need to set up a cluster first. You can use Google Kubernetes Engine (GKE) or a local Kubernetes cluster with Kind.

After that, create a `deployment.yml` file with the following content:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-name
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "2Gi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-name
spec:
replicas: 1
selector:
matchLabels:
app: label-name
template:
metadata:
labels:
app: label-name
spec:
restartPolicy: Always
containers:
- name: express-ts
image: express-ts # replace with your image
ports:
- containerPort: 3000
name: deployment-port
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "500Mi"
cpu: "500m"
volumeMounts:
- name: volumen-name
mountPath: /app/data # path to store data
volumes:
- name: volumen-name
persistentVolumeClaim:
claimName: pvc-name
---
apiVersion: v1
kind: Service
metadata:
name: service-name
spec:
type: LoadBalancer
selector:
app: label-name
ports:
- protocol: TCP
port: 80 # port service
targetPort: deployment-port # port pod

  • PersistentVolumeClaim: When you create a PersistentVolumeClaim (PVC), it will automatically bind to a PersistentVolume (PV) with a storage capacity of "2Gi".
  • I've already written a guide on how to create a Deployment and Service, which you can find here.
  • In the Deployment, there's an additional field called `volumes` which references the PVC defined above. The `volumeMounts` field specifies the volume name and the `mountPath`, which is the path defined when building the Docker Image.


To apply and create the resource:

kubectl apply -f deployment.yml

Note: I've defined all resources in a single file for simplicity, but in practice, you should separate each resource into individual YAML files for better management.


Checking resource provision


Checking if PV and PVC have been created


Deployment results



Next, restart the deployment to test if the volume mounts successfully so that no data is lost.

kubectl rollout restart deployment.apps/deployment-name


Conclusion

The above is a simple example of using PV (PersistentVolume) and PVC (PersistentVolumeClaim) for data storage. In this example, I stored the data in a JSON file, but in a real-world scenario, you would likely store data in a database. The concept remains the same, and you can try implementing it yourself.

Happy coding!

Comments

Popular posts from this blog

Kubernetes Practice Series

NodeJS Practice Series

Docker Practice Series

React Practice Series

Sitemap

Setting up Kubernetes Dashboard with Kind

Deploying a NodeJS Server on Google Kubernetes Engine

DevOps Practice Series

A Handy Guide to Using Dynamic Import in JavaScript

Using Kafka with Docker and NodeJS