juicefs-csi中pod mount的annotation与label分析

juicefs-csi中pod mount的annotation与label分析

juicefs-csi实现了三种挂载方式:pod mount, sidecar mount, process mount。最常用的便是pod mount,这种方式支持带业务平滑升级,并且支持多个业务pod挂载点由同一个mount pod管理,从而节约资源开销。

本文将通过对pod mount的annotion和label,以及相关的实现,来进一步深入理解juicefs-csi的设计思想。

参考:

  1. juicefs.com/docs/zh/csi...
  2. github.com/juicedata/j...

annotations

1. juicefs-<HASH>: 宿主机的挂载点路径

RefrenceKey,用于描述mount pod对业务pod的引用信息。

eg: juicefs-e0b59010f7a24ee15c8625a12727a21ef13f0951f55da74a2edf56a /var/lib/kubelet/pods/566a11ab-6af1-4597-ab5c-14735974f7ce/volumes/kubernetes.io~csi/juicefs-pv2/mount

此处的HASH是根据挂载点路径通过GetReferenceKey(target)计算出来的。

这个annotation用于描述当前mount pod所需维护的挂载点。若只有一个业务pod通过 PV的方式引用到这个mount pod,则只会有一个。若有多个业务pod通过引用同一个PV的方式引用到这个mount pod,则会有多个。【另:这种用法其实不建议,因为缺乏分布式锁,业务对相同资源可能会相互覆盖,除非业务自己对文件有锁保护】

可以用于定位某个挂载点路径,从而实现需要该路径的逻辑,如:

  • 添加时机:在PodMount的createOrAddRef(ctx, podName,jfsSetting,appinfo)中,在需要新建mount pod的时候会将jfsSettin.TargetPath作为value,赋值给juicefs-<HASH_OF_TARGET_PATH>的key的annotation中。

可以用于定位某个挂载点路径,从而实现需要该路径的逻辑,如:

  • 删除时机:在PodDriver checkAnnotations(ctx, pod)中,遍历该pod所有的Annotations得到k,v, 若k==GetReferenceKey(v),则说明这个pod包含这个引用信息。之后在podDriver.mit.deletedPods[targetUid]表中查询,若该target已经被标记为删除状态,则可以删除当前的annotation,表示不再被该target引用。

  • 修改时机:在PodDriver的applyConfigPatch(ctx,pod)中,获取老的pod中的anno,更新到新的pod中。

  • 在PodDriver podDeletedhandler(ctx, pod)中,会遍历所有anno,若发现是reference key,则记录到existTarget\[k]:v的map类型的局部变量中。若该map的长度为0,且这个uniqueId没有可用的mount pod (根据uniqueId和upgradeUUID,无法在podDriver.uniqueIdIndex[uinqueId]中找到status为podDeletedpodComplete状态),则调用cleanBeforeDeleted(ctx,pod)进行清理。

    • 通过pod获取挂载点

    • umount path

    • 清理挂载点

    • 清理cache

    • 关闭fd,清理 socket。(调用passfd.GlobalFds.StopFd(ctx, pod))。

  • 在PodDriver recover(ctx,pod,mntPath)中,会遍历所有pod的anno,对于reference key,会对挂载点进行恢复:

    • 解析挂载点target: p.mit.resolveTarget(ctx, target)

    • 恢复挂载点:p.recoverTarget(ctx, pod.Name, mntPath, mi.baseTarget, mi)

      • 根据ti.status的不同状态,进行不同的处理:

      • targetStatusNotExist,但target在/proc/self/mountinfo文件中。场景为本地target存在,但target bind的源不存在,会导致用户删除pod失败。此处处理一下可以避免此场景。

      • targetStatusCorrupt,会先判断几种意外情况(如source path /proc/self/mountinfo和bind不一致、pod被删除)跳出,之后进行挂载点恢复:

        • umount target,否则mountinfo文件会无限增加,需要通过umount清理无效mountinfo。

        • 对于subPath的处理

        • 调用p.Mount(sourcePath, ti.target,"none",mountOption)进行挂载。

      • 遍历子路径,恢复挂载点:err := p.recoverTarget(ctx, pod.Name, mntPath, ti, mi);

  • 在podMount的UmountTarget(ctx,target,podName)时,会先调用CleanupMountPoint(),然后根据target获取refrenceKey,若存在,则删除此anno。

  • 在podMount的AddRefOfMount(ctx,target,podName)中,增加新的refrenceKey的anno。

  • 在webhook的EvictPodHandler(ctx,request)中,会判断如果有refrenceKey,则不去驱逐pod。

2. juicefs-uniqueid: <PV_volumeHandler>

若为static provision,则是volumeHandler(即pv name);若为dynamic provision,则为Storage Class Name。这在集群中肯定是唯一的。

eg: juicefs-uniqueid: juicefs-pv2

这个anno可以做以下事情:

  • 所有需要获取pvName,以及通过pvName做一些操作的(如拼接挂载点路径),都可以由此获得。

  • 当集群中有许多mount pod,需要找到某个mount pod对应的挂载点路径时,通过这个uniqueid进行过滤匹配。

这个anno的值实际上和label: volume-id的值是相同的。

相关设计:

  • 设置时机:在podMount JMount()时,新建mount pod后会打上volume-id的label。

  • PodDriver结构体维护了一个uniqueIdIndex[string][]podWithUpgradeUUID

golang 复制代码
type PodDriver struct {
    lock sync.Mutex
    Client *k8sclient.K8sClient
    handlers map[podStatus]podHandler
    uniqueIdIndex map[string][]podWithUpgradeUUID
    mit mountInfoTable
    mount.SafeFormatAndMount
}

type podWithUpgradeUUID struct {
    upgradeUUID string
    status podStatus
}

这个结构体只在controller/pod_driver.go中使用。controller pod中进程如下:

shell 复制代码
juicefs-csi-driver --endpoint=unix:///var/lib/csi/sockets/pluginproxy/csi.sock --logtostderr --nodeid=vm-146-97-tencentos --leader-election --config=/etc/config/config.yaml

在controller的Reconcile()过程中进行增删改查。

调用controller Reconcile()接口的时机是mount pod的创建、状态变化、配置更新、删除、节点故障恢复等场景。PodController是个全局实例。用于管理pod的生命周期。

  • uniqueIdIndex 添加时机:在Controller启动时,NewPodDriver(k8sClient, mounter, poolList)中,会遍历所有的podList,若有volume-id的label,则会添加到driver.uniqueIdIndex[uniqueId]中,其value为podWithUpgradeUUID{ upgradeUUID, status}。

  • uniqueIdIndex 添加时机2: 在PodDriver recordPod(uniqueId, upgradeUUID)中,会将传入的uniqueId, upgradeUUID添加到p.uniqueIdIndex\[uniqueId]中。而调用recordPod()的时机为podCompletehandler()podDeletedHandler()

  • uniqueIdIndex 查询时机:可以通过getUniqueMountPod(ctx, uniqueId)来检查指定uniqueId的pod是否存在。若该表中能查到,且状态不是podDeletedpodComplete,则说明该mount pod存在。否则不存在。这主要用于在PodDriver cleanBeforeDeleted(ctx, pod)中做清理使用。当指定uniqueId的pod不存在,才能清理cache。

3. juicefs-uuid: <HASH>

eg: juicefs-uuid: 67d847ef-acb2-420e-8664-d6d0e4f434a2

  • 设置时机:在pod\_mount.go中,JMount()的时候通过setUUIDAnnotation(ctx, podName, uuid)来设置。uuid通过GetJfsVolUUID(ctx, jfsSetting)方法生成,本质上是获取juicefs status中的UUID字段。这是由juicefs生成的唯一字段,可以用于区分文件系统。

    • 之后会将其保存到配置文件jfsSetting中。
  • 使用时机:在PodDriver CleanUpCache(ctx,pod)中,会通过anno获取uuid和uniqueId,然后调用podMnt.CleanCache(ctx,image,uuid,uniqueId,cacheDirs)清理缓存。

    • 对于PodMount,会获取配置文件jfsSetting,然后创建一个Job(具体定义在genCleanCachePod()中),用于清理rm -rf /var/jfsCache/\*/chunks,对应于hostPath的具体目录。

    • 对于ProcessMount。待续。

  • 缓存的管理:cachedir-0(详见cache.md)

    • 允许在mountPodPatch-mountOptions中,通过cache-dir和cache-size配置hostPath形式的缓存目录。默认目录为/var/jfsCache,是系统目录,因此建议配置为指定的非系统盘目录。

      • 使用hostPath的好处在于管理和观测都方便,不过mount pod可能随着业务容器被分配到其他节点,这样的话缓存就会失效,并且原有的缓存也需要清理。配置方法:

      • 但如果集群中的所有节点都用于运行mount pod,那么由于每个宿主机都持有大致相同的缓存,那么pod漂移问题可能也影响不大。

    • 也可以使用PVC作为缓存目录。这种形式可以有很好的隔离性,并且即使业务pod漂移,由于PVC绑定关系不变,缓存也是有效的。

      • 不过这里的PVC得是云服务提供,从而在任意节点都能访问到,而不能是hostPath形式(自然)。

      • 感觉这种场景应该不太多,缓存本身是为了加速,这里引入外部云存储的依赖,会变成拖累。

  • 缓存的清理:因为缓存是昂贵的,因此设计上mount pod退出时默认不会清理缓存。可以通过配置PV的volumeAttributes: juicefs/clean-cache:"true"的方式实现退出时默认清理缓存。

  • 由PodMount CleanCache()定义清理缓存步骤。如上述所示。

labels

1. app.kubernetes.io/name: juicefs-mount

用于过滤mount pod。

当label selector需要查找juicefs mount pod的时候,不用全量遍历pod,而是通过这个label来过滤。

2. juicefs-hash: <HASH>

juicefs-hash用于描述配置的改动。

通过GenHashOfSetting(JfsSetting)来生成,简单来讲就是将JfsSetting的key按照固定规则排序,然后将其内容进行哈希,得到一个哈希值。

eg: juicefs-hash: 1733a399cf220ea29a9bc6edd6e95e40b0aeb94fa999a21bcb3dc3856e8273b

若juicefs-hash为空,则表示没有配置,是个异常场景(在NewPodUpgrade() 中有检查)。

setting.HashVal与pod label中的juicefs-hash不同,则说明有配置变更,需要做一些对应操作。如更新secret、在genMountPodName时用于清理过时的引用。

3. juicefs-upgrade-uuid: <HASH>

在pod_mount.go的JMount()的时候生成。这个生成没有什么讲究,直接用uuid.NewUUID()来生成,然后保存在jfsSettin.UpgradeUUID中。

对于平滑升级前后的mount pod来说,这个label是相同的。那么这是如何实现的呢?

是通过GenSettingAttrWithMountPod(ctx,client,mountPod),从旧mount pod获取所有配置,保存在JfsSetting中。在处理的时候会优先使用旧mount pod的label对应的值。若为空,则会使用juicefs-hash对应的值。(感觉像是为了兼容旧版本?)

newMountPod(ctx, pod, newPodName) -> applyConfigPatch(ctx, pod)的时候调用。

eg: juicefs-upgrade-uuid: c3bc9d3c-fa0a-494e-9f17-58683c00ea70

为什么需要这个标签呢?

  • 会通过GetUpgradeUUID(pod)获取某个pod的upgradeUUID

  • 会通过GetCommPath(bashPath, pod)结合upgradeUUID来拼接获取fuse_fd_comm.1的路径。这样新旧mount pod都可以找到相同的路径。

  • 在全局的表结构Fds中,维护一个map: fds[upgradeUUID]: *fd。用于记录通过upgradeUUID区分开来的所有mount pod的fd表。

go 复制代码
type Fds struct {
    client  *k8s.K8sClient
    globalMu sync.Mutex
    basePath string
    fds  map[string]*fd
}

type fd struct {
    done chan struct{}
    fuseFd  int
    fuseSetting []byte
    sid  uint64
    serverAddress  string // server for pod
    serverAddressInPod string // server path in pod
}

4. volume-id: juicefs-pv2

对应于PV定义中的volumeHandler.

相关: uniqueId通过getUniqueId(ctx, volumeId)来计算。当是dynamic provision时,UniqueId为SC name。当为static provision时,UniqueId为volumeId。

pkg/controller/pod_driver.go中会维护driver.uniqueIdIndex[unqiueId]: podWithUpgradeUUID{ upgradeUUID, status}的映射关系,用于便捷地查询某个mount pod的状态,如getAvailableMountPod(uniqueId, upgradeUUID)

相关推荐
无敌糖果3 小时前
K8S的Pod之initC容器restartPolicy新特性
云原生·容器·kubernetes·pod·restartpolicy·容器重启
G皮T4 小时前
【云计算】云主机的亲和性策略(一):快乐旅行团
云原生·云计算·公有云·集群架构·云主机·亲和性·反亲和性
Cyber4K5 小时前
MySQL--组从复制的详解及功能演练
运维·数据库·mysql·云原生
东风微鸣7 小时前
GitOps:云原生时代的革命性基础设施管理范式
docker·云原生·kubernetes·可观察性
David爱编程8 小时前
Kubernetes NetworkPolicy 实践与策略误区
云原生·容器·kubernetes
FJW0208148 小时前
Mysql集成技术
linux·mysql·云原生
Gold Steps.12 小时前
K8S周期性备份etcd数据实战案例
云原生·kubernetes·数据安全·etcd
G皮T12 小时前
【云计算】云主机的亲和性策略(四):云主机组
云原生·云计算·云服务器·云主机·亲和性·反亲和性·调度策略