如何在 Kubernetes 集群中玩转 Fluid + JuiceFS
作者简介:
吕冬冬,云知声超算平台架构师, 负责大规模分布式机器学习平台架构设计与功能研发,负责深度学习算法应用的优化与 AI 模型加速。研究领域包括高性能计算、分布式文件存储、分布式缓存等。
朱唯唯,Juicedata 全栈工程师,负责 JuiceFS CSI Driver 的开发和维护,负责 JuiceFS 在云原生领域的发展。
云知声 Atlas 团队在 2021 年初开始接触并跟进 JuiceFS 存储,并且在早期已经积累了丰富的 Fluid 使用经验。近期,云知声团队与 Juicedata 团队合作开发了 Fluid JuiceFS 加速引擎,使用户能够更好地在 Kubernetes 环境中使用 JuiceFS 缓存管理能力。本篇文章讲解如何在 Kubernetes 集群中玩转 Fluid + JuiceFS。
背景介绍
Fluid 简介
CNCF Fluid 是一个开源的 Kubernetes 原生的分布式数据集编排和加速引擎,主要服务于云原生场景下的数据密集型应用,例如大数据应用、AI 应用等,关于 Fluid 更多信息可以参考地址。

Fluid 不是全存储加速和管理,而是应用使用的数据集加速和管理。Fluid 提供了一种更加云原生的方式对数据集进行管理,通过缓存加速引擎实现将底层存储系统的数据 cache 在计算节点的内存或者硬盘上,解决了计算与存储分离架构中由于数据传输带宽限制以及底层存储带宽与 IOPS 能力限制等问题,导致的 IO 效率不高等问题。Fluid 提供缓存数据调度能力,缓存被纳入 kubernetes 扩展资源,kubernetes 在进行任务的调度的时候,能够参考缓存进行调度策略的分配。
Fluid 有 2个重要的概念:Dataset 与 Runtime
- Dataset: 数据集是逻辑上相关的一组数据的集合,一致的文件特性,会被同一运算引擎使用。
- Runtime: 实现数据集安全性,版本管理和数据加速等能力的执行引擎的接口,定义了一系列生命周期的方法。
Fluid 的 Runtime 定义了标准化的接口,Cache Runtime Engine 可以对接多种缓存引擎,提供了用户更灵活的选择,用户能够针对不同的场景与需求,充分利用缓存引擎加速相应的场景应用。
JuiceFS 简介

JuiceFS 是一个面向云环境设计的高性能开源分布式文件系统,完全兼容 POSIX、HDFS、S3 接口,适用于大数据、AI 模型训练、Kubernetes 共享存储、海量数据归档管理等场景。
使用 JuiceFS 存储数据,数据本身会被持久化在对象存储(例如,Amazon S3),而数据所对应的元数据可以根据场景需求被持久化在 Redis、MySQL、TiKV 等多种数据库引擎中。JuiceFS 客户端具有数据缓存能力,当通过 JuiceFS 客户端读取数据时,这些数据将会智能地缓存到应用配置的本地缓存路径(可以是内存,也可以是磁盘),同时元数据也会缓存到客户端节点本地内存中。
对于 AI 模型训练场景来说,第一个 epoch 完成之后后续的计算都可以直接从缓存中获取训练数据,极大地提升了训练效率。JuiceFS 也具有预读、并发读取数据的能力,在 AI 训练场景能够保证每个 mini-batch 的生成效率,提前准备好数据。数据预热能够提前将公有云上的数据换到到本地节点,对于 AI 训练场景能够保证申请完 GPU 资源后,即有预热的数据进行运算,为宝贵的 GPU 使用节省了时间。
为什么使用 JuiceFSRuntime
云知声 Atlas 超算平台作为底层基础架构,支持着公司在 AI 各个领域的模型训练与推理服务的开展。云知声很早就开始布局建设业界领先的 GPU/CPU 异构 Atlas 计算平台和分布式文件存储系统,该计算集群可为 AI 计算提供高性能计算和海量数据的存储访问能力。云知声 Atlas 团队在 2021 年初开始接触并跟进 JuiceFS 存储,进行了一系列 POC 测试,在数据可靠性与业务场景的适配,都满足我们目前的需求。
在训练场景我们充分利用 JuiceFS 客户端的缓存能力,为 AI 模型训练做数据加速,但是在使用过程中发现了一些问题:
- 训练 Pod 通过 hostpath 挂载,需要在每个计算节点挂载 JuiceFS 客户端,挂载需要管理员操作,挂载参数固定,不够灵活。
- 用户无法对计算节点客户端的缓存管理,缓存无法手动清理与扩容。
- 缓存数据集无法像 Kubernetes 自定义资源一样能够被 kubernetes 进行调度。
由于我们在生产环境已经积累了一定的 Fluid 使用经验,所以我们与 Juicedata 团队合作设计并开发了 JuiceFSRuntime,将 Fluid 对数据编排与管理能力和 JuiceFS 的缓存能力结合起来。
什么是 Fluid + JuiceFS(JuiceFSRuntime)
JuiceFSRuntime 是 Fluid 自定义的一种 Runtime,其中可以指定 JuiceFS 的 worker、fuse 镜像以及相应的缓存参数。其构建方式与 Fluid 其他 Runtime 一致,即通过 CRD 的方式构建,JuiceFSRuntime Controller 监听 JuiceFSRuntime 资源,实现缓存 Pod 的管理。
JuiceFSRuntime 支持数据亲和性调度(nodeAffinity),选择合适的缓存节点,支持 Fuse pod 懒启动,支持用户以 POSIX 接口访问数据,目前只支持一个挂载点。

其架构图如上图所示,JuiceFSRuntime 由 Fuse Pod 与 Worker Pod 组成。Worker pod 主要实现缓存的管理,如 Runtime 退出时的缓存清理;Fuse pod 主要负责 JuiceFS 客户端的参数设置及挂载。
如何使用 JuiceFSRunime
下面来看看如何使用 JuiceFSRuntime 进行缓存加速。
前期准备
要使用 JuiceFSRuntime 首先需要准备元数据引擎和对象存储。
构建元数据引擎
用户可以很容易的在云计算平台购买到各种配置的云 Redis 数据库,如果是评估测试使用可以使用 Docker 快速的在服务器上运行一个 Redis 数据库实例:
$ sudo docker run -d --name redis \
-v redis-data:/data \
-p 6379:6379 \
--restart unless-stopped \
redis redis-server --appendonly yes
准备对象存储
和 Redis 数据库一样,几乎所有的公有云计算平台都提供对象存储服务。因为 JuiceFS 支持几乎所有主流平台的对象存储服务,用户可以结合自己的情况进行部署。
这里是评估测试应该使用的是 Dokcer 运行的 minio 实例:
$ $ sudo docker run -d --name minio \
-p 9000:9000 \
-p 9900:9900 \
-v $PWD/minio-data:/data \
--restart unless-stopped \
minio/minio server /data --console-address ":9900"
对象存储初始的 Access Key 和 Secret Key 均为 minioadmin。
下载并安装 Fluid
按照文档步骤安装 Fluid,在 Fluid 的安装 chart values.yaml 中将 runtime.juicefs.enable 设置为 true,并安装 Fluid。确保 Fluid 集群正常运行:
kubectl get po -n fluid-system
NAME READY STATUS RESTARTS AGE
csi-nodeplugin-fluid-ctc4l 2/2 Running 0 113s
csi-nodeplugin-fluid-k7cqt 2/2 Running 0 113s
csi-nodeplugin-fluid-x9dfd 2/2 Running 0 113s
dataset-controller-57ddd56b54-9vd86 1/1 Running 0 113s
fluid-webhook-84467465f8-t65mr 1/1 Running 0 113s
juicefsruntime-controller-56df96b75f-qzq8x 1/1 Running 0 113s
确保 juicefsruntime-controller、dataset-controller、fluid-webhook 的 pod 以及若干 csi-nodeplugin pod 正常运行。
创建 Dataset
在使用 JuiceFS 之前,需要提供元数据服务(如 redis)及对象存储服务(如 minio)的参数,并创建对应的 secret:
kubectl create secret generic jfs-secret \
--from-literal=metaurl=redis://$IP:6379/1 \ # redis 的地址 IP 为 redis 所在节点的 IP
--from-literal=access-key=minioadmin \ # 对象存储的 ak
--from-literal=secret-key=minioadmin #对象存储的 sk
创建 Dataset yaml 文件
cat<<EOF >dataset.yaml
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: jfsdemo
spec:
mounts:
- name: minio
mountPoint: "juicefs:///demo"
options:
bucket: "<bucket>"
storage: "minio"
encryptOptions:
- name: metaurl
valueFrom:
secretKeyRef:
name: jfs-secret
key: metaurl
- name: access-key
valueFrom:
secretKeyRef:
name: jfs-secret
key: access-key
- name: secret-key
valueFrom:
secretKeyRef:
name: jfs-secret
key: secret-key
EOF
由于 JuiceFS 采用的是本地缓存,对应的 Dataset 只支持一个 mount,且 JuiceFS 没有 UFS,mountpoint 中可以指定需要挂载的子目录 ("juicefs:///" 为根路径),会作为根目录挂载到容器内。
创建 Dataset 并查看 Dataset 状态
$ kubectl create -f dataset.yaml
dataset.data.fluid.io/jfsdemo created
$ kubectl get dataset jfsdemo
NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE
jfsdemo NotBound 44s
如上所示,status 中的 phase 属性值为 NotBound,这意味着该 Dataset 资源对象目前还未与任何 JuiceFSRuntime 资源对象绑定,接下来,我们将创建一个 JuiceFSRuntime 资源对象。
创建 JuiceFSRuntime
创建 JuiceFSRuntime 的 yaml 文件
$ cat<<EOF >runtime.yaml
apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
replicas: 1
tieredstore:
levels:
- mediumtype: SSD
path: /cache
quota: 40960 # JuiceFS 中 quota 的最小单位是 MiB,所以这里是 40GiB
low: "0.1"
EOF
创建并查看 JuiceFSRuntime
$ $ kubectl create -f runtime.yaml
juicefsruntime.data.fluid.io/jfsdemo created
$ kubectl get juicefsruntime
NAME WORKER PHASE FUSE PHASE AGE
jfsdemo Ready Ready 72s
查看 JuiceFS 相关组件 Pod 的状态
$$ kubectl get po |grep jfs
jfsdemo-worker-mjplw 1/1 Running 0 4m2s
JuiceFSRuntime 没有 master 组件,而 Fuse 组件实现了懒启动,会在 pod 使用时再创建。
创建缓存加速作业
创建需要加速的应用,其中 Pod 使用上面创建的 Dataset 的方式为指定同名的 PVC
$ cat<<EOF >sample.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-app
spec:
containers:
- name: demo
image: nginx
volumeMounts:
- mountPath: /data
name: demo
volumes:
- name: demo
persistentVolumeClaim:
claimName: jfsdemo
EOF
创建 Pod
$ kubectl create -f sample.yaml
pod/demo-app created
查看 pod 状态
$ kubectl get po |grep demo
demo-app 1/1 Running 0 31s
jfsdemo-fuse-fx7np 1/1 Running 0 31s
jfsdemo-worker-mjplw 1/1 Running 0 10m
可以看到 pod 已经创建成功,同时 JuiceFS 的 Fuse 组件也启动成功。
进入 Pod 执行 df -hT 查看缓存目录是否挂载:
$ kubectl exec -it demo-app bash -- df -h
Filesystem Size Used Avail Use% Mounted on
overlay 20G 14G 5.9G 71% /
tmpfs 64M 0 64M 0% /dev
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
JuiceFS:minio 1.0P 7.9M 1.0P 1% /data
可以看到这时候缓存目录已经成功挂载了。
接下来,我们在 demo-app 这个 pod 中测试一下写功能:
$ kubectl exec -it demo-app bash
[root@demo-app /]# df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 20751360 14585944 6165416 71% /
tmpfs 65536 0 65536 0% /dev
tmpfs 3995028 0 3995028 0% /sys/fs/cgroup
JuiceFS:minio 1099511627776 8000 1099511619776 1% /data
/dev/sda2 20751360 14585944 6165416 71% /etc/hosts
shm 65536 0 65536 0% /dev/shm
tmpfs 3995028 12 3995016 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs 3995028 0 3995028 0% /proc/acpi
tmpfs 3995028 0 3995028 0% /proc/scsi
tmpfs 3995028 0 3995028 0% /sys/firmware
[root@demo-app /]#
[root@demo-app /]# cd /data
[root@demo-app data]# echo "hello fluid" > hello.txt
[root@demo-app data]# cat hello.txt
hello fluid
最后再来看看缓存功能,在 demo-app 这个 pod 中的挂载目录 /data 中创建一个 1G 的文件,然后再 cp 出来:
$ kubectl exec -it demo-app bash
root@demo-app:~# dd if=/dev/zero of=/data/test.txt count=1024 bs=1M
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 6.55431 s, 164 MB/s
root@demo-app:~# time cp /data/test.txt ./test.txt
real 0m5.014s
user 0m0.003s
sys 0m0.702s
root@demo-app:~# time cp /data/test.txt ./test.txt
real 0m0.602s
user 0m0.004s
sys 0m0.584s
从执行结果来看,第一次 cp 用了 5s,此时建立缓存,第二次 cp 的时候由于缓存已经存在,只用了 0.6s。JuiceFS 所提供的强大的缓存能力,使得只要访问某个文件一次,该文件就会被缓存在本地缓存路径中中,所有接下来的重复访问都是从 JuiceFS 中直接获取数据。
后续规划
目前 JuiceFSRuntime 支持的功能并不多,未来我们会继续完善,比如 Fuse Pod 以 Nonroot 的方式运行,以及 Dataload 数据预热功能等。
推荐阅读:
知乎 x JuiceFS:利用 JuiceFS 给 Flink 容器启动加速
如有帮助的话欢迎关注我们 Juicedata/JuiceFS 哟! (0ᴗ0✿)
如何在 Kubernetes 集群中玩转 Fluid + JuiceFS的更多相关文章
- 在Kubernetes集群中使用calico做网络驱动的配置方法
参考calico官网:http://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm ...
- 初试 Kubernetes 集群中使用 Traefik 反向代理
初试 Kubernetes 集群中使用 Traefik 反向代理 2017年11月17日 09:47:20 哎_小羊_168 阅读数:12308 版权声明:本文为博主原创文章,未经博主允许不得转 ...
- 在kubernetes集群中创建redis主从多实例
分类 > 正文 在kubernetes集群中创建redis主从多实例 redis-slave镜像制作 redis-master镜像制作 创建kube的配置文件yaml 继续使用上次实验环境 ht ...
- Kubernetes集群中Service的滚动更新
Kubernetes集群中Service的滚动更新 二月 9, 2017 0 条评论 在移动互联网时代,消费者的消费行为已经“全天候化”,为此,商家的业务系统也要保持7×24小时不间断地提供服务以满足 ...
- 【转载】浅析从外部访问 Kubernetes 集群中应用的几种方式
一般情况下,Kubernetes 的 Cluster Network 是属于私有网络,只能在 Cluster Network 内部才能访问部署的应用.那么如何才能将 Kubernetes 集群中的应用 ...
- Kubernetes集群中Jmeter对公司演示的压力测试
6分钟阅读 背景 压力测试是评估Web应用程序性能的有效方法.此外,越来越多的Web应用程序被分解为几个微服务,每个微服务的性能可能会有所不同,因为有些是计算密集型的,而有些是IO密集型的. 基于微服 ...
- (转)在Kubernetes集群中使用JMeter对Company示例进行压力测试
背景 压力测试是评估应用性能的一种有效手段.此外,越来越多的应用被拆分为多个微服务而每个微服务的性能不一,有的微服务是计算密集型,有的是IO密集型. 因此,压力测试在基于微服务架构的网络应用中扮演着越 ...
- 实操教程丨如何在K8S集群中部署Traefik Ingress Controller
注:本文使用的Traefik为1.x的版本 在生产环境中,我们常常需要控制来自互联网的外部进入集群中,而这恰巧是Ingress的职责. Ingress的主要目的是将HTTP和HTTPS从集群外部暴露给 ...
- kubernetes集群中的pause容器
昨天晚上搭建好了k8s多主集群,启动了一个nginx的pod,然而每启动一个pod就伴随这一个pause容器,考虑到之前在做kubelet的systemd unit文件时有见到: 1 2 3 4 5 ...
随机推荐
- 初学Python-day8 案例2
中奖率 1 import random 2 num = 123456 3 i = 1 4 while True: 5 win = random.randrange(100000, 999999) 6 ...
- Noip模拟37 2021.8.12
T1 数列 真是考场上不是数学的乱推柿子,想定理,是数学的没想出来.. 比较悲伤... 列柿子不用动脑子,就是没有想出来$EXgcd$解不定方程,淦.. 解处一组解后利用比较显然的性质: $x+\fr ...
- 单片机入门stm32知识学习的先后顺序
这里大概的罗列了一些学习STM32的内容,以及学习顺序.如果是新手的话,建议边看中文手册和学习视频;如果是已经入门的,个人建议自己做一个项目,不论项目大小,当然里面会涉及到自己已经学习过的,或者是自己 ...
- Python课程笔记(七)
今天学习神奇的海龟,非常有意思,还有很多图片想去绘制,分享一个turtle绘图网站: https://www.python123.io/index/turtles/latest , 要是可以分享出源码 ...
- 近期业务大量突增微服务性能优化总结-3.针对 x86 云环境改进异步日志等待策略
最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...
- Pod 健康检查和服务可用性检查
Kubernetes 对 Pod 的健康状态可以通过两类探针来检查:LivenessProbe 和 ReadinessProbe,kubelet 定期执行这两类探针来针对容器的健康状况. Livene ...
- 【Go语言学习笔记】函数做参数和闭包
函数做参数 在Go语言中,函数也是一种数据类型,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型.类似于重写(同名覆盖). 回调函数:函数有一个参数是函数类型,这个 ...
- Labview一个循环中放两个事件结构会导致前面板锁定的问题
建议在同一个循环中,只放置一个事件结构.此时,当一个事件发生时,事件结构将对事件进行处理,然后继续循环,事件结构再等待下一个事件发生. 如在同一个循环中放置两个事件结构,只有在两个事件结构都处理了事件 ...
- 端口被占用(启动tomcat时 错误: 代理抛出异常 : java.rmi.server.ExportException: Port already in use: 1099的解决办法)
一.问题描述 在IntelliJ IDEA 中启动Tomcat服务器时就出现了如下图所示的错误: 错误: 代理抛出异常错误**: java.rmi.server.ExportException: Po ...
- css 马赛克悬停效果
css 马赛克悬停效果 <!DOCTYPE html> <html lang="en"> <head> <meta charset=