Kubernetes CSI 迁移核心库 csi-translation-lib 解析:In-Tree 卷插件与 CSI 驱动之间的 PV 翻译机制
2026/9/8 22:47:12 网站建设 项目流程

Kubernetes CSI 迁移核心库 csi-translation-lib 解析:In-Tree 卷插件与 CSI 驱动之间的 PV 翻译机制

【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes

导读

本文围绕 Kubernetes 仓库中的staging/src/k8s.io/csi-translation-lib组件展开,它是 Kubernetes 与各云厂商 Out-of-Tree CSI 组件(如 external provisioner)共同消费的"翻译层":当集群启用 CSI migration(卷插件迁移)后,把仍以kubernetes.io/gce-pdkubernetes.io/aws-ebs等 In-Tree 形态存在的 PV / StorageClass / 内联卷描述,无损地翻译成对应 CSI 驱动的 API 对象,让存量数据卷在不改动用户 YAML 的前提下平滑迁往 CSI 生态。读完本文你将掌握该库的定位、CSITranslator全部公开 API 的调用语义、7 大云厂商插件的注册与翻译细节、拓扑与访问模式的兼容性处理,以及它在 Kubernetes 控制器与调度器中的真实消费路径。

组件定位:谁在生产中消费这套翻译函数

staging/src/k8s.io/csi-translation-lib/README.md对其定位有非常明确的表述:这个仓库提供一系列函数,供 Kubernetes 各组件以及Out-of-Tree CSI 组件(如 external provisioner)使用,目标是"把 Kubernetes In-Tree 插件代码的迁移逻辑从插件仓库中提取出来、供双方共享"。README 特别指出它的典型消费方式——外部 CSI 组件可以直接调用TranslateToCSITranslateToInTree系列函数来翻译 PV source。

由于该库属于 Kubernetes 官方 staged repository(外部仓库暂存区) 机制,它既以独立模块发布,也被主仓库k8s.io/kubernetes通过 vendor 方式内部引用。在本仓库内,真实的调用方包括:

  • 卷迁移门面层 pkg/volume/csimigration/plugin_manager.go,它包装了GetInTreePluginNameFromSpecGetCSINameFromInTreeName等映射方法,配合 CSIMigration 特性门控判断"某插件的迁移是否已经完成";
  • PV 控制器与 attach/detach 控制器,如 pkg/controller/volume/persistentvolume/pv_controller_base.go、pkg/controller/volume/attachdetach/attach_detach_controller.go;
  • Kubelet 卷管理器 pkg/kubelet/volumemanager/volume_manager.go;
  • 调度器的卷绑定插件 pkg/scheduler/framework/plugins/volumebinding/binder.go 与节点卷上限插件 pkg/scheduler/framework/plugins/nodevolumelimits/csi.go。

这些调用方共享的是同一个入口对象——CSITranslator,其核心实现位于 staging/src/k8s.io/csi-translation-lib/translate.go。

核心 API:CSITranslator 的翻译方法族

CSITranslator是一个空结构体,通过New()工厂函数创建实例(translate.go#L44-L50):

type CSITranslator struct{} func New() CSITranslator { return CSITranslator{} }

所有翻译逻辑都维护在一个包级注册表inTreePlugins中(translate.go#L29-L39):

var inTreePlugins = map[string]plugins.InTreePlugin{ plugins.GCEPDDriverName: plugins.NewGCEPersistentDiskCSITranslator(), plugins.AWSEBSDriverName: plugins.NewAWSElasticBlockStoreCSITranslator(), plugins.CinderDriverName: plugins.NewOpenStackCinderCSITranslator(), plugins.AzureDiskDriverName: plugins.NewAzureDiskCSITranslator(), plugins.AzureFileDriverName: plugins.NewAzureFileCSITranslator(), plugins.VSphereDriverName: plugins.NewvSphereCSITranslator(), plugins.PortworxDriverName: plugins.NewPortworxCSITranslator(), }

map 的 key 是CSI 驱动名,因此查找"In-Tree 名 → CSI 名"实际是遍历比较;而"CSI 名 → In-Tree 名"是 O(1) 的 map 直接命中。下面逐个介绍四个核心翻译方法。

1. TranslateInTreePVToCSI:In-Tree PV 源 → CSI 源

这是最常用、也是 README 中TranslateToCSI所指的翻译入口(translate.go#L96-L107)。它接收一个*v1.PersistentVolume,先做DeepCopy(),再遍历注册表调用CanSupport(pv)找到能够识别该 PV 的插件,交给插件自身的TranslateInTreePVToCSI处理:

func (CSITranslator) TranslateInTreePVToCSI(logger klog.Logger, pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { if pv == nil { return nil, errors.New("persistent volume was nil") } copiedPV := pv.DeepCopy() for _, curPlugin := range inTreePlugins { if curPlugin.CanSupport(copiedPV) { return curPlugin.TranslateInTreePVToCSI(logger, copiedPV) } } return nil, fmt.Errorf("could not find in-tree plugin translation logic for %#v", copiedPV.Name) }

注意两点实现语义:

  • 输入对象不被修改:方法先DeepCopy()再翻译,注释明确 "The input persistent volume will not be modified";
  • 插件接口层与门面层的语义差异:接口方法TranslateInTreePVToCSI的注释是 "The input persistent volume can be modified"(plugins/in_tree_volume.go#L46)——门面层负责拷贝保护,插件实现直接改写传入对象。从插件实现看(如 plugins/gce_pd.go#L262-L263),翻译后会清空pv.Spec.PersistentVolumeSource.GCEPersistentDisk并把CSI源填进去。

以 GCE PD 为例,翻译会做三件事(plugins/gce_pd.go#L212-L267):

  1. 依据 PV 上的 zone 标签(Beta 版failure-domain.beta.kubernetes.io/zone或 GA 版topology.kubernetes.io/zone,多个 zone 用__分隔)构造标准化的 CSI volume handle;
  2. partitionfstypereadOnly等字段映射进CSIPersistentVolumeSource
  3. 调用translateTopologyFromInTreeToCSI把 NodeAffinity/Labels 中的旧拓扑键改写为 CSI 拓扑键topology.gke.io/zone

2. TranslateCSIPVToInTree:CSI 源 → In-Tree 源(回滚兼容)

与上一方法互为逆操作,用于 CSI 迁移发生问题需要回退、或迁移尚未在目标组件上完成时,把 CSI PV 还原成 In-Tree PV(translate.go#L112-L123):

func (CSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { if pv == nil || pv.Spec.CSI == nil { return nil, errors.New("CSI persistent volume was nil") } copiedPV := pv.DeepCopy() for driverName, curPlugin := range inTreePlugins { if copiedPV.Spec.CSI.Driver == driverName { return curPlugin.TranslateCSIPVToInTree(copiedPV) } } return nil, fmt.Errorf("could not find in-tree plugin translation logic for %s", copiedPV.Spec.CSI.Driver) }

它的分派依据是pv.Spec.CSI.Driver字段——哪个驱动名命中注册表,就用哪个插件做回翻。以 GCE PD 为例,回翻时会从 volume handle 中解析出 PDName、还原Partition整数,并把 CSI 拓扑键再改写回 Kubernetes 拓扑(plugins/gce_pd.go#L271-L304)。测试 translate_test.go#L49-L102 中的TestTranslationStability专门验证了"PV → CSI → 再回 In-Tree"往返后reflect.DeepEqual与原对象完全一致,从测试层面保证了双向翻译的稳定性。

3. TranslateInTreeInlineVolumeToCSI:内联卷 → CSI PV

Pod 内的内联卷(例如直接在 Pod spec 里写gcePersistentDisk:而非引用 PVC)无法在 CSI 时代原样存在,因此该库把它们包装成一个临时 PV(translate.go#L67-L90)。分派依据是接口中的CanSupportInline(volume)

for _, curPlugin := range inTreePlugins { if curPlugin.CanSupportInline(volume) { pv, err := curPlugin.TranslateInTreeInlineVolumeToCSI(logger, volume, podNamespace) ... if pv.Spec.VolumeMode == nil { volumeMode := v1.PersistentVolumeFilesystem pv.Spec.VolumeMode = &volumeMode } return pv, nil } }

实现中有两处容易忽略的关键行为:

  • 卷模式兜底:内联卷只支持 Filesystem(不支持 Block)。由于 PV 默认初始化逻辑不覆盖内联卷场景,若插件未显式设置VolumeMode,门面层统一把它置为PersistentVolumeFilesystem(translate.go#L77-L85)。
  • podNamespace参数只为 azurefile 服务:注释说明该参数仅 azurefile 翻译器需要(用于定位 secret 所在 namespace),其他插件传空即可(plugins/in_tree_volume.go#L41)。

以 GCE PD 内联卷翻译为例(plugins/gce_pd.go#L166-L208):它会构造一个名字形如pd.csi.storage.gke.io-<diskName>的 PV(该名字作为 stage 路径的唯一区分部分),把partitionfstypereadOnly搬到 CSI 源,并按只读与否推导出ReadOnlyManyReadWriteOnce访问模式。

4. TranslateInTreeStorageClassToCSI:StorageClass 参数翻译

对于动态供给场景,external provisioner 拿到的是 StorageClass。该函数(translate.go#L54-L62)先 DeepCopy,再按 In-Tree 插件名匹配并翻译,例如把 In-Tree 时代的fstypezonezones等参数转换为 CSI 驱动认识的形态。每个插件的参数转换规则各有差异:

参数场景In-Tree 写法CSI 翻译后依据实现
文件系统类型fstype: ext4csi.storage.k8s.io/fstype: ext4(前缀参数由 external provisioner 剥离)plugins/gce_pd.go#L88-L90
单 zone 约束zone: us-central1-a转成AllowedTopologies,键为 CSI 拓扑键plugins/aws_ebs.go#L70-L71
多 zone 约束zones: a,b拆分后转成AllowedTopologies的多个值plugins/gce_pd.go#L94-L95
EBS 特有iopspergb保留原参数,并追加allowautoiopspergbincrease: "true"以保持 In-Tree 行为plugins/aws_ebs.go#L74-L79

约束检查:如果AllowedTopologieszone/zones参数同时出现,翻译器直接报错cannot simultaneously set allowed topologies and zone/zones parameters,避免语义冲突;若只存在旧的AllowedTopologies,则通过translateAllowedTopologies(plugins/in_tree_volume.go#L310-L336)把其中failure-domain.beta.kubernetes.io/zone/topology.kubernetes.io/zone条目改写成 CSI 拓扑键,其他拓扑原样透传。

辅助判定 API:迁移能力探测与名称双向映射

除了四个翻译方法,README 强调的"判断翻译逻辑是否存在、建立 in-tree 与 CSI 的双向映射"由下列方法完成(均在 translate.go 中):

方法作用分派依据
IsMigratableIntreePluginByName(name)给定 In-Tree 插件名,判断是否有对应迁移逻辑遍历比较GetInTreePluginName()(L127-L134)
IsMigratedCSIDriverByName(name)给定 CSI 驱动名,判断它是否为已迁移的 In-Tree 插件的替代者直接查 map key(L138-L143)
GetInTreePluginNameFromSpec(pv, vol)从 PV 或内联卷 spec 反查 In-Tree 插件名CanSupport/CanSupportInline(L146-L164)
GetCSINameFromInTreeName(name)In-Tree 插件名 → CSI 驱动名遍历 map(L168-L175)
GetInTreeNameFromCSIName(name)CSI 驱动名 → In-Tree 插件名map 命中后返回GetInTreePluginName()(L179-L184)
IsPVMigratable(pv)给定 PV 是否可迁移CanSupport(L187-L194)
IsInlineMigratable(vol)给定内联卷是否可迁移CanSupportInline(L197-L204)
RepairVolumeHandle(driverName, volumeHandle, nodeID)依据节点 ID 修复缺失 project/zone 信息的 volume handle按 driverName 命中插件(L207-L212)

这些映射方法被仓库内卷迁移门面复用。例如 pkg/volume/csimigration/plugin_manager.go#L29-L46 定义了PluginNameMapper接口(仅要求GetInTreePluginNameFromSpecGetCSINameFromInTreeName),PluginManager组合它并据此判断某插件的迁移是否"完整完成"——需要同时开启 CSIMigration 特性门控与对应的 InTreePluginUnregister 门控。

插件注册表全景:7 大 In-Tree → CSI 转换器

每个云厂商插件都以独立的plugins/*.go文件存在,并实现统一的InTreePlugin接口。当前注册的驱动与文件路径如下:

云平台In-Tree 插件名CSI 驱动名CSI 拓扑键源码位置
GCE PDkubernetes.io/gce-pdpd.csi.storage.gke.iotopology.gke.io/zoneplugins/gce_pd.go
AWS EBSkubernetes.io/aws-ebsebs.csi.aws.comtopology.ebs.csi.aws.com/zoneplugins/aws_ebs.go
Azure Diskkubernetes.io/azure-diskdisk.csi.azure.comtopology.disk.csi.azure.com/zoneplugins/azure_disk.go
Azure Filekubernetes.io/azure-filefile.csi.azure.com不涉及 zone 拓扑(共享文件卷)plugins/azure_file.go
OpenStack Cinderkubernetes.io/cindercinder.csi.openstack.orgtopology.cinder.csi.openstack.org/zoneplugins/openstack_cinder.go
vSpherekubernetes.io/vsphere-volumecsi.vsphere.vmware.comtopology.csi.vmware.com/zone(另有 region 键)plugins/vsphere_volume.go
Portworxkubernetes.io/portworx-volumepxd.portworx.complugins/portworx.go

其中部分插件(如 azurefile)因为 PV 形态特殊,还单独维护了 volume handle 编解码格式(azurefile 用#分隔 shareName、secret 等字段,见 plugins/azure_file.go#L39-L51)。

InTreePlugin 接口:每个转换器必须实现的契约

plugins/in_tree_volume.go#L31-L69 定义了每个插件翻译器都必须满足的接口:

type InTreePlugin interface { TranslateInTreeStorageClassToCSI(logger klog.Logger, sc *storage.StorageClass) (*storage.StorageClass, error) TranslateInTreeInlineVolumeToCSI(logger klog.Logger, volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) TranslateInTreePVToCSI(logger klog.Logger, pv *v1.PersistentVolume) (*v1.PersistentVolume, error) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) CanSupport(pv *v1.PersistentVolume) bool CanSupportInline(vol *v1.Volume) bool GetInTreePluginName() string GetCSIPluginName() string RepairVolumeHandle(volumeHandle, nodeID string) (string, error) }

各翻译器结构体通常以编译期断言保证契约完整实现,例如 GCE PD 与 AWS EBS 都写有var _ InTreePlugin = &gcePersistentDiskCSITranslator{}(plugins/gce_pd.go#L57)、var _ InTreePlugin = &awsElasticBlockStoreCSITranslator{}(plugins/aws_ebs.go#L50)。接口中的CanSupport/CanSupportInline决定了门面层的分派结果,例如 GCE 实现仅判断pv.Spec.GCEPersistentDisk != nil(plugins/gce_pd.go#L309-L318)。

翻译中的兼容性细节:访问模式、拓扑键与 region 推导

从源码看,这套翻译并不是简单的字段搬家,其中埋了多处在 In-Tree 时代"宽松、不校验"而 CSI 驱动"严格、会报错"的兼容处理,理解它们对排查迁移期问题至关重要。

访问模式向后兼容:GCE PD 的 In-Tree 实现从不校验ReadWriteMany——用户即使声明了它,底层也只是按单节点读写挂载。但 CSI driver 会严格拒绝ReadWriteMany。因此backwardCompatibleAccessModes(plugins/gce_pd.go#L130-L162)把所有ReadWriteMany收敛为ReadWriteOnce,并把[ReadWriteOnce, ReadOnlyMany]这种"单盘不可同时满足"的组合也降级为ReadWriteOnce,确保旧卷迁移后仍可工作。

拓扑标签 Beta → GA 升级translateTopologyFromInTreeToCSI(plugins/in_tree_volume.go#L185-L214)会把 PV 上存在的failure-domain.beta.kubernetes.io/zone等 Beta 标签一并改写为 GA 的topology.kubernetes.io/zone,并优先消费 NodeAffinity 中的拓扑(若 NodeAffinity 与 PV Labels 同时存在,NodeAffinity 优先)。getTopologyLabel(in_tree_volume.go#L225-L241)按"NodeAffinity GA → NodeAffinity Beta → Labels GA → Labels Beta"的顺序判定当前使用的拓扑键代际。

CSI → In-Tree 回翻与 region 推导translateTopologyFromCSIToInTree(plugins/in_tree_volume.go#L275-L308)把 CSI 拓扑改写回 Kubernetes zone 标签,并通过可选的regionParserFn由每个云厂商自行实现"zone → region"推导(例如 GCE 要求 zone 形如{locale}-{region}-{zone}三段式,见 plugins/gce_pd.go#L383-L400)。若某个插件存在多个拓扑键(如 vSphere 同时有 zone 与 region 键),接口注释明确指出其需要单独处理,不能复用通用路径(in_tree_volume.go#L262-L267)。

volume handle 修复:RepairVolumeHandle 的实战价值

CSI 迁移早期,部分存量卷的 volume handle 缺失 project / region 等前缀信息(因为 In-Tree 时代的卷 ID 更短)。RepairVolumeHandle通过传入的 nodeID 补齐这些字段。以 GCE PD 为例(plugins/gce_pd.go#L333-L371),其 volume handle 期望格式为:

  • zonal:projects/{project}/zones/{zone}/disks/{disk}
  • regional:projects/{project}/regions/{region}/disks/{disk}

实现会按/切分字符串并校验元素个数(少于 6 段即报错);当 project 段为UNSPECIFIED时,从同样结构的 nodeID 中提取 project 与 zone,regional 卷则再把 zone 反推为 region,最终重写出完整 handle。测试文件 plugins/gce_pd_test.go、plugins/aws_ebs_test.go 等对这类路径均有覆盖。

如何在自己的组件中接入这套翻译逻辑

对希望编写 Out-of-Tree CSI 控制器(如 external provisioner、卷修复控制器)的开发者,接入方式非常直接——把k8s.io/csi-translation-lib作为依赖引入,然后:

  1. csitranslation.New()创建翻译器实例;
  2. 拿到底层对象前先做能力探测,例如IsPVMigratable(pv)IsMigratableIntreePluginByName("kubernetes.io/gce-pd"),避免对未知对象直接翻译返回 error;
  3. 需要正向迁移时调用TranslateInTreePVToCSI/TranslateInTreeStorageClassToCSI,输出 CSI 形态对象;需要回退时调用TranslateCSIPVToInTree
  4. 遇到 volume handle 不完整时调用RepairVolumeHandle(driverName, volumeHandle, nodeID)补齐。

仓库内控制器层的既有实现可作为接入范例:例如 attach/detach 控制器与 kubelet 卷管理器在启用迁移后,先通过翻译把 In-Tree spec 转成 CSI spec 再走 CSI 附加/挂载流程,其反向翻译则在特性门控回退场景下兜底。翻译前后的幂等与稳定性由测试保障——除前文TestTranslationStability外,plugins/gce_pd_test.go、plugins/aws_ebs_test.go 等还各自覆盖了 zonal/regional 卷 ID 解析、内联卷翻译、StorageClass 参数冲突等边界用例,可以作为自定义插件翻译器行为的参照。

社区、讨论、贡献与支持

该组件由 Kubernetes SIG-Storage 维护。本仓库的 README 特别说明:它是一个自动发布的 staged 仓库,问题与 PR 都应提交到主仓库 kubernetes/kubernetes(见 staging/README.md),本目录只读用于导入,不接受直接贡献。可以参与讨论的渠道包括:

  • Slack:#sig-storage(kubernetes.slack.com)
  • 邮件列表:kubernetes-sig-storage(google groups)

仓库内同样放置了 code-of-conduct.md 与 CONTRIBUTING.md,参与者需遵守 Kubernetes 社区行为准则与贡献规范。

小结

csi-translation-lib是整个 Kubernetes In-Tree 卷插件向 CSI 迁移链条上的"翻译枢纽":对外,它向 external provisioner 等 Out-of-Tree 组件开放一组稳定的TranslateToCSI/TranslateToInTree函数;对内,它与 pkg/volume/csimigration 及各类控制器、调度器深度耦合。理解其注册表结构(7 大插件的双向映射)、接口契约(InTreePlugin十方法)、门面层语义(DeepCopy 保护、内联卷 Filesystem 兜底)以及兼容性细节(访问模式收敛、Beta/GA 拓扑改写、volume handle 修复),就能在迁移排障、自定义 CSI 控制器接入或为新的 In-Tree 插件编写翻译器时准确判断"谁在什么条件下把什么对象翻译成了什么",这也是该库代码与测试最能帮助到你的地方。

【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询