K8s 存储体系:三层抽象

Kubernetes 存储体系通过三层抽象解耦了存储使用方与提供方,这是理解容器持久化存储的核心。根据 K8s 存储文档,三层结构如下:

┌──────────────────────────────────────────────────────┐
│                   Pod (使用方)                        │
│                 volumeMounts → volumes                │
├──────────────────────────────────────────────────────┤
│              PVC (声明) — 用户申请存储                 │
│           "我需要 10Gi RWO 的存储"                    │
├──────────────────────────────────────────────────────┤
│           StorageClass (动态供给) — 存储模板           │
│         "使用 ceph-rbd 驱动,reclaim: Retain"        │
├──────────────────────────────────────────────────────┤
│              PV (物理资源) — 实际存储                  │
│          "10.0.0.5:/data/pvc-xxx (NFS)"              │
└──────────────────────────────────────────────────────┘

PV(PersistentVolume)

PV 是集群级资源,代表物理存储的抽象。PV 可以由管理员手动创建,也可通过 StorageClass 自动供给:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-data
spec:
  capacity:
    storage: 50Gi
  accessModes:
  - ReadWriteMany           # 多节点读写
  persistentVolumeReclaimPolicy: Retain
  nfs:
    server: 10.0.0.5
    path: /data/k8s-share
  storageClassName: ""      # 静态 PV 不绑定 StorageClass

PVC(PersistentVolumeClaim)

PVC 是命名空间级的存储声明,用户通过 PVC 申请所需存储,系统自动匹配满足条件的 PV:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data-pvc
  namespace: production
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: ceph-rbd
  resources:
    requests:
      storage: 20Gi

StorageClass(动态供给)

StorageClass 是动态存储供给的核心,绑定 CSI 驱动和参数模板,实现按需创建 PV:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ceph-rbd
provisioner: rbd.csi.ceph.com
reclaimPolicy: Retain
allowVolumeExpansion: true   # 支持在线扩容
parameters:
  clusterID: ceph-cluster-1
  pool: k8s-rbd-pool
  imageFormat: "2"
  imageFeatures: layering
  csi.storage.k8s.io/provisioner-secret-name: ceph-secret
  csi.storage.k8s.io/provisioner-secret-namespace: ceph-system

AccessMode 详解

AccessMode缩写含义典型场景
ReadWriteOnceRWO单节点读写MySQL、PostgreSQL
ReadOnlyManyROX多节点只读配置文件、静态资源
ReadWriteManyRWX多节点读写NFS、CephFS、共享数据
ReadWriteOncePodRWOP单 Pod 读写K8s 1.22+,精确到 Pod 级

CSI 驱动机制

CSI(Container Storage Interface)是 K8s 与存储后端之间的标准接口,取代了早期的 in-tree 存储插件。根据 CSI 规范,CSI 驱动通过 Sidecar 组件与 K8s 控制面交互。

CSI 架构

┌───────────────┐    gRPC     ┌──────────────┐
│  K8s 控制面    │◄──────────►│ CSI Sidecar  │
│  (PV/PVC/SC)  │            │ (provisioner │
└───────────────┘            │  attacher)   │
                             └──────┬───────┘
                                    │ gRPC
                             ┌──────▼───────┐
                             │  CSI Driver  │
                             │  (存储后端)   │
                             └──────────────┘

CSI 驱动由三个 Sidecar 组件协同工作:

  • external-provisioner:监听 PVC 事件,调用 CSI 驱动创建/删除存储卷
  • external-attacher:管理 VolumeAttachment,挂载/卸载存储到 Node
  • external-snapshotter:支持存储快照功能

常见 CSI 驱动

NFS CSI

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
  server: 10.0.0.5
  share: /data/k8s-share
  subDir: ${pvc.metadata.namespace}/${pvc.name}
reclaimPolicy: Delete
volumeBindingMode: Immediate

NFS 适合共享文件场景,优点是原生 RWX 支持,缺点是网络性能依赖 NFS 服务器。

Ceph RBD CSI

Ceph RBD 提供块存储,适合数据库等高 IO 场景:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ceph-rbd-fast
provisioner: rbd.csi.ceph.com
parameters:
  clusterID: ceph-cluster-1
  pool: ssd-pool              # 使用 SSD 池
  imageFormat: "2"
  imageFeatures: layering
  csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - discard                   # 启用 TRIM

Local Path Provisioner

Rancher Local Path Provisioner 使用 Node 本地磁盘,适合低延迟、高吞吐场景:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer  # 延迟到 Pod 调度时才绑定
reclaimPolicy: Delete

WaitForFirstConsumer 是 Local Volume 的关键设置——它确保 PV 在 Pod 调度到某个 Node 后才创建,避免调度冲突。

存储选型矩阵

不同存储方案在性能、成本和可移植性上各有取舍:

存储类型性能成本可移植性RWX适用场景
Local Path极高极低缓存、临时数据、单副本数据库
NFS共享文件、配置同步
Ceph RBD数据库、块设备
CephFS中高共享存储、大数据
云盘(EBS/PD)云上数据库
Longhorn中高K8s 原生分布式存储

选型决策树

需要 RWX(多 Pod 同时读写)?
├─ 是 → NFS(简单)/ CephFS(高性能)
└─ 否 → 需要高 IOPS?
         ├─ 是 → Local Path(单节点)/ Ceph RBD SSD(分布式)
         └─ 否 → 成本敏感?
                  ├─ 是 → Local Path / Longhorn
                  └─ 否 → Ceph RBD / 云盘

有状态应用部署注意事项

MySQL 部署

MySQL 是典型的有状态应用,存储配置需格外注意:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 1               # 主从需用 Operator 管理
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        resources:
          requests:
            memory: "2Gi"
            cpu: "1000m"
          limits:
            memory: "4Gi"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ceph-rbd
      resources:
        requests:
          storage: 50Gi

关键注意事项:

  • 数据安全reclaimPolicy: Retain,防止误删 PVC 导致数据丢失
  • 资源限制:内存限制不低于 innodb_buffer_pool_size + 1GB
  • 调度亲和:使用 nodeSelectorpodAntiAffinity 固定到高性能节点
  • 备份策略:使用 Velero 或存储快照定期备份

Redis 部署

Redis 需要区分缓存和持久化两种场景:

# Redis 持久化模式需启用 AOF
volumeClaimTemplates:
- metadata:
    name: redis-data
  spec:
    accessModes: ["ReadWriteOnce"]
    storageClassName: local-path    # Redis IO 敏感,优先 Local
    resources:
      requests:
        storage: 10Gi

Elasticsearch 部署

ES 对存储 IO 要求极高,每个节点需要独立 PV:

# ES Pod 使用 initContainer 优化系统参数
initContainers:
- name: sysctl
  image: busybox
  command: ["sysctl", "-w", "vm.max_map_count=262144"]
  securityContext:
    privileged: true
containers:
- name: elasticsearch
  image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
  env:
  - name: ES_JAVA_OPTS
    value: "-Xms2g -Xmx2g"    # 堆内存 = 容器内存的 50%
  volumeMounts:
  - name: data
    mountPath: /usr/share/elasticsearch/data

存储故障排查

PVC 一直 Pending

# 查看 PVC 事件
kubectl describe pvc app-data-pvc -n production

# 常见原因排查
# 1. StorageClass 不存在
kubectl get sc

# 2. 没有可用的 PV(静态供给)
kubectl get pv

# 3. StorageClass 的 provisioner 未运行
kubectl get pods -n kube-system | grep csi

# 4. 存储后端连接失败(检查 CSI 日志)
kubectl logs -n ceph-system ceph-csi-rbd-provisioner-xxx

PV 挂载失败

# 查看 Pod 事件中的挂载错误
kubectl describe pod <pod-name>

# 常见错误及处理
# "MountVolume.MountDevice failed" → 存储后端不可达
# "Unable to attach or mount volumes" → Node 上 CSI Node 插件异常

# 登录目标 Node 检查
# 检查块设备
lsblk
# 检查挂载点
mount | grep <pv-name>
# 检查 CSI Node 插件
crictl ps | grep csi

存储扩容失败

# 确认 StorageClass 支持扩容
kubectl get sc ceph-rbd -o jsonpath='{.allowVolumeExpansion}'
# 输出应为 true

# 执行扩容
kubectl patch pvc app-data-pvc -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'

# 查看扩容状态
kubectl get pvc app-data-pvc -o jsonpath='{.status.conditions}'

常见故障速查表

故障现象可能原因排查命令
PVC PendingSC 不存在/Provisioner 未运行kubectl describe pvc
Pod ContainerCreatingPV 挂载失败kubectl describe pod
Pod 事件 “disk pressure”Node 磁盘满kubectl describe node
IO 延迟高存储后端性能差iostat -x 1
PVC 扩容无响应SC 未开启扩容kubectl get sc

总结

容器持久化存储的核心在于理解三层抽象、合理选型、预防故障。三层抽象(PV/PVC/StorageClass)解耦了使用与供给,CSI 标准化了驱动接口。选型时需要平衡性能、成本和可移植性三要素——Local Path 适合极致性能但牺牲可移植性,Ceph RBD 提供分布式块存储的均衡方案,NFS 则是共享存储的经济选择。

生产环境的关键原则:数据安全第一(Retain 策略)、监控存储健康、做好备份方案。定期验证备份的可用性,比存储选型本身更重要。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. K8s 存储文档 — Kubernetes 官方,参考了K8s 存储文档相关内容
  2. CSI 规范 — Kubernetes-csi,参考了CSI 规范相关内容
  3. Rancher Local Path Provisioner — GitHub 开源社区,参考了Rancher Local Path Provisioner相关内容