PV 创建好了之后就到了 Mount 的过程,还是那张图,但是调用接口不一样,在 Mount 过程中参与的组件是 Kubelet 和 CSI Node。Kubelet 在创建 Pod 之前会去帮它准备各种各样的运行环境,包括需要声明存储的环境,这些环境准备好之后 Kubelet 才会去创建 Pod,那么准备 volume 的环境就是 Kubelet 去调用 CSI Node Plugin 的接口去实现的,在这个接口里面去实现 volume 的 mount 过程就完成了整个 Pod 所需要的一些存储环境。这些整个完成之后,Kubelet 的才会创建 Pod。
JuiceFS CSI Driver 的设计 CSI Driver 遇到的挑战JuiceFS CSI Driver 最初的架构设计是这样的,我们实现了 CSI 的几个接口和常见的基本一样,不同的就是我们在 NodeService 组件里面,我们会去实现 JuiceFS mount 这个过程。
由于 JuiceFS 是用户态文件系统,CSI 在完成 Mount 工作的时候,首先是会在节点上创建一个挂载点,同时会 fork 出一个 mount 进程,也就是 JuiceFS 客户端。而进程它是直接运行在 CSI Node 的 Pod 里的,挂载点准备好之后,我们还会在接口里面把这个机器上的挂载点 bind 到 kubelet target 路径。这个进程直接运行在 CSI Pod 里,有多少 Volume 就会有多少进程同时运行在这个 Pod 里,这样的实现就会带来一系列的问题。
首先,JuiceFS 客户端之间没有资源隔离,而且进程直接运行在 CSI Pod 里会导致 Kubernetes 集群对客户端进程无感知,当客户端进程意外退出的时候,在集群中是看不出任何变化的;最关键的是 CSI Driver 不能平滑升级,升级的唯一方式就是,先把用户所有使用到 JuiceFS 的 Pod 全部停掉,然后升级,升级完再把所有的业务 Pod 一个个再运行起来。这样的升级方式对于运维同学来说简直是灾难;另外一个问题是 CSI Driver 的爆炸半径过大,跟第三点类似,CSI Driver 一旦退出,那运行在里面的 JuiceFS 客户端都不能正常使用。
CSI Driver 架构升级针对这些问题,我们对 CSI Driver 的架构设计进行了一些改进。具体的做法就是将执行 volume 挂载的操作在单独的 Pod 里执行,这样 fork 出来的进程就可以运行在 Pod 里了。如果有多个的业务 Pod 共用一份存储,mount Pod 会在 annotation 进行引用计数,确保不会重复创建。每当有业务 Pod 退出时,mount Pod 会删除对应的计数,只有当最后一个记录被删除时 mount Pod 才会被删除。还有一点是 CSI Node 会通过 APIService watch mount Pod 的状态变化,以管理其生命周期。我们一旦观察到它意外退出,及它 Pod 的退出了,但是它的 annotation 还有计数,证明它是意外退出,并不是正常的一个被删除,这样的话我们会把它重新起来,然后在业务的容器的 target 路径重新执行 mount bind,这是我们目前还在开发的一个功能。
架构升级的益处最直接的好处就是 CSI Driver 被解耦出来了,CSI driver无论做什么操作都不会影响到客户端,做升级不会再影响到业务容器了,
将 JuiceFS 客户端独立在 Pod 中运行也就使其在 Kubernetes 的管控内,可观测性更强;
同时 Pod 的好处我们也能享受到,比如隔离性更强,可以单独设置客户端的资源配额等。
未来展望对于未来我们还会去做一些工作,目前我们把 JuiceFS 客户端的进程运行在单独的 Pod 里,但是对于 FUSE 进程在容器环境的高可用性依然存在很大的挑战,这也是我们今后会一直关注探讨的问题。另外我们也在持续摸索对于 JuiceFS 在云原生环境更多的可能性,比如使用方式上面除了 CSI 还会有 Fluid、Paddle-operator 等方式。
推荐阅读:
百亿级小文件存储,JuiceFS 在自动驾驶行业的最佳实践
项目地址: Github (https://github.com/juicedata/juicefs)如有帮助的话欢迎关注我们哟! (0ᴗ0✿)