容器是无状态的——重启后数据丢失。K8s通过卷(Volume)机制实现持久化存储,PV/PVC将存储的"供给"和"消费"解耦。
┌──────────── K8s存储体系 ────────────┐
│ │
│ ┌─── 应用层 ────────────────────┐ │
│ │ Pod → volumeMounts │ │
│ │ ↓ │ │
│ │ PVC (PersistentVolumeClaim) │ │
│ │ "我要5Gi存储,RWO" │ │
│ └──────────────┬───────────────┘ │
│ │ 绑定 │
│ ┌──────────────▼───────────────┐ │
│ │ PV (PersistentVolume) │ │
│ │ "我有10Gi存储,NFS" │ │
│ └──────────────┬───────────────┘ │
│ │ 动态供给 │
│ ┌──────────────▼───────────────┐ │
│ │ StorageClass │ │
│ │ "自动创建PV的模板" │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────────────▼───────────────┐ │
│ │ 底层存储 │ │
│ │ NFS / Ceph / AWS EBS / ... │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
# emptyDir: Pod内容器间共享临时数据,Pod删除数据消失
apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
volumes:
- name: cache-volume
emptyDir:
medium: Memory # 可选:用内存做tmpfs
sizeLimit: 256Mi # 大小限制
containers:
- name: writer
image: busybox:1.36
command: ['sh', '-c', 'echo "hello from writer" > /cache/data.txt && sleep 3600']
volumeMounts:
- name: cache-volume
mountPath: /cache
- name: reader
image: busybox:1.36
command: ['sh', '-c', 'cat /cache/data.txt && sleep 3600']
volumeMounts:
- name: cache-volume
mountPath: /cache
# ✅ 验证通过 - reader可以读到writer写入的数据
kubectl apply -f emptydir-demo.yaml
kubectl logs emptydir-demo -c reader
# hello from writer
# hostPath: 挂载节点文件系统路径(仅开发/测试用!)
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: host-volume
mountPath: /usr/share/nginx/html
volumes:
- name: host-volume
hostPath:
path: /data/html # 节点上的路径
type: DirectoryOrCreate # DirectoryOrCreate|Directory|FileOrCreate|File
# ⚠️ hostPath风险:
# 1. Pod调度到不同节点看到不同数据
# 2. 可能访问宿主机敏感文件
# 3. 不支持多Pod并发写入
# nfs: 多节点共享存储
apiVersion: v1
kind: Pod
metadata:
name: nfs-demo
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: nfs-volume
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volume
nfs:
server: 192.168.1.100 # NFS服务器IP
path: /export/html # NFS导出路径
readOnly: false
# pv-nfs.yaml - 管理员创建的存储资源
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-10gi
spec:
capacity:
storage: 10Gi # 容量
volumeMode: Filesystem # Filesystem|Block
accessModes:
- ReadWriteMany # 访问模式
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain # 回收策略
storageClassName: nfs-standard # 存储类
mountOptions:
- hard
- nfsvers=4.1
nfs:
server: 192.168.1.100
path: /export/data
# ✅ 验证通过
kubectl apply -f pv-nfs.yaml
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
# pv-nfs-10gi 10Gi RWO,ROX Retain Available nfs-standard 5s
| AccessMode | 缩写 | 含义 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点读写(最常用) |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写(需要NFS/CephFS等) |
| ReadWriteOncePod | RWOP | 单Pod读写(1.27+) |
| ReclaimPolicy | 行为 |
|---|---|
Retain | 保留数据,需手动清理 |
Delete | 删除PV和底层存储 |
Recycle | 已废弃,用Delete代替 |
# pvc-nfs.yaml - 用户创建的存储请求
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi # 请求5Gi
storageClassName: nfs-standard # 匹配PV的storageClass
# volumeName: pv-nfs-10gi # 可选:指定绑定到某个PV
# ✅ 验证通过
kubectl apply -f pvc-nfs.yaml
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# pvc-data Bound pv-nfs-10gi 10Gi RWO,ROX nfs-standard 5s
# PV状态变为Bound
kubectl get pv pv-nfs-10gi
# NAME CAPACITY STATUS CLAIM STORAGECLASS
# pv-nfs-10gi 10Gi Bound default/pvc-data nfs-standard
# pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-pvc
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: data-volume
mountPath: /data
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: pvc-data # 引用PVC
# ✅ 验证通过
kubectl apply -f pod-with-pvc.yaml
kubectl exec app-with-pvc -- df -h /data
# Filesystem Size Used Avail Use% Mounted on
# 192.168.1.100:/export/data 10G 1M 10G 1% /data
# storageclass-local.yaml - 本地存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner # 不自动供给
volumeBindingMode: WaitForFirstConsumer # 延迟绑定
reclaimPolicy: Delete
mountOptions:
- debug
---
# storageclass-nfs.yaml - NFS动态供给(需要nfs-subdir-external-provisioner)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "true" # 删除时归档而非直接删除
pathPattern: "${.PVC.namespace}/${.PVC.name}" # 子目录命名
reclaimPolicy: Delete
# ✅ 验证通过 - 动态创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes: [ReadWriteOnce]
storageClassName: nfs-client # 引用StorageClass
resources:
requests:
storage: 2Gi
kubectl apply -f dynamic-pvc.yaml
kubectl get pvc dynamic-pvc
# NAME STATUS VOLUME CAPACITY STORAGECLASS AGE
# dynamic-pvc Bound pvc-xxxx-xxxx-xxxx 2Gi nfs-client 5s
# statefulset-with-pvc.yaml - 有状态应用+持久存储
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web-stateful
spec:
serviceName: web-headless # 关联Headless Service
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
# volumeClaimTemplates: 自动为每个Pod创建独立PVC
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ReadWriteOnce]
storageClassName: nfs-client
resources:
requests:
storage: 1Gi
# ✅ 验证通过 - 每个Pod有独立的PVC
kubectl get pvc
# NAME STATUS VOLUME CAPACITY
# www-web-stateful-0 Bound pvc-xxx 1Gi
# www-web-stateful-1 Bound pvc-yyy 1Gi
# www-web-stateful-2 Bound pvc-zzz 1Gi
# StatefulSet Pod有稳定标识
kubectl get pods -l app=web
# NAME READY STATUS RESTARTS AGE
# web-stateful-0 1/1 Running 0 2m
# web-stateful-1 1/1 Running 0 90s
# web-stateful-2 1/1 Running 0 60s
# 检查PVC事件
kubectl describe pvc dynamic-pvc
# Events:
# Warning ProvisioningFailed ... storageclass "nfs-client" not found
# 常见原因:
# 1. StorageClass不存在 → 创建或修改storageClassName
# 2. 没有匹配的PV(静态供给)→ 检查accessModes和容量
# 3. Provisioner未安装 → 部署nfs-subdir-external-provisioner
# 4. WaitForFirstConsumer → 需要先创建Pod才绑定
# Pod重启后数据消失?
# 1. 确认使用PVC而非emptyDir
kubectl get pod <name> -o jsonpath='{.spec.volumes}'
# 2. 检查reclaimPolicy
kubectl get pv -o custom-columns=NAME:.metadata.name,POLICY:.spec.persistentVolumeReclaimPolicy
# 3. PV的Status是否为Lost
kubectl get pv
# NAME STATUS CLAIM REASON
# pv-1 Lost pvc-1 原始NFS不可用
# 现象:Pod无法调度,事件提示Multi-Attach error
# 原因:RWO卷已被另一节点挂载
kubectl describe pod <name>
# Warning FailedAttachVolume ... Multi-Attach error for volume
# 解决:
# 1. 确认AccessMode(RWO只允许单节点写)
# 2. 需要多节点共享 → 使用RWX(NFS/CephFS)
# 3. 确保旧Pod已完全卸载卷
下一课预告:第07课深入节点调度——nodeSelector/nodeAffinity/taint与toleration。