1. CSI 卷扩容

CSI 卷扩容主要通过 external-resizer 、 kubelet 以及以下 RPC 接口实现:

  • ControllerExpandVolume:由 ControllerServer 实现,external-resizer 调用
  • NodeExpandVolume:由 NodeServer 实现,kubelet 调用(PS:只有在卷绑定到容器时会调用该接口)

整个扩容流程非常清晰:

  • external-resizer 通过监听 PVC 的容量变化,判断 PVC 和 PV 的容量大小,决定是否进行扩容;
  • external-resizer 调用 ControllerExpandVolume 接口执行扩容操作;
  • external-resizer 根据 ControllerExpandVolume 返回的结果,更新 PV 容量(spec.capacity.storage 字段)
  • external-resizer 根据 ControllerExpandVolume 返回的信息( NodeExpansionRequired ),为 PVC 添加 Conditions (FileSystemResizePending)
  • 当volume处于挂载状态时,kubelet 检查 PVC 对应 Conditions(FileSystemResizePending),并调用 NodeExpandVolume
  • 修改 pvc 容量 (status.capacity.storage 字段)

如果 volume 是离线状态的并且 NodeExpansionRequired 为 true,那么扩容会一直处于 pending 状态(FileSystemResizePending conditions 一直存在),直到 pvc 被使用。

2. Liveness Probe

通过 kubernetes-csi/livenessprobe 实现探针,该容器通过 RPC 调用 Probe() 接口。

Probe() 接口在 Identity Service 服务中实现:

1
2
3
4
5
6
7
8
9
message ProbeRequest {}

message ProbeResponse {
   // 返回值表示的几种情况:
   // 1. Ready == nil:初始化完成
   // 2. Ready == true:初始化完成
   // 3. Ready == false:初始化中
  .google.protobuf.BoolValue ready = 1;
}

上述返回的异常错误码:

  • FAILED_PRECONDITION:表示插件未处于健康/就绪状态或者依赖的组件不健康。

LivenessProbe 容器只是提供 RPC 接口到 HTTPS 接口的转换。

由于 CSI pod 本身不是通过 Service 对外提供服务的,因此 Probe 返回异常或者 False 业务对 CSI RPC 的调用仍是在进行的,通过探针实际上无法起到隔离作用。

3. 卷健康检查

1. KEP-1432: Volume Health Monitor

KEP-1432 中描述了 CSI 卷健康检查功能的概要设计,包括以下内容:

  1. 设计只包含卷状态信息收集,发现故障后自动恢复的功能没有在设计考虑中。
  2. external-health-monitor-controller 容器调用 ListVolumes/ControllerGetVolume 接口,获取 Volume Condition 信息
  3. Kubelet 负责调用 NodeGetVolumeStats 接口,负责采集卷的使用信息/Volume Condition 信息,并提供 VolumeStats metrics API 。

CSI 中关于健康检查的接口包括以下三个:

Interface Service Capacity 备注
ListVolumes ControllerService LIST_VOLUMES/VOLUME_CONDITION/LIST_VOLUMES_PUBLISHED_NODES 分页返回所有卷信息
ControllerGetVolume ControllerService GET_VOLUME/VOLUME_CONDITION/LIST_VOLUMES_PUBLISHED_NODES 返回指定卷的信息
NodeGetVolumeStats NodeService GET_VOLUME_STATS/VOLUME_CONDITION 返回指定卷 Condition 信息和容量使用情况

上述三个接口中,ListVolumes 和 ControllerGetVolume 被 external-health-monitor-controller 服务调用,可以只实现其中一个。

2. external-health-monitor 项目

external-health-monitor 项目是 Kubernetes 1.19 版本中引入的,该项目中包含以下两个边车容器:

  • external-health-monitor-controller:调用 ListVolumes/ControllerGetVolume 接口,检查所有 bound 状态的 csi 卷。
  • external-health-monitor-agent:调用 NodeGetVolumeStats 接口,检查绑定到 pod 的 csi 卷。

上述容器中,controller 主要用于检查存储设备上卷的状态,agent 部署在所有 node 上用于检测 csi 卷到指定节点的连接状态。 controller 还会 watch node 状态,当 node 故障时,如果节点上 pod 使用了对应 PVC 会发出相应 event。

external-health-monitor 容器会根据 csi 接口中返回的 VolumeCondition 判断卷的状态是否符合预期。 当卷的状态异常时创建一条 VolumeConditionAbnormal 类型的 event,当卷从异常状态恢复时创建一条 VolumeConditionNormal 类型的 event。

3.Kubelet 采集 volume 使用率

Kubelet 会通过 csi.socket 调用 NodeGetVolumeStats 接口,相关代码在 “pkg/volume/csi/csiDriverClient.go” 的 NodeGetVolumeStats 函数中,主要用于获取对应的 metrics 信息。

Kubelet 调用 NodeGetVolumeStats 时不会在 NodeGetVolumeStatsRequest 中填充 stagingtargetpath 参数。

Kubernetes 1.21 之后的版本添加了 CSIVolumeHealth 特性,开启这个特性后 Kubelet 会将 NodeGetVolumeStats 接口中获取的 VolumeCondition 信息,更新到 metrics 中。

CSIVolumeHealth 特性取代了 external-health-monitor-agent 容器的部分职责,因此官方建议在高版本的 k8s 中可以不部署该容器。

PS:external-health-monitor-agent容器会创建event,我没有部署高版本 k8s 验证 kubelet 是否同样创建 event.

通过 kubectl get --raw "/api/v1/nodes/<node>/proxy/metrics" 可以看到对于被挂载到 Pod 上的卷,有以下六个指标:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# HELP kubelet_volume_stats_capacity_bytes [ALPHA] Capacity in bytes of the volume
# TYPE kubelet_volume_stats_capacity_bytes gauge
kubelet_volume_stats_capacity_bytes{namespace="default",persistentvolumeclaim="csi-block-pvc"} 2.13696512e+09
# HELP kubelet_volume_stats_available_bytes [ALPHA] Number of available bytes in the volume
# TYPE kubelet_volume_stats_available_bytes gauge
kubelet_volume_stats_available_bytes{namespace="default",persistentvolumeclaim="csi-block-pvc"} 2.102980608e+09
# HELP kubelet_volume_stats_used_bytes [ALPHA] Number of used bytes in the volume
# TYPE kubelet_volume_stats_used_bytes gauge
kubelet_volume_stats_used_bytes{namespace="default",persistentvolumeclaim="csi-block-pvc"} 3.3984512e+07
# HELP kubelet_volume_stats_inodes [ALPHA] Maximum number of inodes in the volume
# TYPE kubelet_volume_stats_inodes gauge
kubelet_volume_stats_inodes{namespace="default",persistentvolumeclaim="csi-block-pvc"} 0
# HELP kubelet_volume_stats_inodes_free [ALPHA] Number of free inodes in the volume
# TYPE kubelet_volume_stats_inodes_free gauge
kubelet_volume_stats_inodes_free{namespace="default",persistentvolumeclaim="csi-block-pvc"} 0
# HELP kubelet_volume_stats_inodes_used [ALPHA] Number of used inodes in the volume
# TYPE kubelet_volume_stats_inodes_used gauge
kubelet_volume_stats_inodes_used{namespace="default",persistentvolumeclaim="csi-block-pvc"} 0

以上输出是在1.20.11的版本中,并没有 volume condition 指标

参考