> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lab Solution Persistent Volumes and Persistent Volume Claims optional

> Shows how to persist a Pod's application logs in Kubernetes using hostPath and PersistentVolume plus PersistentVolumeClaim, including creation, mounting, and reclaim policy implications.

In this lesson we'll persist application logs from a Pod so they survive Pod restarts and deletions. The application writes logs to `/log/app.log` inside the container. We'll:

* Confirm the `webapp` Pod is running and inspect its logs.
* Demonstrate two persistence approaches:
  * A direct `hostPath` volume mounted into the Pod (quick proof-of-concept).
  * A `PersistentVolume` (PV) backed by a `hostPath` and a `PersistentVolumeClaim` (PVC) that the Pod consumes (recommended for controlled lifecycle).

***

## 1) Confirm the Pod and view logs

Verify the `webapp` Pod is Running and read the application log:

```bash theme={null}
# confirm Pod status
kubectl get pod webapp

# view the application log inside the Pod
kubectl exec webapp -- cat /log/app.log
```

Example output (truncated):

```text theme={null}
[2022-04-18 21:44:51,766] INFO in event-simulator: USER1 logged out
[2022-04-18 21:44:52,767] INFO in event-simulator: USER1 is viewing page1
[2022-04-18 21:44:53,768] WARNING in event-simulator: USER5 Failed to Login as the account is locked due to MANY FAILED ATTEMPTS.
...
[2022-04-18 21:45:15,797] INFO in event-simulator: USER3 logged out
```

Describe the Pod to confirm there are no volumes currently configured for application logs:

```bash theme={null}
kubectl describe pod webapp
```

Relevant excerpt (showing only the default `kube-api-access` volume mounted — no log volume yet):

```text theme={null}
Mounts:
  /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-lmntb (ro)

Volumes:
  kube-api-access-lmntb:
    Type: Projected
    TokenExpirationSeconds: 3607
```

Because the log file lives inside the container filesystem (`/log/app.log`) and no persistent volume is configured, deleting the Pod will delete the logs.

***

## 2) Quick proof-of-concept: mount a hostPath on the Pod

To validate that mounting a host directory preserves logs on the node, edit the Pod to add a `hostPath` volume and mount it to `/log` inside the container.

Using `kubectl edit pod webapp` (or export → modify → replace), add a `volumeMount` and a `hostPath` volume:

Container `volumeMounts`:

```yaml theme={null}
volumeMounts:
  - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    name: kube-api-access-lmntb
    readOnly: true
  - mountPath: /log
    name: log-volume
```

Under `spec.volumes` add the `hostPath`:

```yaml theme={null}
volumes:
  - name: log-volume
    hostPath:
      path: /var/log/webapp
  - name: kube-api-access-lmntb
    projected:
      defaultMode: 420
      sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
```

Save/apply the edit. If `kubectl edit` is inconvenient, export, modify, and replace the Pod manifest, or delete and recreate the Pod with the new spec.

After the Pod is recreated, check the host node to verify logs are being written to the hostPath:

```bash theme={null}
# on the node where the Pod is running
ls /var/log/webapp
cat /var/log/webapp/app.log
```

You should see the same application log lines in `/var/log/webapp/app.log`. This confirms the `hostPath` mount works, but note:

* hostPath mounts are node-local and tied to the node's filesystem.
* They are not managed with Kubernetes reclaim semantics and are not ideal for multi-Pod sharing or for declarative lifecycle management.

<Callout icon="warning" color="#FF6B6B">
  Using `hostPath` bypasses Kubernetes storage abstractions. It can introduce security and portability issues (node-specific paths, permission mismatches, and potential data coupling to a single node). Use for short-lived demos or troubleshooting; prefer PV/PVC for production.
</Callout>

***

## 3) Create a PersistentVolume (PV) backed by a hostPath

To manage the storage lifecycle declaratively, create a PersistentVolume that references a host directory (we'll use `/pv/log` for the PV's hostPath). Example `pv.yaml`:

```yaml theme={null}
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-log
spec:
  capacity:
    storage: 100Mi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /pv/log
```

Create the PV and verify:

```bash theme={null}
kubectl create -f pv.yaml
kubectl get pv
```

Expected output (example):

```text theme={null}
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   AGE
pv-log   100Mi      RWX            Retain           Available           <none>        3s
```

Notes:

* This PV advertises 100Mi and access mode `ReadWriteMany` (RWX) with reclaim policy `Retain`.
* A hostPath-based PV is node-local; although it advertises RWX, the underlying directory is only accessible on the node where it exists. It does not provide true multi-node RWX sharing.

***

## 4) Create a PersistentVolumeClaim (PVC) and bind it

Create a PVC that requests storage compatible with the PV:

`pvc.yaml`:

```yaml theme={null}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim-log-1
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 50Mi
```

Create and check the PVC and PV binding:

```bash theme={null}
kubectl create -f pvc.yaml
kubectl get pvc
kubectl get pv
```

Expected binding output:

```text theme={null}
# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    AGE
pv-log   100Mi      RWX            Retain           Bound    default/claim-log-1      5m

# kubectl get pvc
NAME         STATUS   VOLUME   CAPACITY   ACCESS MODES   AGE
claim-log-1  Bound    pv-log   100Mi      RWX            26s
```

If the PVC is `Pending`, check for:

* access mode mismatches (PVC requests modes not supported by PV)
* storage class mismatches
* insufficient capacity

<Callout icon="lightbulb" color="#1CB2FE">
  Make sure `accessModes` in the PVC are compatible with the PV. A PVC's requested accessModes must be supported by the PV (the PV must include every mode the PVC requests). For example, a PVC requesting `ReadWriteOnce` (RWO) will not bind to a PV that only advertises `ReadWriteMany` (RWX); the PV must include the requested mode.
</Callout>

***

## 5) Update the Pod to consume the PVC

Now update the Pod to consume the PVC instead of directly using a `hostPath`. The container `volumeMounts` remain the same (still mount to `/log`):

Container `volumeMounts`:

```yaml theme={null}
volumeMounts:
  - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    name: kube-api-access-lmntb
    readOnly: true
  - mountPath: /log
    name: log-volume
```

Replace the `volumes` entry with the `persistentVolumeClaim` reference:

```yaml theme={null}
volumes:
  - name: log-volume
    persistentVolumeClaim:
      claimName: claim-log-1
  - name: kube-api-access-lmntb
    projected:
      defaultMode: 420
      sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
```

Apply the Pod changes (edit, replace or recreate). Example from the demo:

```bash theme={null}
kubectl replace --force -f /tmp/kubectl-edit-xxxxx.yaml
# or edit to trigger Pod recreation with the PVC mount
```

After the Pod is running and using `claim-log-1`, the application writes logs to `/log/app.log` inside the Pod and those writes appear on the node at the PV hostPath `/pv/log/app.log`:

```bash theme={null}
# on the node
ls /pv/log
cat /pv/log/app.log
```

Example log excerpt in `/pv/log/app.log`:

```text theme={null}
[2022-04-18 22:01:50,986] INFO in event-simulator: USER4 is viewing page2
[2022-04-18 22:01:51,986] INFO in event-simulator: USER1 is viewing page2
...
[2022-04-18 22:02:14,012] INFO in event-simulator: USER4 is viewing page2
```

This confirms the Pod uses the PV via the PVC and logs are persisted on the node at `/pv/log`.

***

## 6) Reclaim policy and deletion behavior

Check the PV reclaim policy and current status:

```bash theme={null}
kubectl get pv pv-log
```

Example:

```text theme={null}
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM
pv-log   100Mi      RWX            Retain           Bound    default/claim-log-1
```

With `persistentVolumeReclaimPolicy: Retain`:

* Deleting the PVC does not automatically delete underlying data.
* When the PVC is removed, the PV transitions to `Released` (binding removed but data preserved).
* The administrator must manually clean up the data or make the PV available again.

If you try deleting the PVC while it is still referenced by a Pod, PVC deletion can get stuck in `Terminating` due to the PVC protection finalizer. Example:

```text theme={null}
kubectl get pvc
NAME          STATUS        VOLUME   CAPACITY   ACCESS MODES
claim-log-1   Terminating   pv-log   100Mi      RWX
```

Resolve this by first deleting the Pod that references the PVC:

```bash theme={null}
kubectl delete pod webapp
```

After the Pod is removed, PVC deletion will complete (if previously requested). The PV then moves to `Released` and must be handled manually if you want to reuse or remove the data.

***

## Quick comparison: hostPath vs PV/PVC

| Resource | Use Case | Notes |
| - | - | - |
| `hostPath` mount on Pod | Fast proof-of-concept for node-local persistence | Simpler, node-specific, not managed by Kubernetes reclaim semantics; security risks |
| PV backed by `hostPath` + PVC | Declarative lifecycle and reuse across workloads | Provides controlled binding and reclaim semantics, but hostPath PVs remain node-local (advertised RWX doesn't guarantee multi-node access) |

***

## Useful commands reference

| Action | Command |
| - | - |
| Check Pod status | `kubectl get pod webapp` |
| Read app log inside Pod | `kubectl exec webapp -- cat /log/app.log` |
| Describe Pod | `kubectl describe pod webapp` |
| Create PV | `kubectl create -f pv.yaml` |
| Create PVC | `kubectl create -f pvc.yaml` |
| Get PVs and PVCs | `kubectl get pv`, `kubectl get pvc` |
| Replace Pod manifest (force recreate) | `kubectl replace --force -f pod.yaml` |
| Delete Pod | `kubectl delete pod webapp` |

***

## Summary

* The application originally wrote logs to the container filesystem at `/log/app.log`. Without a volume, logs are lost when the Pod is removed.
* Mounting a `hostPath` directly into a Pod demonstrates node-local persistence but lacks Kubernetes-managed lifecycle control and portability.
* Creating a PersistentVolume (even backed by `hostPath`) and a matching PersistentVolumeClaim, then mounting that PVC into the Pod, provides persistent storage that survives Pod recreation and offers declarative lifecycle semantics.
* Ensure PVC `accessModes` are compatible with the PV. HostPath PVs are node-local even when they advertise RWX.
* `persistentVolumeReclaimPolicy: Retain` preserves data after PVC deletion; manual cleanup or reclamation is required.

***

## Links and references

* [Kubernetes Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
* [Kubernetes PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims)
* [Kubernetes hostPath Volume](https://kubernetes.io/docs/concepts/storage/volumes/#hostpath)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/cka-certification-course-certified-kubernetes-administrator/module/883f0aa9-3aa9-45a6-bd26-3a87ded1e00e/lesson/3c8f7c4f-f459-44dc-ae0a-af927dd42e79" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.