第 8 章:Kubernetes Volume 完整教程:EmptyDir、HostPath、PV、PVC、StorageClass 一次讲清

0 阅读2分钟

上一章讲了 ConfigMap 和 Secret。本章讲存储。容器本身是无状态的,但业务需要持久化数据,Volume 就是解决这个问题的。

8.1 为什么容器需要 Volume

Docker 容器默认是可写的,但容器删除后,所有数据都会丢失。这叫容器存储层的短暂性。K8s 的 Volume 机制让 Pod 可以访问持久化存储,即使 Pod 被调度到不同节点,数据仍然可以保留。

K8s 里的 Volume 分为两大类:

  • 临时存储:和 Pod 生命周期绑定,Pod 删了就消失。如 EmptyDir。
  • 持久化存储:独立于 Pod 生命周期,Pod 删了数据还在。如 PV、PVC。

Volume 类型

8.2 EmptyDir:和 Pod 同生共死

EmptyDir 是最简单的 Volume 类型。Pod 创建时分配一个空目录,Pod 内所有容器可以共享读写。Pod 删除后,目录里的数据也删除。

典型用法:

  • 主容器写日志,sidecar 读取后上传
  • 两个容器共享临时数据
  • 缓存目录
apiVersion: v1
kind: Pod
metadata:
  name: share-cache
spec:
  containers:
  - name: writer
    image: busybox:1.28
    command: ["sh", "-c", "while true; do echo $(date) >> /cache/log.txt; sleep 5; done"]
    volumeMounts:
    - name: cache
      mountPath: /cache
  - name: reader
    image: busybox:1.28
    command: ["sh", "-c", "while true; do cat /cache/log.txt; sleep 5; done"]
    volumeMounts:
    - name: cache
      mountPath: /cache
  volumes:
  - name: cache
    emptyDir: {}

创建后,两个容器共享 /cache 目录。writer 写入文件,reader 读取文件。

emptyDir 也可以指定介质类型:

volumes:
- name: cache
  emptyDir:
    medium: Memory

medium: Memory 表示用内存做临时文件系统,性能高,但 Pod 删除数据丢失,适合临时缓存。

8.3 HostPath:生产环境的坑

HostPath 把节点上的文件或目录挂载到 Pod 里。用法简单,但问题很多:

volumes:
- name: log
  hostPath:
    path: /var/log/myapp
    type: DirectoryOrCreate

问题:

  1. 多节点不一致:Pod 在 k8s-node1 上写 /var/log/myapp,重启后被调度到 k8s-node2,数据就找不到了
  2. 安全风险:Pod 可以读取节点上的敏感路径,如 /etc/kubernetes
  3. 调度依赖:Pod 被绑定到特定节点,K8s 的调度优势被浪费

HostPath 只适合:

  • 单节点测试环境
  • 需要访问节点特定设备的场景(如监控 agent 读取 /proc)
  • 节点本地日志收集

生产环境的多节点集群里,HostPath 是反模式。应该用 PV/PVC。

8.4 PV、PVC、StorageClass 的关系

这是 K8s 持久化存储最核心的三个对象:

PV/PVC/StorageClass 关系

8.4.1 PV(PersistentVolume)

PV 是集群里一块真实的存储资源,由管理员预先创建,或由 StorageClass 动态分配。它代表一块磁盘、一个 NFS 目录、一个 iSCSI LUN 等。

8.4.2 PVC(PersistentVolumeClaim)

PVC 是用户申请存储的声明。用户说"我要 5Gi 的 ReadWriteOnce 存储",K8s 会找到合适的 PV 绑定,或者通过 StorageClass 动态创建 PV。

8.4.3 StorageClass

StorageClass 定义存储的"类型"和"分配方式"。它指定了 provisioner(谁来创建存储),比如 local-path、NFS、AWS EBS 等。

用户创建 PVC → StorageClass 通知 provisioner → provisioner 创建 PV → PV 与 PVC 绑定 → Pod 挂载 PVC
对象谁创建作用
PV管理员或 provisioner真实存储
PVC用户申请存储
StorageClass管理员定义动态分配策略

8.5 动态供给与回收策略

8.5.1 动态供给

StorageClass 的核心价值是动态供给。用户提交 PVC 时,如果没有匹配的 PV,StorageClass 会自动创建一个。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

volumeBindingMode: WaitForFirstConsumer 表示等 Pod 调度后再创建 PV,这样可以避免 PV 和 Pod 不在同一个可用区的问题。

8.5.2 回收策略

PV 的回收策略决定 PVC 删除后,PV 怎么处理:

策略含义
RetainPVC 删除后,PV 保留,数据还在,需要管理员手动清理
DeletePVC 删除后,PV 自动删除,数据清空
Recycledeprecated,不建议用

生产环境默认 Delete 省事,但重要数据建议用 Retain,避免误删。

8.6 ReadWriteOnce vs ReadWriteMany

PVC 需要声明访问模式:

访问模式说明
ReadWriteOnce(RWO)只能被一个节点挂载为读写
ReadOnlyMany(ROX)可以被多个节点挂载为只读
ReadWriteMany(RWX)可以被多个节点同时挂载为读写
ReadWriteOncePod只能被一个 Pod 挂载为读写(K8s 1.27+)

单节点数据库用 RWO,多个副本需要共享存储的场景用 RWX。例如 NFS 支持 RWX,本地磁盘通常只支持 RWO。

8.7 local-path-provisioner 实战

我们的 3 节点集群没有云厂商存储,可以用 local-path-provisioner 来模拟动态供给。它会根据 PVC 在节点本地创建目录,并分配 PV。

安装:

$ kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-provisioner.yaml

查看默认 StorageClass:

$ kubectl get storageclass
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer

创建 PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web-pvc
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi

把 PVC 挂载到 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: web-with-pvc
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    volumeMounts:
    - name: data
      mountPath: /usr/share/nginx/html
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: web-pvc

创建后验证:

$ kubectl apply -f pvc-pod.yaml

$ kubectl get pvc
NAME      STATUS   VOLUME                                     CAPACITY   STORAGECLASS
web-pvc   Bound    pvc-7d4f8c2b-...                           1Gi        local-path

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY
pvc-7d4f8c2b-...                           1Gi        RWO            Delete

$ kubectl exec -it web-with-pvc -- sh -c "echo hello > /usr/share/nginx/html/index.html"

$ kubectl delete pod web-with-pvc
$ kubectl apply -f pvc-pod.yaml

$ kubectl exec -it web-with-pvc -- cat /usr/share/nginx/html/index.html
hello

数据保留了下来。

8.8 完整 YAML 示例:Deployment + PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-pvc
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: nginx:alpine
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: app-pvc

注意:Deployment 副本数为 1,因为 PVC 是 RWO。如果副本数大于 1,多个 Pod 可能调度到不同节点,无法共享同一个 RWO PVC。

8.9 本章小结

  • Volume 解决容器数据持久化问题
  • EmptyDir 适合临时共享存储,生命周期和 Pod 一致
  • HostPath 在多节点生产环境是反模式,除非特殊场景
  • PV 是真实存储,PVC 是用户申请,StorageClass 控制动态分配
  • 动态供给和回收策略是生产存储的核心配置
  • 访问模式 RWO/RWO/RWX 要根据应用选择
  • local-path-provisioner 适合裸金属或本地测试环境

8.10 课后练习

  1. 创建一个 EmptyDir Pod,让两个容器共享文件,验证数据随 Pod 删除而消失。
  2. 安装 local-path-provisioner,查看默认 StorageClass 和 provisioner。
  3. 创建 PVC 并挂载到 Pod,写入数据后删除重建 Pod,验证数据持久化。
  4. 尝试把 Deployment 副本数改为 2,挂载同一个 RWO PVC,观察报错并理解原因。