Kubernetes basics · Lesson 11 of 12 · about 25 minutes

Storage: keeping data when pods restart

A container forgets everything when it restarts. Volumes keep data around for as long as you need it, and PersistentVolumes keep it even after the pod is gone.

By the end you'll know

  • Why files written inside a container disappear
  • How long data lasts in a container, in an emptyDir, and in a PersistentVolume
  • How a pod asks for storage, and who creates it
  • What deletes your data for good, so you can avoid it

The problem: containers forget

When a container restarts, it starts fresh from its image. Anything it wrote to its own files since it started is gone. For most apps that's fine, even good: a web server or an API keeps nothing it can't rebuild. But some apps exist to keep data: a database, a file upload service, a metrics store. For those, Kubernetes needs a way to keep files around longer than one container.

That's what volumes are: storage that's attached to a pod from outside the container. The question is always how long the data needs to last.

Three lifetimes

Where the data isContainer restartsPod is deleted or replacedGood for
The container's own filesLostLostNothing you want to keep
An emptyDir volumeKeptLostScratch space and caches that can be rebuilt
A PersistentVolumeKeptKeptData that must survive: databases, uploads

An emptyDir is an empty folder created when the pod starts, and deleted when the pod goes away. It belongs to the pod, not the container, so it survives a container restart. Remember that Deployments replace pods all the time: during updates, when a node is drained, when you delete one. Every replacement starts with a new, empty emptyDir.

A PersistentVolume lives separately from any pod. Pods come and go; the volume stays, and the next pod picks up where the last one left off.

How a pod asks for storage

Storage on a cloud is a real disk somewhere, and a pod shouldn't need to know which one. So Kubernetes splits it into three pieces:

  • PersistentVolumeClaim (PVC): the pod's request. "I need 1 gigabyte, that one node can read and write." You write this.
  • StorageClass: how this cluster creates storage. On DigitalOcean it creates a block storage disk; on AWS, an EBS disk; on kind, a folder on your computer. Someone sets this up once per cluster.
  • PersistentVolume (PV): the actual storage. The StorageClass creates one to match each claim, and binds the two together.
Think of renting a storage unit. The PVC is your rental form: "one small unit, please." The StorageClass is the storage company, which knows where its buildings are and how to set up a unit. The PV is the unit itself, with your things inside. You can move house as often as you like (that's the pod being replaced); the unit and its contents stay put until you end the rental.
How a pod gets persistent storageA pod mounts the claim notes-data at the folder /data. The claim asks for 1 gigabyte, read and write by one node. The StorageClass standard creates a matching PersistentVolume, the actual storage, and the claim and the volume are bound together. The pod only names its claim; where the storage comes from is the cluster's job.Podmounts notes-data at /data1. usesPersistentVolumeClaimnotes-data1Gi, read-write by one node2. asksStorageClass: standardknows how to create storagein this cluster3. createsPersistentVolumepvc-3f2a...the actual storage4. bound togetherThe pod only names its claim. Where the storage comes from is the cluster's job.
You write the claim. The StorageClass creates a PersistentVolume to match it, and the two are bound. The pod never deals with the disk directly.

Two more words you'll see in a claim:

  • Access mode: who can use the volume at once. The common one is ReadWriteOnce: read and write, from one node at a time. ReadWriteMany lets pods on many nodes share it, but only some kinds of storage support that.
  • Reclaim policy: what happens to the storage when you delete the claim. Storage created by a StorageClass usually has Delete: the claim goes, and the data goes with it. Retain keeps the data until someone removes it by hand.

Try it: data that outlives its pod

Start a cluster:

BASH
kind create cluster --name learn
kubectl get storageclass

kind comes with one StorageClass, standard, marked (default). Its PROVISIONER, rancher.io/local-path, creates each volume as a folder on the node. That's the local-path-storage namespace you saw in Lesson 10. A claim that doesn't name a StorageClass gets the default one.

Save this as claim.yaml:

YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: notes-data
spec:
  accessModes:
    - ReadWriteOnce        # read and write, from one node at a time
  resources:
    requests:
      storage: 1Gi         # how much space to ask for
BASH
kubectl apply -f claim.yaml
kubectl get pvc

STATUS: Pending. That's normal here, not an error. kind's StorageClass waits until a pod actually uses the claim before it creates the storage, so the storage ends up on the same node as the pod. Now give it a pod. Save this as notes.yaml:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: notes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: notes
  template:
    metadata:
      labels:
        app: notes
    spec:
      containers:
        - name: app
          image: busybox:1.36
          command: ["sleep", "3600"]
          volumeMounts:
            - name: data
              mountPath: /data          # the volume appears here inside the container
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: notes-data       # use the claim, not a specific disk
BASH
kubectl apply -f notes.yaml
kubectl get pvc
kubectl get pv

Once the pod is running, the claim is Bound, and there's a PersistentVolume with a generated name like pvc-3f2a... that nobody had to write. That's the StorageClass at work. Now write something to the volume:

BASH
kubectl exec deploy/notes -- sh -c 'echo "written before the pod was deleted" > /data/note.txt'

Delete the pod. The Deployment replaces it with a brand-new one:

BASH
kubectl delete pod -l app=notes
kubectl get pods

When the new pod is Running, read the file:

BASH
kubectl exec deploy/notes -- cat /data/note.txt

Still there. Go further, and delete the whole Deployment, then create it again:

BASH
kubectl delete deployment notes
kubectl apply -f notes.yaml

Give the new pod a few seconds to start, then:

BASH
kubectl exec deploy/notes -- cat /data/note.txt

Still there. The claim and its volume don't belong to any pod or Deployment, so they outlive both.

Deleting the claim deletes the data

kubectl delete pvc notes-data with the default Delete reclaim policy removes the PersistentVolume and everything on it, with no "are you sure?". The same goes for deleting a namespace (Lesson 10), which deletes every claim inside it. Before deleting a claim on a real cluster, check its reclaim policy with kubectl get pv, and make sure there's a backup.

In my real cluster

My cluster has no PersistentVolumes at all, and that's on purpose. The data that matters, CivicGrid's database, lives in Supabase, a managed database service outside the cluster. Everything inside the cluster can be rebuilt from Git, so there's nothing on a disk to lose or back up.

My Redis cache only holds rate-limit counters. It uses a small emptyDir with Redis's own saving turned off: if the pod is replaced, the counters reset, and that's fine.

My Prometheus, which keeps the cluster's metrics, also uses an emptyDir, and it taught me what "survives a container restart" really means. Prometheus kept running out of memory while reading back its own log at startup, and crashed. Because the emptyDir survived each crash, the log was still there for the next start, a little bigger each time. It crashed 2,012 times over nine days before I noticed, and fixed it by giving it more memory and having it keep less data. With a PersistentVolume the same thing would have happened. Persistence keeps your data, including the data that's causing the problem.

The trade-off of emptyDir for Prometheus is that its metric history resets whenever the pod is replaced. I only keep three days of history anyway, so I accept that. If I needed more, I'd give it a claim using DigitalOcean's StorageClass, do-block-storage.

Break it on purpose

Ask for a StorageClass that doesn't exist. Save this as bad-claim.yaml:

YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fast-data
spec:
  storageClassName: fast-ssd   # no StorageClass by this name
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
BASH
kubectl apply -f bad-claim.yaml
kubectl get pvc
kubectl describe pvc fast-data

fast-data stays Pending, and this time it will stay that way forever. The Events at the bottom of describe say why: the StorageClass fast-ssd wasn't found. Any pod that uses this claim would also wait in Pending, because Kubernetes won't start a pod whose storage doesn't exist.

When a claim is stuck in Pending, check the StorageClass name against kubectl get storageclass. And remember the first Pending you saw: on kind, a claim waits for its first pod, so a claim with no pod yet isn't a problem. describe tells you which case you're in.

Clean up

BASH
kind delete cluster --name learn

Check yourself

  1. An app writes a file to its container's own filesystem, then the container restarts. What happens to the file?

  2. A pod writes to an emptyDir volume. Then the Deployment replaces the pod. What happens to the data?

  3. Which object does a pod's spec name, to get persistent storage?

  4. On kind, you create a claim and kubectl get pvc shows Pending. No pod uses it yet. What's going on?

  5. A claim is stuck in Pending, and kubectl describe pvc says its StorageClass wasn't found. What do you check?

  6. You delete a PersistentVolumeClaim whose volume has the reclaim policy Delete. What happens to the data?

Key terms

  • Volume: storage attached to a pod from outside the container.
  • emptyDir: a volume that starts empty and lasts as long as its pod. Survives container restarts, not pod replacement.
  • PersistentVolume (PV): storage that lives separately from any pod.
  • PersistentVolumeClaim (PVC): a request for storage: how much, and how it may be used. Pods name claims, not volumes.
  • StorageClass: how a cluster creates storage on request. Most clusters have a default one.
  • Access mode: who can use a volume at once. ReadWriteOnce (one node) is the most common.
  • Reclaim policy: what happens to the storage when its claim is deleted: Delete or Retain.

Go deeper