摘要
依赖容器化带来的高效部署、敏捷迭代,以及云计算在资源成本和弹性扩展方面的天然优势,以 Kubernetes 为代表的云原生编排框架吸引着越来越多的 AI 与大数据应用在其上部署和运行。但是数据密集型应用计算框架的设计理念和云原生灵活的应用编排的分歧,导致了数据访问和计算瓶颈。
CNCF开源项目Fluid作为 AI 与大数据云原生应用提供一层高效便捷的数据抽象,将数据从存储抽象出来,针对具体的场景(比如大模型),加速计算访问数据。
Fluid提供了可灵活配置的分层数据本地性调度能力。在云平台和数据中心的场景下,可以结合数据集缓存的位置信息对于使用该数据集的任务进行调度,无需用户了解底层数据缓存的排布的前提下,将任务优先调度到和数据传输距离较近的节点。
- Fluid为什么要支持分层的数据本地性调度能力?因为云原生体系下,计算存储分离的架构是业界的主流,它可以带来更好的架构灵活性和成本上的优势,但是计算访问数据的性能也随之受到影响。解决这一问题一个很直接的想法就是云上或者数据中心引入缓存层,在计算侧部署一个缓存或者分布式存储。但是具体实践中,这并不能保证带来更好的性能收益,其中一个重要的原因就是用户并没有意识到实际部署物理位置差异带来的网络延迟和吞吐受限。如何解决这个问题呢?Fluid参考大数据领域的本地性调度策略。在大数据领域中,有一个著名的概念是"移动数据不如移动计算"。这是因为数据在网络中的传输会增加较大的I/O开销。为了提高效率,需要尽量减少I/O开销,即尽量避免数据在网络中传输。即使数据必须在网络上传输,也要尽量缩短传输距离,而数据本地性就是衡量这个传输距离的概念。
而Fluid在将数据的分布式缓存部署到Kubernetes集群后,就可以根据数据缓存本地性传输距离的远近进而划分为不同级别。当数据能够在本地计算节点上进行计算,而无需在网络上进行传输时,就达到了最佳的数据本地性级别。如果无法实现最佳本地性,则根据数据的传输距离划分为不同的级别,比如同一个节点(Node),机架(Rack),可用区(Availability Zone),区域(Region)。传输距离越远,数据本地性级别越低,延时的影响会越高。
加图
- Fluid为什么要支持分层的数据本地性调度能力的可灵活配置性?因为不同的公共云对于不同亲和性有不同的定义,比如AWS支持Placement groups,阿里云支持Deployment Set,这些不同于Kubernetes内置的标签topology.kubernetes.io/zone,topology.kubernetes.io/region;同时在自建数据中心中对于同一个机架等特有概念会有不同的标签;同时一些低版本Kubernetes中关于zone和region也有不同的标签。如果有特定的需求,可以在部署和升级Fluid时进行配置。
在此基础上,Fluid提供了分层数据本地性调度能力。Fluid负责数据集缓存本身的编排调度和数据亲和性调度,在部署缓存Pod时优先反亲和性,这样可以保证每个缓存的worker能够充分使用带宽。同时在调度使用该数据集的应用Pod时,则会根据分层的数据亲和性,优先调度到缓存所在的节点,当条件不满足的时候则会调度到同一个可用区的节点,这样可以避免跨可用区的数据访问。
演示:
本演示介绍如何通过ACK Fluid的分层数据缓存亲和性调度确保缓存数据和计算任务运行在同一个可用区,
本实验分为三部分:
1.软约束调度,优先将使用分布式缓存的Pod调度到和分布式缓存同一个地域的节点,如果条件不满足的话,最终调度仍然会成功
2.强制调度,必须将使用分布式缓存的Pod调度到和分布式缓存同一个地域的节点,直到条件满足,最终调度才会成功
3.修改整体调度策略配置,可以针对定制分层亲和调度的条件修改强制调度的。
前提条件
- 已创建ACK Pro版集群,且集群版本为1.18及以上。具体操作,请参见创建ACK Pro版集群。
- 已安装云原生AI套件并部署ack-fluid组件。
重要 若您已安装开源Fluid,请卸载后再部署ack-fluid组件。
- 未安装云原生AI套件:安装时开启Fluid数据加速。具体操作,请参见安装云原生AI套件。
- 已安装云原生AI套件:在容器服务管理控制台的云原生AI套件页面部署ack-fluid。
- 已通过kubectl连接Kubernetes集群。具体操作,请参见通过kubectl工具连接集群。
背景信息
准备好K8s和OSS环境的条件,您只需要耗费10分钟左右即可完成JindoRuntime环境的部署。
环境准备:
步骤一:查看Kubernetes节点信息
在本次实验环境中,包含三个节点,其中节点cn-beijing.192.168.125.127运行在阿里云北京可用区b,cn-beijing.192.168.58.146和 cn-beijing.192.168.58.147运行在阿里云北京可用区l 。
$ kubectl get no -o custom-columns="NAME:.metadata.name,ZONE:.metadata.labels.topology\.kubernetes\.io/zone" NAME ZONE cn-beijing.192.168.125.127 cn-beijing-b cn-beijing.192.168.58.146 cn-beijing-l cn-beijing.192.168.58.147 cn-beijing-l
查看调度策略
kubectl get cm -n fluid-system tiered-locality-config -oyaml apiVersion: v1 data: tieredLocality: | preferred: - name: fluid.io/node weight: 100 - name: topology.kubernetes.io/zone weight: 50 - name: topology.kubernetes.io/region weight: 20 required: - fluid.io/node kind: ConfigMap metadata: annotations: meta.helm.sh/release-name: fluid meta.helm.sh/release-namespace: default labels: app.kubernetes.io/managed-by: Helm name: tiered-locality-config namespace: fluid-system
步骤二:准备OSS Bucket的数据
- 执行以下命令,下载一份测试数据。
$ wget https://archive.apache.org/dist/hbase/2.5.2/RELEASENOTES.md
$ ossutil cp RELEASENOTES.md oss://<bucket>/<path>/RELEASENOTES.md
步骤三:创建Dataset和JindoRuntime
- 创建一个mySecret.yaml文件,用于保存OSS的accessKeyId和accessKeySecret。YAML示例如下所示。
apiVersion: v1 kind: Secret metadata: name: mysecret stringData: fs.oss.accessKeyId: ****** # 请输入accessKeyId。 fs.oss.accessKeySecret: ****** # # 请输入accessKeySecret。
- 执行以下命令,生成Secret。
kubectl create -f mySecret.yaml
预期输出:
secret/demo created
- 创建一个dataset.yaml文件,用于创建Dataset。展开查看YAML示例
apiVersion: data.fluid.io/v1alpha1 kind: Dataset metadata: name: demo spec: mounts: - mountPoint: oss://<bucket-name>/<path> options: fs.oss.endpoint: <oss-endpoint> name: demo path: "/" encryptOptions: - name: fs.oss.accessKeyId valueFrom: secretKeyRef: name: mysecret key: fs.oss.accessKeyId - name: fs.oss.accessKeySecret valueFrom: secretKeyRef: name: mysecret key: fs.oss.accessKeySecret --- apiVersion: data.fluid.io/v1alpha1 kind: JindoRuntime metadata: name: demo spec: replicas: 1 master: nodeSelector: topology.kubernetes.io/zone: cn-beijing-l worker: nodeSelector: topology.kubernetes.io/zone: cn-beijing-l tieredstore: levels: - mediumtype: MEM path: /dev/shm quota: 20Gi high: "0.99" low: "0.8"
重要:如果想使用分层亲和性调度,在指定缓存部署时务必要对于worker角色配置nodeSelector或者nodeAffinity。
相关参数解释如下表所示:
参数 |
说明 |
|
Dataset |
mountPoint |
oss://<oss_bucket>/<path>表示挂载UFS的路径,路径中不需要包含Endpoint信息。 |
fs.oss.endpoint |
OSS Bucket的Endpoint信息,公网或私网地址皆可。 |
|
accessModes |
Dataset的访问模式。 |
|
JindoRuntime |
replicas |
创建JindoFS集群的Worker数量。 |
mediumtype |
缓存类型。定义创建JindoRuntime模板样例时,JindoFS支持HDD、SSD、MEM其中任意一种缓存类型。 |
|
path |
存储路径,暂时只支持单个路径。当选择MEM做缓存时,需指定一个本地路径来存储Log等文件。 |
|
quota |
缓存最大容量,单位GB。缓存容量可以根据UFS数据大小自行配置。 |
|
high |
存储容量上限大小。 |
|
low |
存储容量下限大小。 |
|
fuse.args |
表示可选的Fuse客户端挂载参数。通常与Dataset的访问模式搭配使用。
|
- 执行以下命令,通过部署dateset.yaml创建JindoRuntime和Dataset。
kubectl create -f dataset.yaml
预期输出:
dataset.data.fluid.io/demo created jindoruntime.data.fluid.io/demo created
- 执行以下命令,查看Dataset的部署情况。
kubectl get dataset
预期输出:
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE demo 588.90KiB 0.00B 10.00GiB 0.0% Bound 2m7s
实验一: 软约束调度
- 创建应用Pod
$ cat<<EOF >app-1.yaml apiVersion: v1 kind: Pod metadata: name: app-1 labels: # enable Fluid's scheduling optimization for the pod fuse.serverful.fluid.io/inject: "true" spec: containers: - name: app-1 image: nginx volumeMounts: - mountPath: /data name: demo volumes: - name: demo persistentVolumeClaim: claimName: demo EOF $ kubectl create -f app-1.yaml
注意:如果需要Fluid干预调度,必须在labels中开启fuse.serverful.fluid.io/inject: "true"
查看Pod所在节点为cn-beijing.192.168.58.147, 所在可用区就是在阿里云北京可用区l,证明这个Pod可以优先调度和分布式缓存同一个区域。
$ kubectl get po app-1 -owide kubectl get po app-1 -owide NAME READY STATUS RESTARTS AGE IP NODE app-1 1/1 Running 0 4m59s 192.168.58.169 cn-beijing.192.168.58.147
- 将北京可用区l的两个节点都设置成不可调度
$ kubectl cordon cn-beijing.192.168.58.146 $ kubectl cordon cn-beijing.192.168.58.147
- 此时阿里云北京可用区l的节点都处于不可调度的状态, 目的是不让新调度的Pod再调度到该可用区
$ kubectl get no NAME STATUS ROLES AGE VERSION cn-beijing.192.168.125.127 Ready <none> 32h v1.26.3-aliyun.1 cn-beijing.192.168.58.146 Ready,SchedulingDisabled <none> 81d v1.26.3-aliyun.1 cn-beijing.192.168.58.147 Ready,SchedulingDisabled worker 81d v1.26.3-aliyun.1
- 提交于之前配置完全相同的第二个应用Pod
$ cat<<EOF >app-2.yaml apiVersion: v1 kind: Pod metadata: name: app-2 labels: # enable Fluid's scheduling optimization for the pod fuse.serverful.fluid.io/inject: "true" spec: containers: - name: app-2 image: nginx volumeMounts: - mountPath: /data name: demo volumes: - name: demo persistentVolumeClaim: claimName: demo EOF $ kubectl create -f app-2.yaml
- 此时该Pod被调度到了节点为cn-beijing.192.168.58.147, 所在可用区就是在阿里云北京可用区b, 证明该Pod在调度条件不满足的时候,可以勉强调度到不同可用区的节点。
$ kubectl get po -owide app-2 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES app-2 1/1 Running 0 98s 192.168.125.131 cn-beijing.192.168.125.127 <none> <none>
结论:使用软约束调度,会优先调度到分布式缓存所在可用区,如果调度条件不满足时会退而求其次到调度其他可用区的节点。
实验二: 强制调度
- 创建应用Pod,metadata中指定label(格式fluid.io/dataset.{dataset_name}.sched: required),如fluid.io/dataset.demo.sched: required表明该Pod需要使用强制亲和调度策略,按照默认配置调度到数据集 demo 的缓存节点上。
$ cat<<EOF >app-3.yaml apiVersion: v1 kind: Pod metadata: name: app-3 labels: # enable Fluid's scheduling optimization for the pod fuse.serverful.fluid.io/inject: "true" fluid.io/dataset.demo.sched: required spec: containers: - name: app-3 image: nginx volumeMounts: - mountPath: /data name: demo volumes: - name: demo persistentVolumeClaim: claimName: demo EOF $ kubectl create -f app-3.yaml
- 此时Pod app-3处于Pending状态,不可以调度,查看事件, 可以发现两个节点不可以调度,另外一个节点则不满足调度条件didn't match Pod's node affinity/selector,,说明强制调度生效。
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 16s default-scheduler 0/3 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 2 node(s) were unschedulable. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling., .
- 查看此时的Pod调用策略, 强制要调度到缓存所在节点
$kubectl get po app-3 -o jsonpath='{.spec.affinity}' {"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"fluid.io/s-default-demo","operator":"In","values":["true"]}]}]}}}%
- 删除该Pod
$ kubectl delete po app-3
实验三: 修改调度策略
- 将强制调度策略从节点亲和性修改为可用区亲和性,这是为了满足在一些性能敏感的场景下强制要求缓存和计算资源运行在同一个可用区(数据中心)。
$ kubectl edit cm -n fluid-system tiered-locality-config apiVersion: v1 data: tieredLocality: | preferred: - name: fluid.io/node weight: 100 - name: topology.kubernetes.io/zone weight: 50 - name: topology.kubernetes.io/region weight: 20 required: - topology.kubernetes.io/zone kind: ConfigMap metadata: annotations: meta.helm.sh/release-name: fluid meta.helm.sh/release-namespace: default labels: app.kubernetes.io/managed-by: Helm name: tiered-locality-config namespace: fluid-system
具体变化在于
修改前:
required: - fluid.io/node
修改后:
required: - topology.kubernetes.io/zone
- 无需重启fluid-webhook即可以使配置生效
- 通过ACK添加新的节点,并且查看其所在区域
$ kubectl get no -o custom-columns="NAME:.metadata.name,ZONE:.metadata.labels.topology\.kubernetes\.io/zone" NAME ZONE STATUS cn-beijing.192.168.125.127 cn-beijing-b True cn-beijing.192.168.58.146 cn-beijing-l True cn-beijing.192.168.58.147 cn-beijing-l True cn-beijing.192.168.58.180 cn-beijing-l True
其中cn-beijing.192.168.58.180 为可用区L的新加节点。
- 再次创建应用Pod,metadata中指定label(格式fluid.io/dataset.{dataset_name}.sched: required),如fluid.io/dataset.demo.sched: required表明该Pod需要使用强制亲和调度策略,按照默认配置调度到数据集 demo 的缓存节点上。
$ cat<<EOF >app-3.yaml apiVersion: v1 kind: Pod metadata: name: app-3 labels: # enable Fluid's scheduling optimization for the pod fuse.serverful.fluid.io/inject: "true" fluid.io/dataset.demo.sched: required spec: containers: - name: app-3 image: nginx volumeMounts: - mountPath: /data name: demo volumes: - name: demo persistentVolumeClaim: claimName: demo EOF $ kubectl create -f app-3.yaml
可以发现此时的Pod已经处于运行状态,并且运行在新扩容的节点cn-beijing.192.168.58.180上。同时查看应用Pod的affinity配置:
$ kubectl get po app-3 -o jsonpath='{.spec.affinity}' {"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"topology.kubernetes.io/zone","operator":"In","values":["cn-beijing-l"]}]}]}}}%
实验结果:
为了比较跨可用区对于大模型数据访问的性能差异,我们将选择将一个30GiB的模型放置在OSS上,并使用Fluid以同样的方式进行访问比较。
我们选择了ECS实例机型:ecs.g8i.24xlarge,该实例具有64 vCPU、256GiB内存和30Gbps网络带宽。我们将通过Fluid加速访问模式(数据预热和多流数据加速)同时访问可用区内和跨可用区内的数据,以评估性能差异。
我们的观察是,同一个可用区的性能会提升1.41倍,同区域的数据访问带宽能可以达到非常30Gbps的硬件极限,这种性能提升非常明显。
总结:
通过本篇文章,你可以了解到如何通过Fluid使用分层的数据缓存亲和调度,并根据真实场景进行亲和性自定义的配置,通过调度提升数据访问的性能。