Kubernetes Storage

Kubernetes storage — PersistentVolumes, claims, storage classes, and volume mounts for k3s.

Storage Classes

List and inspect storage classes
kubectl get storageclass
kubectl describe storageclass local-path    # k3s default
kubectl get storageclass -o jsonpath='{.items[*].metadata.name}'
k3s ships with local-path as the default StorageClass. PersistentVolumes are provisioned at /var/lib/rancher/k3s/storage/ on the node.

PersistentVolumeClaims

Create a PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: local-path
List and inspect PVCs
kubectl get pvc -A
kubectl describe pvc app-data
kubectl get pvc -o custom-columns='NAME:.metadata.name,STATUS:.status.phase,SIZE:.spec.resources.requests.storage'

PersistentVolumes

List and inspect PVs
kubectl get pv
kubectl describe pv <pv-name>
Reclaim policies
# Retain  — PV persists after PVC deletion (manual cleanup)
# Delete  — PV and underlying storage deleted with PVC
# Recycle — deprecated, do not use
kubectl get pv -o custom-columns='NAME:.metadata.name,RECLAIM:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase'

Mount Volumes in Pods

Use a PVC in a deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myapp:v1.0
        volumeMounts:
        - name: data
          mountPath: /var/lib/app/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: app-data

Ephemeral Storage

emptyDir — shared scratch space between containers
volumes:
- name: scratch
  emptyDir: {}
  # emptyDir: { sizeLimit: "1Gi" }   # optional size limit
hostPath — mount node filesystem (use sparingly)
volumes:
- name: host-logs
  hostPath:
    path: /var/log
    type: Directory
hostPath breaks portability and bypasses scheduler constraints. Acceptable on single-node k3s; avoid in multi-node clusters.

ConfigMaps and Secrets as Volumes

Mount a ConfigMap as files
kubectl create configmap app-config --from-file=config.yaml
volumes:
- name: config
  configMap:
    name: app-config
Mount a Secret as files
volumes:
- name: tls-certs
  secret:
    secretName: my-tls
    defaultMode: 0400              # read-only for owner

Troubleshooting

Debug storage issues
kubectl get events --field-selector reason=ProvisioningFailed
kubectl describe pvc app-data | awk '/Events:/,0'
kubectl get pv,pvc -A -o wide
# Check local-path provisioner logs
kubectl logs -n kube-system -l app=local-path-provisioner