什么是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需要支持子数据集(subDataset)?
Fluid最早的模式默认支持一个Dataset独占一个Runtime,可以理解为一个数据集就有专门的缓存集群加速。可以针对于数据集的特点,比如单文件大小特征,文件数量规模,客户端数量进行定制优化;并且提供单独的缓存系统。它能够提供最好的性能以及稳定性,并且不会互相干扰,但是它的问题在于硬件资源浪费,需要为每个不同的数据集部署缓存系统;同时运维复杂,需要管理多个缓存Runtime。这种模式其实本质上是单租户架构,适合于对于数据访问吞吐和延时高要求的场景。
当然随着Fluid使用的深入,也有不同的需求出现。其中社区一个比较共性的需求:
-
可以跨namespace访问数据集缓存
-
只允许用户访问数据集的某个子目录
特别是JuiceFS的用户,他们倾向于使用Dataset指向JuiceFS的根目录。然后对于不同数据科学家组分配不同的子目录作为不同的数据集,并且希望彼此间的数据集不可见;同时还支持子数据集的权限收紧,比如根数据集支持读写,子数据集可以收紧为只读。
本文以AlluxioRuntime为例子向您演示如何使用Fluid支持子数据集能力。
使用样例:
想像一下,用户A在Kubernetes的命名空间spark下创建数据集spark, 该数据集包含spark的三个版本,希望团队A只能看到数据集spark-3.1.3, 团队B只能看到数据集spark-3.3.1。
.
|-- spark-3.1.3
| |-- SparkR_3.1.3.tar.gz
| |-- pyspark-3.1.3.tar.gz
| |-- spark-3.1.3-bin-hadoop2.7.tgz
| |-- spark-3.1.3-bin-hadoop3.2.tgz
| |-- spark-3.1.3-bin-without-hadoop.tgz
| `-- spark-3.1.3.tgz
|-- spark-3.2.3
| |-- SparkR_3.2.3.tar.gz
| |-- pyspark-3.2.3.tar.gz
| |-- spark-3.2.3-bin-hadoop2.7.tgz
| |-- spark-3.2.3-bin-hadoop3.2-scala2.13.tgz
| |-- spark-3.2.3-bin-hadoop3.2.tgz
| |-- spark-3.2.3-bin-without-hadoop.tgz
| `-- spark-3.2.3.tgz
`-- spark-3.3.1
|-- SparkR_3.3.1.tar.gz
|-- pyspark-3.3.1.tar.gz
|-- spark-3.3.1-bin-hadoop2.tgz
|-- spark-3.3.1-bin-hadoop3-scala2.13.tgz
|-- spark-3.3.1-bin-hadoop3.tgz
|-- spark-3.3.1-bin-without-hadoop.tgz
`-- spark-3.3.1.tgz
这样就实现了不同团队可以访问不同子数据集,并且彼此间的数据不可见。
阶段1: 管理员创建数据集,指向根目录
kubectl get po -n fluid-system
NAME READY STATUS RESTARTS AGE
alluxioruntime-controller-5c6d5f44b4-p69mt 1/1 Running 0 149m
csi-nodeplugin-fluid-bx5d2 2/2 Running 0 149m
csi-nodeplugin-fluid-n6vbv 2/2 Running 0 149m
csi-nodeplugin-fluid-t5p8c 2/2 Running 0 148m
dataset-controller-797868bd7f-g4slx 1/1 Running 0 149m
fluid-webhook-6f6b7dfd74-hmsgw 1/1 Running 0 149m
fluidapp-controller-79c7b89c9-rtdg7 1/1 Running 0 149m
thinruntime-controller-5fdbfd54d4-l9jcz 1/1 Running 0 149m
-
创建命名空间spark
$ kubectl create ns spark
-
在命名空间development创建Dataset和AlluxioRuntime, 其中数据集为可读写
$ cat<<EOF >dataset.yaml
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: spark
namespace: spark
spec:
mounts:
- mountPoint: https://mirrors.bit.edu.cn/apache/spark/
name: spark
path: "/"
accessModes:
- ReadWriteMany
---
apiVersion: data.fluid.io/v1alpha1
kind: AlluxioRuntime
metadata:
name: spark
namespace: spark
spec:
replicas: 1
tieredstore:
levels:
- mediumtype: MEM
path: /dev/shm
quota: 4Gi
high: "0.95"
low: "0.7"
EOF
$ kubectl create -f dataset.yaml
-
查看数据集状态
$ kubectl get dataset -n spark
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE
spark 3.41GiB 0.00B 4.00GiB 0.0% Bound 61s
-
在命名空间spark创建Pod访问数据集
$ cat<<EOF >app.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: spark
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /data
name: spark
volumes:
- name: spark
persistentVolumeClaim:
claimName: spark
EOF
$ kubectl create -f app.yaml
-
查看应用通过dataset可以访问的数据, 可以看到3个文件夹:spark-3.1.3, spark-3.2.3, spark-3.3.1, 同时可以看到 挂载权限为RW( ReadWriteMany )
$ kubectl exec -it -n spark nginx -- bash
mount | grep /data
alluxio-fuse on /data type fuse.alluxio-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,max_read=131072)
root@nginx:/# ls -ltr /data/
total 2
dr--r----- 1 root root 6 Dec 16 07:25 spark-3.1.3
dr--r----- 1 root root 7 Dec 16 07:25 spark-3.2.3
dr--r----- 1 root root 7 Dec 16 07:25 spark-3.3.1
root@nginx:/# ls -ltr /data/spark-3.1.3/
total 842999
-r--r----- 1 root root 25479200 Feb 6 2022 spark-3.1.3.tgz
-r--r----- 1 root root 164080426 Feb 6 2022 spark-3.1.3-bin-without-hadoop.tgz
-r--r----- 1 root root 231842529 Feb 6 2022 spark-3.1.3-bin-hadoop3.2.tgz
-r--r----- 1 root root 227452039 Feb 6 2022 spark-3.1.3-bin-hadoop2.7.tgz
-r--r----- 1 root root 214027643 Feb 6 2022 pyspark-3.1.3.tar.gz
-r--r----- 1 root root 347324 Feb 6 2022 SparkR_3.1.3.tar.gz
root@nginx:/# ls -ltr /data/spark-3.2.3/
total 1368354
-r--r----- 1 root root 28375439 Nov 14 18:47 spark-3.2.3.tgz
-r--r----- 1 root root 209599610 Nov 14 18:47 spark-3.2.3-bin-without-hadoop.tgz
-r--r----- 1 root root 301136158 Nov 14 18:47 spark-3.2.3-bin-hadoop3.2.tgz
-r--r----- 1 root root 307366638 Nov 14 18:47 spark-3.2.3-bin-hadoop3.2-scala2.13.tgz
-r--r----- 1 root root 272866820 Nov 14 18:47 spark-3.2.3-bin-hadoop2.7.tgz
-r--r----- 1 root root 281497207 Nov 14 18:47 pyspark-3.2.3.tar.gz
-r--r----- 1 root root 349762 Nov 14 18:47 SparkR_3.2.3.tar.gz
阶段2: 管理员创建子数据集,指向目录spark 3.1.3
-
创建命名空间spark-313
$ kubectl create ns spark-313
-
管理员在 spark-313 命名空间下,创建:
- 引用数据集spark,其mountPoint格式为dataset://${初始数据集所在namespace}/${初始数据集名称}/子目录, 在本例子中应该为dataset://spark/spark/spark-3.1.3
注: 当前引用的数据集,只支持一个mount,且形式必须为dataset://(即出现dataset://和其它形式时,dataset创建失败),数据集权限为读写;
$ cat<<EOF >spark-313.yaml
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: spark
namespace: spark-313
spec:
mounts:
- mountPoint: dataset://spark/spark/spark-3.1.3
accessModes:
- ReadWriteMany
EOF
$ kubectl create -f spark-313.yaml
-
查看dataset状态
$ kubectl get dataset -n spark-313
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE
spark 3.41GiB 0.00B 4.00GiB 0.0% Bound 108s
-
用户在 spark-313 命名空间,创建Pod:
$ cat<<EOF >app-spark313.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: spark-313
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /data
name: spark
volumes:
- name: spark
persistentVolumeClaim:
claimName: spark
EOF
$ kubectl create -f app-spark313.yaml
-
在spark-313命名空间访问数据, 只能看到spark-3.1.3文件夹中的内容,并且可以 看到pvc权限RWX
$ kubectl get pvc -n spark-313
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
spark Bound spark-313-spark 100Gi RWX fluid 21h
$ kubectl exec -it -n spark-313 nginx -- bash
root@nginx:/# mount | grep /data
alluxio-fuse on /data type fuse.alluxio-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,max_read=131072)
root@nginx:/# ls -ltr /data/
total 842999
-r--r----- 1 root root 25479200 Feb 6 2022 spark-3.1.3.tgz
-r--r----- 1 root root 164080426 Feb 6 2022 spark-3.1.3-bin-without-hadoop.tgz
-r--r----- 1 root root 231842529 Feb 6 2022 spark-3.1.3-bin-hadoop3.2.tgz
-r--r----- 1 root root 227452039 Feb 6 2022 spark-3.1.3-bin-hadoop2.7.tgz
-r--r----- 1 root root 214027643 Feb 6 2022 pyspark-3.1.3.tar.gz
-r--r----- 1 root root 347324 Feb 6 2022 SparkR_3.1.3.tar.gz
阶段3: 管理员创建子数据集,指向目录spark 3.3.1
-
创建命名空间spark-331
$ kubectl create ns spark-331
-
管理员在 spark-331 命名空间下,创建:
- 引用数据集spark,其mountPoint格式为dataset://${初始数据集所在namespace}/${初始数据集名称}/子目录, 在本例子中应该为dataset://spark/spark/spark-3.3.1
注: 当前引用的数据集,只支持一个mount,且形式必须为dataset://(即出现dataset://和其它形式时,dataset创建失败),并且指定读写权限为只读ReadOnlyMany;
$ cat<<EOF >spark-331.yaml
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: spark
namespace: spark-331
spec:
mounts:
- mountPoint: dataset://spark/spark/spark-3.3.1
accessModes:
- ReadOnlyMany
EOF
$ kubectl create -f spark-331.yaml
-
用户在 spark-331 命名空间,创建Pod:
$ cat<<EOF >app-spark331.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: spark-331
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /data
name: spark
volumes:
- name: spark
persistentVolumeClaim:
claimName: spark
EOF
$ kubectl create -f app-spark331.yaml
-
在spark-331命名空间访问数据, 只能看到spark-3.3.1文件夹中的内容, 并且可以 看到PVC权限ROX( ReadOnlyMany )
$ kubectl get pvc -n spark-331
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
spark Bound spark-331-spark 100Gi ROX fluid 14m$ kubectl exec -it -n spark-331 nginx -- bash
mount | grep /data
alluxio-fuse on /data type fuse.alluxio-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,max_read=131072)
root@nginx:/# ls -ltr /data/
total 842999
-r--r----- 1 root root 25479200 Feb 6 2022 spark-3.1.3.tgz
-r--r----- 1 root root 164080426 Feb 6 2022 spark-3.1.3-bin-without-hadoop.tgz
-r--r----- 1 root root 231842529 Feb 6 2022 spark-3.1.3-bin-hadoop3.2.tgz
-r--r----- 1 root root 227452039 Feb 6 2022 spark-3.1.3-bin-hadoop2.7.tgz
-r--r----- 1 root root 214027643 Feb 6 2022 pyspark-3.1.3.tar.gz
-r--r----- 1 root root 347324 Feb 6 2022 SparkR_3.1.3.tar.gz
总结:
在本例子中,我们展示了Sub Dataset的能力,也就是将某个Dataset的子目录作为Dataset。能够实现同一套数据集,根据不同的细分策略,被不同的数据科学家使用。