存储架构深度剖析
概述
Kubernetes 存储系统是一个复杂的多层架构,涉及 PV、PVC、StorageClass、CSI 等多个组件。本文深入剖析存储系统的工作原理、生命周期管理和源码实现。
核心架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| ┌──────────────────────────────────────────────────────────────────────────────┐ │ 用户层 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Pod │ │ ConfigMap │ │ Secret │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ └─────────┼─────────────────┼─────────────────┼───────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ Volume (卷) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Volume 类型 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │emptyDir │ │hostPath │ │configMap │ │ secret │ │ │ │ │ │临时存储 │ │节点路径 │ │配置卷 │ │密钥卷 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ PVC │ │ CSI │ │downwardAPI│ │projected │ │ │ │ │ │持久卷声明 │ │存储接口 │ │元数据卷 │ │投射卷 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ PersistentVolume (持久卷) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ PV 类型 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ GCE PD │ │ AWS EBS │ │ AzureDB │ │ NFS │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Ceph │ │ Gluster │ │ iSCSI │ │ FC │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ StorageClass (存储类) │ │ │ │ - 动态 Provisioning │ │ │ │ - 回收策略 (Retain/Delete/Recycle) │ │ │ │ - 绑定模式 (Immediate/WaitForFirstConsumer) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ CSI (容器存储接口) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ CSI Driver │ │ CSI Identity│ │ CSI Node │ │ CSIControll│ │ │ │ (存储插件) │ │ (身份服务) │ │ (节点服务) │ │ (控制器) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ CSI RPC 接口 │ │ │ │ Identity, Controller, Node │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘
|
PV 和 PVC 生命周期
生命周期状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌──────────────────────────────────────────────────────────────────────────────┐ │ PV 生命周期 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │ │ Pending │ -> │Available│ -> │ Bound │ -> │ Released│ -> │ Failed ││ │ │ (创建中) │ │(可用) │ │ (已绑定) │ │(已释放) │ │ (失败) ││ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘│ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 状态转换: │ │ │ │ Pending -> Available: PV 创建中,等待 Provider 准备完成 │ │ │ │ Available -> Bound: PVC 绑定到此 PV │ │ │ │ Bound -> Released: PVC 删除 │ │ │ │ Released -> Failed: 回收失败 │ │ │ │ Released -> Available: 手动回收,重新可用 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘
|
回收策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
type PersistentVolumeReclaimPolicy string
const ( PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain"
PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete"
PersistentVolumeReclaimRecycle PersistentVolumeReclaimPolicy = "Recycle" )
|
StorageClass
核心字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: pd.csi.storage.gke.io parameters: type: pd-standard replication-type: regional-pd reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer allowVolumeExpansion: true mountOptions: - hard - noatime
|
绑定模式
1 2 3 4 5 6 7
| volumeBindingMode: Immediate
volumeBindingMode: WaitForFirstConsumer
|
CSI 架构
组件架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| ┌──────────────────────────────────────────────────────────────────────────────┐ │ Kubernetes 集群 │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ kube-controller-manager │ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ attachdetach controller │ │ │ │ │ │ - 挂载/卸载 Volume 到节点 │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ │ PV controller │ │ │ │ │ │ - PV/PVC 生命周期管理 │ │ │ │ │ │ - 触发 Volume Provisioning │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ gRPC │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ External Provisioner │ │ │ │ (sidecar: provisioner) │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ CSI Controller │ │ │ │ │ │ CreateVolume / DeleteVolume / ControllerPublishVolume │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ gRPC │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ CSI Driver (存储提供商) │ │ │ │ │ │ │ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ │ │ Identity Svc │ │ Controller Svc │ │ Node Svc │ │ │ │ │ │ 插件信息 │ │ 卷管理 │ │ 节点操作 │ │ │ │ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ Kubelet (节点侧) │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Volume Manager │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Attacher │ │ Mounter │ │ Unmounter │ │ │ │ │ │ 挂载设备 │ │ 挂载文件系统│ │ 卸载文件系统│ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ gRPC │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ CSI Node Driver Registrar │ │ │ │ (sidecar: node-driver-registrar) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ CSI Node Plugin (存储提供商) │ │ │ │ NodeStageVolume / NodePublishVolume / NodeUnpublishVolume │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘
|
CSI RPC 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
service Identity { rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse) {} rpc GetPluginCapabilities(GetPluginCapabilitiesRequest) returns (GetPluginCapabilitiesResponse) {} rpc Probe (ProbeRequest) returns (ProbeResponse) {} }
service Controller { rpc CreateVolume (CreateVolumeRequest) returns (CreateVolumeResponse) {} rpc DeleteVolume (DeleteVolumeRequest) returns (DeleteVolumeResponse) {} rpc ControllerPublishVolume (ControllerPublishVolumeRequest) returns (ControllerPublishVolumeResponse) {} rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest) returns (ControllerUnpublishVolumeResponse) {} rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest) returns (ValidateVolumeCapabilitiesResponse) {} rpc ListVolumes (ListVolumesRequest) returns (ListVolumesResponse) {} rpc GetCapacity (GetCapacityRequest) returns (GetCapacityResponse) {} rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest) returns (ControllerGetCapabilitiesResponse) {} }
service Node { rpc NodeStageVolume (NodeStageVolumeRequest) returns (NodeStageVolumeResponse) {} rpc NodeUnstageVolume (NodeUnstageVolumeRequest) returns (NodeUnstageVolumeResponse) {} rpc NodePublishVolume (NodePublishVolumeRequest) returns (NodePublishVolumeResponse) {} rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest) returns (NodeUnpublishVolumeResponse) {} rpc NodeGetId (NodeGetIdRequest) returns (NodeGetIdResponse) {} rpc NodeGetCapabilities (NodeGetCapabilitiesRequest) returns (NodeGetCapabilitiesResponse) {} }
|
Volume 挂载流程
Pod 创建时的卷挂载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| ┌──────────────────────────────────────────────────────────────────────────────┐ │ Kubelet 收到 Pod 创建请求 │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ VolumeManager 处理卷 │ │ pkg/kubelet/volumemanager/volume_manager.go │ │ │ │ 1. 等待卷准备完成 │ │ - 等待 PVC 绑定 │ │ - 等待卷设备挂载 │ │ 2. 获取期望的卷挂载信息 │ │ - 卷路径 │ │ - 挂载选项 │ │ - SELinux 上下文 │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ CSI Controller 操作 │ │ │ │ 1. CreateVolume (如果有 Provisioner) │ │ - 调用存储后端创建卷 │ │ - 返回 volumeId │ │ │ │ 2. ControllerPublishVolume │ │ - 将卷附加到节点 │ │ - 返回设备路径 (/dev/disk/by-id/...) │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ CSI Node 操作 │ │ │ │ 1. NodeStageVolume │ │ - 格式化 (如果需要) │ │ - 挂载到临时目录 /var/lib/kubelet/plugins/<plugin>/mounts/<volume>/ │ │ - 配置挂载选项 │ │ │ │ 2. NodePublishVolume │ │ - 从临时目录绑定挂载到容器路径 │ │ - /var/lib/kubelet/pods/<pod-uid>/volumes/<plugin>/<volume>/ │ │ - -> <container-path>/ │ └──────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ 卷挂载完成 │ │ │ │ Pod 可以启动容器 │ └──────────────────────────────────────────────────────────────────────────────┘
|
Volume 挂载代码路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
func (rc *Reconciler) reconcile() { for _, volume := range rc.desiredVolumes { if !rc.actualVolumes.Has(volume) { err := rc.mounter.MountVolume( volume, rc.nodeName, ) } }
for _, volume := range rc.actualVolumes { if !rc.desiredVolumes.Has(volume) { err := rc.unmounter.UnmountVolume(volume) } } }
|
Attach/Detach 流程
AttachDetach Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
func (adc *AttachDetachController) syncVolumes() { desiredVolumes := adc.getDesiredVolumes()
attachedVolumes := adc.getAttachedVolumes()
for _, vol := range desiredVolumes.Difference(attachedVolumes) { adc.attachVolume(vol) }
for _, vol := range attachedVolumes.Difference(desiredVolumes) { if !adc.isInUse(vol) { adc.detachVolume(vol) } } }
|
关键代码路径
| 文件 |
说明 |
pkg/controller/volume/pv_controller.go |
PV Controller |
pkg/controller/volume/attachdetach/ |
Attach/Detach Controller |
pkg/kubelet/volumemanager/ |
Kubelet Volume Manager |
pkg/volume/ |
Volume 插件接口 |
staging/src/k8s.io/csi-client/ |
CSI 客户端 |
staging/src/k8s.io/api/core/v1/types.go |
PV/PVC 类型定义 |
常见问题排查
1. PVC 一直处于 Pending
1 2 3 4 5 6 7 8
| kubectl get sc
kubectl get pods -n kube-system -l app=PD_CSI_DRIVER
kubectl describe pvc <pvc-name>
|
2. 卷挂载失败
1 2 3 4 5 6 7 8
| journalctl -u kubelet | grep volume
kubectl get pods -n kube-system -l app=CSI_DRIVER
kubectl describe pv <pv-name>
|
3. 存储卷不可扩容
1 2 3 4 5
| kubectl get sc <sc-name> -o yaml
kubectl get pvc <pvc-name> -o jsonpath='{.spec.resources}'
|
存储卷类型对比
| 类型 |
说明 |
使用场景 |
| emptyDir |
临时存储,Pod 删除后丢失 |
临时缓存、共享存储 |
| hostPath |
节点路径 |
单节点测试、宿主机访问 |
| persistentVolumeClaim |
持久卷 |
有状态应用 |
| configMap |
配置信息 |
应用配置 |
| secret |
敏感信息 |
证书、密钥 |
| downwardAPI |
Pod 元数据 |
Pod 信息注入 |
| projected |
投射卷 |
多种卷合一 |
面试题
基础题
1. PV 和 PVC 的区别是什么?
参考答案:
- **PV (PersistentVolume)**:集群级别的存储资源,由管理员或 StorageClass 动态创建
- **PVC (PersistentVolumeClaim)**:用户对存储的请求,可以请求特定大小和访问模式
2. StorageClass 的作用是什么?
参考答案:
- 定义存储类型和供应者
- 支持动态 Provisioning(自动创建 PV)
- 配置回收策略、绑定模式等
3. 存储卷的回收策略有哪些?
参考答案:
- Retain:保留数据,手动回收
- Delete:删除卷和后端存储
- Recycle:删除数据,重新可用(已废弃)
中级题
4. CSI 的三大服务是什么?各自职责?
参考答案:
| 服务 |
职责 |
运行位置 |
| Identity |
提供插件信息、能力列表 |
Controller |
| Controller |
卷的 CRUD、挂载/卸载 |
Controller |
| Node |
节点级别的格式化、挂载 |
Node |
5. 延迟绑定 (WaitForFirstConsumer) 有什么好处?
参考答案:
- Pod 调度后再绑定 PVC
- 避免 Pod 调度到无存储的节点
- 提高存储卷的可绑定性
- 减少不必要的存储资源浪费
6. Volume 挂载的完整流程是什么?
参考答案:
- PV Controller 创建/绑定 PV
- AttachDetach Controller 将卷附加到节点
- CSI Controller 调用 CreateVolume
- CSI Controller 调用 ControllerPublishVolume
- Kubelet 调用 NodeStageVolume
- Kubelet 调用 NodePublishVolume
- 容器启动,使用卷
7. 如何实现 ReadWriteMany (多节点读写)?
参考答案:
- 支持 RWX 的存储后端:NFS、GlusterFS、CephFS
- 不支持 RWX:AWS EBS、GCE PD(仅 RWO)
- 使用 NFS 作为共享存储:
1 2 3 4 5 6 7 8 9 10
| apiVersion: v1 kind: PersistentVolume spec: storageClassName: nfs persistentVolumeReclaimPolicy: Retain accessModes: - ReadWriteMany nfs: server: nfs-server path: /exports
|
高级题
8. CSI 的扩展机制是怎样的?
参考答案:
CSI 通过 gRPC 与存储提供商通信:
1 2 3 4 5 6 7 8 9 10 11
| type CSIDriver interface { DriverGetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) DriverGetCapabilities(ctx context.Context, req *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) CreateVolume(ctx context.Context, req *CreateVolumeRequest) (*CreateVolumeResponse, error) DeleteVolume(ctx context.Context, req *DeleteVolumeRequest) (*DeleteVolumeResponse, error) ControllerPublishVolume(ctx context.Context, req *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error) ControllerUnpublishVolume(ctx context.Context, req *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error) NodeStageVolume(ctx context.Context, req *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error) NodePublishVolume(ctx context.Context, req *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error) }
|
9. 如何实现存储卷的扩容?
参考答案:
启用扩容:
1 2 3
| apiVersion: storage.k8s.io/v1 kind: StorageClass allowVolumeExpansion: true
|
请求扩容:
1
| kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
|
CSI 扩容流程:
1
| ControllerExpandVolume -> NodeExpandVolume -> 完成
|
10. PV Controller 的工作原理是什么?
参考答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
func (ctrl *PersistentVolumeController) syncClaimKey(key string) { claim := getPVCFromCache(key)
switch claim.Status.Phase { case v1.ClaimPending: if volume := ctrl.findBestMatchPV(claim); volume != nil { ctrl.bindClaimToVolume(claim, volume) } if claim.Spec.StorageClassName != nil { ctrl.provisionClaim(claim) } case v1.ClaimBound: case v1.ClaimLost: ctrl.reclaimVolume(claim.Spec.VolumeName) } }
|
场景题
11. 如何排查存储卷挂载慢的问题?
参考答案:
检查 CSI Driver 状态:
1
| kubectl get pods -n kube-system -l app=csi-driver
|
检查 Attach 操作:
1
| kubectl describe pv <pv-name> | grep Attach
|
常见原因:
- CSI Driver 未就绪
- 云存储 API 限流
- 网络延迟
- 卷正在格式化
12. 如何实现本地存储 (Local PV)?
参考答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
apiVersion: v1 kind: PersistentVolume metadata: name: local-pv spec: storageClassName: local-storage capacity: storage: 100Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain local: path: /mnt/disks/sdb nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node-1
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer
|
13. 如何保护有状态应用的数据?
参考答案:
- 使用 ReadWriteOnce 卷:单节点访问
- 设置 Retain 回收策略:防止数据意外删除
- 启用 VolumeSnapshot:备份数据
1 2 3 4 5 6 7 8
| apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: my-snapshot spec: volumeSnapshotClassName: csi-gcp-pd source: persistentVolumeClaimName: my-pvc
|
- 使用 StatefulSet:保证 Pod 和 PVC 的绑定关系
- 配置 Pod Disruption Budget:防止同时删除多个副本