Kubernetes persistent volumes

Kubernetes persistent volumes

Applications deployed on a Kubernetes cluster run inside containers. As a consequence, their file system is that of the container, which means that if the container is removed, the data it contained is lost.

To save data in a more permanent manner, Kubernetes offers persistent volumes (PV). Those volumes can be in various forms but the one of the simplest consist in mounting a directory of the node's file system into the containers.

A persistent volume achieving such purpose can be created using the following manifest

apiVersion: v1
kind: PersistentVolume
metadata:
  name: image-upload-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/kubernetes/image_upload"

Here, /mnt/kubernetes is the directory that will be mounted in the pods and 50Gi is its maximum allowed size.

Deployments can then claim this volume using a persistent volume claim, or PVC. Here is an example of a deployment that claims the PV defined previously.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: image-upload-pvc
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: image-upload-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: image-upload-deployment
  template:
    metadata:
      labels:
        app: image-upload-deployment
    spec:
      volumes:
      - name: image-upload-storage
        persistentVolumeClaim:
          claimName: image-upload-pvc
      containers:
      - name: image-upload-container
        image: 192.168.179.25:5000/image-upload
        imagePullPolicy: Always
        ports:
        - containerPort: 8888
        volumeMounts:
        - mountPath: "/usr/share/pv"
          name: image-upload-storage
---
apiVersion: v1
kind: Service
metadata:
  labels:
    run: image-upload-service
  name: image-upload-service
spec:
  ports:
  - port: 8888
    nodePort: 30115
  selector:
    app: image-upload-deployment
  type: LoadBalancer

Note that a PV can be claimed by only one PVC

Here is an example NodeJS app that allows the user to upload files on the PV:

const express = require('express')
const path = require('path')
const formidable = require('formidable')
const mv = require('mv');

const app = express()
const port = 4455

const MOUNT_PATH = "/usr/share/pv"

app.get('/', (req, res) => res.sendFile(__dirname, "index.html")))

app.post('/upload', (req, res) => {
  var form = new formidable.IncomingForm();
  form.parse(req, (err, fields, files) => {
    let myFile = files.myFile
    var oldpath = myFile.path;
    var newpath = path.join(MOUNT_PATH, myFile.name);

    mv(oldpath, newpath, (err) => {
      if (err) return res.send(err);
      res.send("Upload complete")
    });

  });
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Note: mv is used instead of fs.rename because the latter cannot move files onto another device

Note 2: /usr/share/pv is used as mounting point. /usr/app, where the app is located, does not seem to work