什么是Fluid?
在云上通过云原生架构运行AI、大数据等任务,可以享受计算资源弹性的优势,但同时也会遇到,计算和存储分离架构带来的数据访问延迟和远程拉取数据带宽开销大的挑战。尤其在GPU深度学习训练场景中,迭代式的远程读取大量训练数据方法会严重拖慢GPU计算效率。
另一方面,Kubernetes只提供了异构存储服务接入和管理标准接口(CSI,Container Storage Interface),对应用如何在容器集群中使用和管理数据并没有定义。在运行训练任务时,数据科学家需要能够管理数据集版本,控制访问权限,数据集预处理,加速异构数据读取等。但是在Kubernetes中还没有这样的标准方案,这是云原生容器社区缺失的重要能力之一。
Fluid对“计算任务使用数据的过程”进行抽象,提出了弹性数据集Dataset的概念,并作为“first class citizen”在Kubernetes中实现。Fluid围绕弹性数据集Dataset,创建了数据编排与加速系统,来实现Dataset管理(CRUD操作)、权限控制和访问加速等能力。
Fluid中有两个最核心的概念:Dataset和Runtime。
- Dataset 是指数据集,是逻辑上相关的一组数据的集合,会被计算引擎使用,比如Spark,TensorFlow,PyTorch等等。
- Runtime 指的是提供分布式缓存的系统,目前 Fluid 支持的 Runtime 类型有 JuiceFS、Alluxio、JindoFS,GooseFS,其中 Alluxio、JindoFS 都是典型的分布式缓存引擎;JuiceFS是一款分布式文件系统,具备分布式缓存能力。这些缓存系统使用Kubernetes集群中节点上的存储资源(如:内存和磁盘)来作为远程存储系统的计算侧缓存。
为什么Fluid需要支持跨namespace共享?
Fluid最早的模式默认支持一个Dataset独占一个Runtime,可以理解为一个数据集就有专门的缓存集群加速。可以针对于数据集的特点,比如单文件大小特征,文件数量规模,客户端数量进行定制优化;并且提供单独的缓存系统。它能够提供最好的性能以及稳定性,并且不会互相干扰,但是它的问题在于硬件资源浪费,需要为每个不同的数据集部署缓存系统;同时运维复杂,需要管理多个缓存Runtime。这种模式其实本质上是单租户架构,适合于对于数据访问吞吐和延时高要求的场景。
当然随着Fluid使用的深入,也有不同的需求出现。比如用户会在多个不同的Namespace中创建数据密集型作业,且这些作业将访问相同的数据集。多个数据科学家共享相同的数据集,各数据科学家拥有自己独立的Namespace提交作业。如果对于每个Namespace重新部署缓存系统并进行缓存预热,那么就会造成数据冗余和作业启动延迟问题。
此时社区用户能够为了节省资源和运维简单降低对于性能的要求,就开始有了跨Namespace访问数据集的需求。跨namespace需求本质上是在呼唤多租户架构,即集群管理员将Runtime指向某个存储的根目录,多个数据科学家可以在不同的Namespace中创建多个Dataset共用同一个Runtime。更近一步,管理员可以为不同Namespace的数据科学家配置子目录和不同的读写权限。
共享Runtime模式 |
独占Runtime模式 |
|
性能 |
相对低 |
高 |
可靠性 |
相对低 |
高 |
隔离性 |
相对低 |
高 |
可定制配置能力 |
相对低 |
高 |
升级能力 |
升级简单,只需要更新一次,维护人员不需要对每个用户更新,节省了很大的运维成本。 |
可以控制升级的时间和方式,选择延迟甚至跳过升级周期。 |
运维复杂度 |
简单 |
多个数据集管理成本高 |
硬件成本 |
相对低 |
高 |
所有的架构选择上并不存在银弹,都是取舍。本文以AlluxioRuntime为例子向您演示如何使用Fluid共享Runtime。
使用样例:
想像一下,用户A在Kubernetes的命名空间development下对于数据集spark进行预热,用户B在另一个命名空间production中也同样需要访问数据集spark,通过Fluid可以帮助用户B在命名空间production中访问缓存过的数据,不需要二次预热,简化用户使用。可以做到一次预热,不同namespace的用户都得到收益。
- 在运行该示例之前,请参考安装文档完成安装(目前该功能存在于master分支)。并检查Fluid各组件正常运行:
NAME READY STATUS RESTARTS AGE csi-nodeplugin-fluid-mwx59 2/2 Running 0 5m46s csi-nodeplugin-fluid-tcbfd 2/2 Running 0 5m46s csi-nodeplugin-fluid-zwm8t 2/2 Running 0 5m46s dataset-controller-5c7557c4c5-q58bb 1/1 Running 0 5m46s fluid-webhook-67fb7dffd6-h8ksp 1/1 Running 0 5m46s fluidapp-controller-59b4fcfcb7-b8tx5 1/1 Running 0 5m46s
- 创建命名空间development
$ kubectl create ns development
- 在命名空间development创建Dataset和AlluxioRuntime,
$ cat<<EOF >dataset.yaml apiVersion data.fluid.io/v1alpha1 kind Dataset metadata name spark namespace development spec mountsmountPoint https //mirrors.bit.edu.cn/apache/spark/ name spark path"/"---apiVersion data.fluid.io/v1alpha1 kind AlluxioRuntime metadata name spark namespace development spec replicas1 tieredstore levelsmediumtype MEM path /dev/shm quota 4Gi high"0.95" low"0.7"EOF
- 查看数据集状态
$ kubectlget dataset -ANAMESPACE NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE development spark 3.41GiB 0.00B 4.00GiB 0.0% Bound 2m54s
- 在命名空间development创建Pod访问数据集
$ cat<<EOF >app.yaml apiVersion: v1 kind: Pod metadata: name: nginx namespace: development spec: containers: - name: nginx image: nginx volumeMounts: - mountPath: /data name: spark volumes: - name: spark persistentVolumeClaim: claimName: spark EOF $ kubectl create -f app.yaml
- 查看应用通过dataset可以访问的数据, 并且执行拷贝。可以发现拷贝1.4G数据(7个文件)花费了3分钟16秒。
$ kubectl exec -it-n development nginx --ls-ltr /data total 2dr--r----- 1 root root 6 Dec 415:39 spark-3.1.3 dr--r----- 1 root root 7 Dec 415:39 spark-3.2.3 dr--r----- 1 root root 7 Dec 415:39 spark-3.3.1 $ kubectl exec -it-n development nginx --bashroot@nginx:/# time cp -R /data/spark-3.3.1/* /tmpreal 3m16.761s user 0m0.021s sys 0m3.520s root@nginx:/# du -sh /tmp/1.4G /tmp/ root@nginx:/# du -sh /tmp/*348K /tmp/SparkR_3.3.1.tar.gz 269M /tmp/pyspark-3.3.1.tar.gz 262M /tmp/spark-3.3.1-bin-hadoop2.tgz 293M /tmp/spark-3.3.1-bin-hadoop3-scala2.13.tgz 286M /tmp/spark-3.3.1-bin-hadoop3.tgz 201M /tmp/spark-3.3.1-bin-without-hadoop.tgz 28M /tmp/spark-3.3.1.tgz
- 通过dataload对于指定数据集子目录进行加载
$ cat<<EOF >dataload.yaml apiVersion: data.fluid.io/v1alpha1 kind: DataLoad metadata: name: spark namespace: development spec: dataset: name: spark namespace: development target: - path: /spark-3.3.1 EOF $ kubectl create -f dataload.yaml
- 查看dataload状态
$ kubectlget dataload -ANAMESPACE NAME DATASET PHASE AGE DURATION development spark spark Complete 5m47s 2m1s
- 查看缓存效果, 可以看到38.4%的数据已经缓存完成
$ kubectlget dataset -n development NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE spark 3.41GiB 1.31GiB 4.00GiB 38.4% Bound 79m
- 再次拷贝1.4G数据仅耗时0.8秒不到1秒钟, 访问速度比之前的提升了245倍
$ kubectl exec -it-n development nginx --bashroot@nginx:/# time cp -R /data/spark-3.3.1/* /tmpreal 0m0.872s user 0m0.009s sys 0m0.859s root@nginx:/# du -sh /tmp/1.4G /tmp/ root@nginx:/# du -sh /tmp/*348K /tmp/SparkR_3.3.1.tar.gz 269M /tmp/pyspark-3.3.1.tar.gz 262M /tmp/spark-3.3.1-bin-hadoop2.tgz 293M /tmp/spark-3.3.1-bin-hadoop3-scala2.13.tgz 286M /tmp/spark-3.3.1-bin-hadoop3.tgz 201M /tmp/spark-3.3.1-bin-without-hadoop.tgz 28M /tmp/spark-3.3.1.tgz
- 创建production命名空间
$ kubectl create ns production
- 在 production 命名空间下,创建:
- 引用数据集spark,其mountPoint格式为dataset://${初始数据集所在namespace}/${初始数据集名称}, 在本例子中初始dataset所在
注: 当前引用的数据集,只支持一个mount,且形式必须为dataset://(即出现dataset://和其它形式时,dataset创建失败),Spec中其它字段无效;
$ cat<<EOF >spark-production.yaml apiVersion: data.fluid.io/v1alpha1 kind: Dataset metadata: name: spark namespace: production spec: mounts: - mountPoint: dataset://development/spark EOF $ kubectl create -f spark-production.yaml
- 查看数据集, 可以看到在production这命名空间下的spark数据集,并且都已经完成了数据缓存
$ kubectlkubectlget dataset -n production NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE spark 3.41GiB 1.31GiB 4.00GiB 38.4% Bound 14h
- 在production命名空间,创建Pod:
$ cat<<EOF >app-production.yaml apiVersion: v1 kind: Pod metadata: name: nginx namespace: production spec: containers: - name: nginx image: nginx volumeMounts: - mountPath: /data name: spark volumes: - name: spark persistentVolumeClaim: claimName: spark EOF $ kubectl create -f app-production.yaml
- 在production命名空间访问数据的耗时也是0.878s,
$ kubectl exec -it-n production nginx --ls-ltr /data total 2dr--r----- 1 root root 6 Dec 415:39 spark-3.1.3 dr--r----- 1 root root 7 Dec 415:39 spark-3.2.3 dr--r----- 1 root root 7 Dec 415:39 spark-3.3.1 $ kubectl exec -it-n production nginx --bashroot@nginx:/# ls -ltr /tmp/total 0root@nginx:/# time cp -R /data/spark-3.3.1/* /tmpreal 0m0.878s user 0m0.014s sys 0m0.851s root@nginx:/# du -sh /tmp1.4G /tmp root@nginx:/# du -sh /tmp/*348K /tmp/SparkR_3.3.1.tar.gz 269M /tmp/pyspark-3.3.1.tar.gz 262M /tmp/spark-3.3.1-bin-hadoop2.tgz 293M /tmp/spark-3.3.1-bin-hadoop3-scala2.13.tgz 286M /tmp/spark-3.3.1-bin-hadoop3.tgz 201M /tmp/spark-3.3.1-bin-without-hadoop.tgz 28M /tmp/spark-3.3.1.tgz
总结:
上面的例子展示了如何使用Fluid实现跨namespace共享数据集的能力,下一步Fluid会支持在Serverless Kubernetes上的跨Namespace数据集访问,实际上对于用户来说整个使用体验没有任何差别。
在近一步我们会支持Sub Dataset的能力,也就是将某个Dataset的子目录作为Dataset。能够实现同一套缓存,适合于不同的数据科学家,敬请期待。