PersistentVolume for Python Logs

Create a PersistentVolume for Python logs in this hands-on Kubernetes for Python Developers tutorial. Learn how to store logs durably and explore next steps.

Focus: create a persistentvolume for python logs

Sponsored

Your Python service is writing logs like there's no tomorrow — and then the pod restarts. In seconds, those logs vanish into the void. That's the pain this lesson solves: by creating a Kubernetes PersistentVolume for Python logs, you ensure that critical application output survives pod restarts, rescheduling, and even node failures, giving you durable, inspection-ready logs when you need them most.

The problem this lesson solves

Containers are ephemeral by design. When your Python app writes to /tmp or even /var/log/myapp.log, that data lives inside the pod's ephemeral filesystem — which is wiped the moment the pod dies, restarts, or is evicted. In Kubernetes, pod restarts are routine: deployments roll out new images, nodes drain for maintenance, and liveness probes kill unresponsive containers. Every one of those events turns yesterday's error traces into ghost logs.

This isn't just about debugging after an incident. Audit trails, security events, and business metrics often live side by side with app logs, and losing them can have real consequences. The solution is persistence — storing logs on durable storage that outlives the pod. Kubernetes gives you that with PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs).

Why Python specifically? Python's logging module writes to plain files just fine, but the container filesystem disappears with the pod. Pairing a standard Python logging FileHandler with a mounted PV transforms your logs from throwaway to durable — no code changes required.

Core concept / mental model

Think of a PersistentVolume as a shared, network-attached hard drive that Kubernetes can plug into any pod. A PersistentVolumeClaim is the request for storage — like asking the cluster's storage orchestrator for '50 Gi of fast disk.' The volume itself is provisioned independently of any pod, so even if a pod is deleted, the volume (and the logs on it) remain.

To visualize it, imagine a parking garage: - The PersistentVolume is a reserved parking spot — the actual resource (disk space, network storage) exists independently. - The PersistentVolumeClaim is your ticket that reserves that spot. - The Pod is the car that parks in it — it can come and go, but the spot remains.

In Kubernetes terms, the lifecycle works like this: 1. An admin (or dynamic provisioner) creates a PV bound to physical storage. 2. A PVC is created to claim that PV. 3. A pod mounts the PVC at a specific path. 4. The Python app writes logs to that path. 5. When the pod dies, the PVC and PV remain — and a new pod can mount the same volume and read the old logs.

How it works step by step

Creating a PersistentVolume for Python logs involves four layers, each with a specific job:

  1. PersistentVolume (PV) — a cluster-level resource that points to actual storage (e.g., an NFS share, EBS volume, or local disk). This is the physical resource.
  2. PersistentVolumeClaim (PVC) — a namespaced request for storage. Kubernetes binds it to a matching PV automatically.
  3. Pod / Deployment — the workload that consumes the PVC via a volume mount.
  4. Python app — writes logs to the mounted path, often using logging.FileHandler.

A typical flow for log persistence looks like this:

  1. Create a PV YAML that defines storage capacity, access mode, and storage class.
  2. Create a PVC that requests storage (size, access mode, storage class).
  3. Deploy your Python pod with a volume mount referencing the PVC.
  4. Your Python code writes log files to /var/log/app/ inside the mount.
  5. After a pod restart, the logs are still there because the PV persists.

For this lesson, we'll use a hostPath PV — perfect for a single-node test cluster (like minikube or kind). It maps a node's filesystem path to a volume, giving you real persistence with zero cloud costs.

Hands-on walkthrough

Let's build a working example. Step 1: define a PersistentVolume that stores logs on the node's disk.

Create a file pv-log.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-python-logs
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/data/python-logs

Apply it:

kubectl apply -f pv-log.yaml
kubectl get pv

Output:

NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-python-logs   1Gi        RWO            Retain           Available                       5s

Step 2: create a PVC that claims the volume.

Create pvc-log.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-python-logs
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

Apply it:

kubectl apply -f pvc-log.yaml
kubectl get pvc

Output:

NAME              STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-python-logs   Bound    pv-python-logs   1Gi        RWO                           6s

The status Bound confirms the PVC matched your PV.

Step 3: create a Python deployment that mounts the volume. We'll use a simple logging script that writes a log line every 5 seconds.

Create app.py:

import logging
import time

logging.basicConfig(
    filename='/var/log/app/app.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

while True:
    logging.info("Python log entry from persistent volume")
    time.sleep(5)

Create a Dockerfile:

FROM python:3.11-slim
COPY app.py /app.py
RUN mkdir -p /var/log/app
CMD ["python", "/app.py"]

Build and push the image (or use a local registry for testing). Then create deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-log-pod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-log-pod
  template:
    metadata:
      labels:
        app: python-log-pod
    spec:
      containers:
      - name: logger
        image: your-registry/python-logger:1.0
        volumeMounts:
        - name: log-volume
          mountPath: /var/log/app
      volumes:
      - name: log-volume
        persistentVolumeClaim:
          claimName: pvc-python-logs

Deploy and verify:

kubectl apply -f deployment.yaml
kubectl get pods

Wait until the pod is running, then check the logs from inside the pod:

kubectl exec deploy/python-log-pod -- cat /var/log/app/app.log

Expected output (your timestamps will differ):

2025-01-15 14:32:01,213 - INFO - Python log entry from persistent volume
2025-01-15 14:32:06,215 - INFO - Python log entry from persistent volume

Now the moment of truth: delete the pod and watch the logs survive.

kubectl delete pod python-log-pod-<random-id>
kubectl get pods  # a new pod gets created automatically

Once the new pod is ready, check the logs again:

kubectl exec deploy/python-log-pod -- tail -n 2 /var/log/app/app.log

You'll see the log entries from before the pod was deleted. Congratulations — you've created a PersistentVolume for Python logs!

Compare options / when to choose what

Your choice of PV type depends on your environment. Here's a comparison:

Storage Type Best For Pros Cons
hostPath Single-node dev/CI (minikube, kind) Simple, no cloud dependencies Doesn't work multi-node; data tied to one node
NFS On-prem clusters, shared logs across nodes Shared access, familiar protocol Requires NFS server setup; performance overhead
Cloud volumes (EBS, AzureDisk, GCE PD) Production in the cloud Managed, durable, resizeable Requires cloud credentials, cost per GB
Local SSDs High-performance local logs Very fast Data not replicated; pod must stay on the same node
Longhorn / Rook Kubernetes-native storage Self-managed, replicated Operational overhead

For Python log volumes, start with hostPath in dev, then move to a cloud volume or NFS for multi-node production. The PVC interface stays the same — only the PV spec changes.

Troubleshooting & edge cases

The most common issue is a PVC stuck in Pending status. This means no PV matched your request. Check your PV's accessModes and capacity. For instance, asking for ReadWriteOnce (RWO) but your PV only supports ReadWriteMany (RWX) will fail. Also, PVCs are namespaced — you must create the PVC in the same namespace as your pod. If your PV has a storageClassName set and your PVC doesn't, they won't match. For hostPath, use storageClassName: "" on both if you have a default StorageClass in your cluster.

Another gotcha: the mount path must exist in the container image. My Dockerfile uses RUN mkdir -p /var/log/app to ensure it's there. If you mount to a non-existent path, the container will fail to start with Error: stat /var/log/app: no such file or directory.

Reference: every object needs a valid apiVersion. For PV, PVC, and Deployment, use v1. Don't use extensions/v1beta1 — that's deprecated and will break on modern clusters.

What you learned & what's next

You now understand the core idea: a PersistentVolume provides durable storage independent of pod lifecycles, and a PersistentVolumeClaim binds your pod to that storage. You've applied this by creating a PV for Python logs, wiring it through a PVC, and confirming logs survive pod deletion. You can scale this pattern to other stateful data like databases or file uploads.

Next in the track, you'll learn how to use StatefulSets and Headless Services for stateful Python applications — such as databases that need stable network identities. That's the natural progression from PVCs, since StatefulSets work hand-in-hand with persistent storage.

Keep experimenting: try changing the reclaim policy to Delete and see what happens to the PV, or mount the PV in two pods at once (with RWX) to test shared log access.

Practice recap

Create a second PVC with accessModes: ReadWriteMany and an NFS-backed PV, then deploy two Python pods writing to the same log directory to verify shared access. Alternatively, change the reclaim policy to Delete and observe how deleting the PVC also removes the PV — then restore the Retain policy and practice recovering a released volume.

Common mistakes

  • Forgetting to set accessModes that match between PV and PVC (e.g., PVC requests RWO but PV only has RWX), causing the PVC to stay Pending.
  • Mounting to a directory that doesn't exist in the container image, leading to a pod crash with a mount error.
  • Using hostPath in a multi-node cluster without node affinity, so the pod lands on a different node and can't see the logs.
  • Forgetting to set storageClassName to an empty string when using a manual PV in a cluster with a default StorageClass, causing the PVC to try dynamic provisioning instead.

Variations

  1. Use dynamic provisioning with a StorageClass and cloud volumes (e.g., standard on GKE, gp2 on EKS) to have PVs created automatically when you apply a PVC.
  2. Use NFS or a shared filesystem as the PV backend to support ReadWriteMany access for multiple pods writing to the same log directory.
  3. Instead of mounting an entire volume, use a subPath to mount only a subdirectory of the PV for finer control.

Real-world use cases

  • Persist application logs for a Python web service so they survive pod restarts during rolling deployments and crash events.
  • Store audit trail logs from a payment processing API in a durable volume to meet compliance and forensic requirements.
  • Share log files across multiple replicas of a Python background worker using a ReadWriteMany NFS volume for centralized log aggregation.

Key takeaways

  • Containers are ephemeral; logs written to the container filesystem vanish when the pod restarts or is deleted.
  • A PersistentVolume (PV) represents physical storage at the cluster level, independent of pod lifecycle.
  • A PersistentVolumeClaim (PVC) binds to a PV and can be mounted into a pod's filesystem.
  • Using a PVC in a Deployment ensures new pods automatically inherit access to the same stored logs.
  • For production, choose cloud volumes or NFS over hostPath for multi-node durability and shared access.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.