Skip to main content

在 Fluid 中使用 JuiceFS

Fluid 是一个 Kubernetes 原生的分布式数据集编排和加速引擎,本文介绍如何在 Fluid 中使用 JuiceFS。更多关于 Fluid 使用方式,请参考 Fluid 官方文档

背景介绍

Fluid 使用 CRD 的方式来管理资源,分别提供了如下自定义资源:

  • Dataset:用于描述一组数据集,资源中记录了文件系统的名称、引用了 JuiceFS 文件系统的认证信息,下方的运行时组件也都会通过 Dataset 来挂载文件系统。
  • JuiceFSRuntime:承载 JuiceFS 的运行时组件,为应用提供 Dataset 的访问能力。组件包含:
    • Worker,相当于 JuiceFS 的独立缓存集群,每一个 worker pod 都是一个缓存集群成员
    • FUSE,也就是实际使用着缓存集群的客户端,和 worker pod 类似,每一个 FUSE pod 也是一个 JuiceFS 客户端,他们同样加入缓存组,但启用了 --no-sharing,以「只索取、不分享」得方式使用缓存集群的数据

以及一些使用过程中可能要用到的可选资源,可以点击链接跳转到 Fluid 官方文档、详细了解:

安装

Fluid 提供 Helm Chart 的安装方式,如下:

helm repo add fluid https://fluid-cloudnative.github.io/charts
helm repo update
helm install fluid fluid/fluid

另外,如果你是在阿里云容器服务 ACK 中使用 Fluid,可在集群中选择安装「云原生 AI 套件」并开启「Fluid 数据加速」功能。

运行 JuiceFS

在 Fluid 中运行 JuiceFS 只需要 3 步:

  1. 创建文件系统认证信息
  2. 创建 Dataset
  3. 创建与 Dataset 同名的 JuiceFSRuntime
  4. (可选)创建 DataLoad,提前预热数据

创建文件系统认证信息

进行这一步之前,你需要有一个已经能够正常挂载、运行的 JuiceFS 文件系统,如果尚未创建,请先在 JuiceFS 云服务中创建文件系统

创建好文件系统后,推荐先挂载测试、确保正常运行,然后再把这些认证信息按照下方示范创建成 Kubernetes Secret。

apiVersion: v1
kind: Secret
metadata:
name: jfs-secret
type: Opaque
stringData:
token: ${JUICEFS_TOKEN}
access-key: ${ACCESS_KEY}
secret-key: ${SECRET_KEY}

创建 Dataset

apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: jfsdemo
spec:
mounts:
- name: <vol-name> # JuiceFS 文件系统名称
mountPoint: "juicefs:///"
encryptOptions:
- name: token
valueFrom:
secretKeyRef:
name: jfs-secret
key: token
- name: access-key
valueFrom:
secretKeyRef:
name: jfs-secret
key: access-key
- name: secret-key
valueFrom:
secretKeyRef:
name: jfs-secret
key: secret-key

其中:

  • name:需要与在 JuiceFS 控制台创建的文件系统名一致。
  • mountPoint:指的是 JuiceFS 的子目录,是用户在 JuiceFS 文件系统中存储数据的目录,以 juicefs:// 开头;如 juicefs:///demo 为 JuiceFS 文件系统的 /demo 子目录,juicefs:/// 为 JuiceFS 文件系统的根目录。
  • encryptOptions:用于指定 JuiceFS 认证信息,需要与上一步创建的 Secret 中的 key 一致。

创建 JuiceFSRuntime

Fluid 中 Runtime 和 Dataset 是一对一的关系,一个 Runtime 只能绑定一个 Dataset,且二者通过同名来绑定。

在实际业务环境中,一个典型的 JuiceFSRuntime 示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo # 与 Dataset 同名
spec:
tieredstore:
levels:
- mediumtype: SSD
path: /data/jfscache
quota: 500Mi
low: "0.01"
# volumeType: hostPath (default), emptyDir
worker:
options:
"cache-size": "102400"
fuse:
options:
# 设为 0 以禁用客户端本地缓存,最大程度利用分布式缓存
# 详见 https://juicefs.com/docs/zh/cloud/guide/distributed-cache/#dedicated-cache-cluster
"cache-size": "0"

tieredstore 的字面含义是“多级存储”,但 JuiceFS 目前并不支持多级存储,因此只需要配置一个缓存级别,也就是 levels 列表只需要填写一个项目,其中各个字段含义:

  • mediumtype:用于指明缓存目录所使用的存储设备类型,取值为 [MEM|SSD|HDD],需要注意这个字段是一个“标记”,并不一定代表底层存储就是内存、SSD 或机械硬盘,实际物理存储介质取决于 path 的设置。

    特别地,如果取值 MEM,那么 Fluid 会根据 quota 大小自动设置容器的内存资源声明,保证容器的 memory requests;

  • path:缓存目录,多盘缓存用 : 分割多个目录,设置为 /dev/shm 以使用内存设备。该参数作用同 --cache-dir

  • quota:每个 worker 容器(也就是缓存集群成员节点)的最大缓存容量,将该值乘以 replicas(容器数量),就能得到整个缓存集群的总体可用空间。该参数作用同 --cache-size,但单位和写法不同,详见上方示范和对应的文档;

  • low:缓存盘的最小剩余空间占比,默认 0.2,在上方示范中填写为 0.01 代表最多可以使用缓存盘 99%。该参数作用同 --free-space-ratio

  • volumeType:卷类型,支持目前支持 [hostPath|emptyDir],默认均为 hostPath,也就是将缓存目录从宿主机目录挂载到容器里。在不支持特权模式(privileged)的情况下,需要改为 emptyDir,用容器存储作为缓存目录,多用于 serverless 场景下。

tieredstore 同时对 worker 和 FUSE 生效,但考虑到在 Fluid 中使用 JuiceFS,属于一个「独立缓存集群」架构,这种场景往往需要为缓存集群和客户端采用不同的缓存设置,因此在上方的示范中也体现了这一点:

  • tieredstore 代表默认的缓存配置,如果 worker.optionsfuse.options 中没有覆盖缓存相关参数,那么便会以 tieredstore 的设置为准;

  • worker.options 中设置了 "cache-size": "102400",单位为 MiB,相当于覆盖了 tieredstore 中设置的 quota: 500Mi

    注意两个字段虽然作用相同,但写法不一样,cache-sizeJuiceFS 客户端挂载参数,而 quota 是 Fluid Runtime 参数,支持使用 Kubernetes 资源的单位写法,并且最终会被翻译为 JuiceFS 客户端的 --cache-size 挂载参数;

  • fuse.options 中设置了 "cache-size": "0",代表 FUSE 容器完全不使用本地缓存,最大程度利用缓存集群(也就是 worker)的数据。要注意,这种场景下 FUSE 容器虽然不使用本地磁盘缓存,但随着读取文件,也会在内存中建立内核页缓存,也能获得极致性能。

JuiceFSRuntime 和 Dataset 创建好后,二者会自动绑定(status.phaseBound),绑定后,Fluid 会创建 worker pod,其个数为 replicas 指定的值,这些 pod 一起组成分布式缓存集群

$ kubectl get po | grep worker
jfsdemo-worker-0 1/1 Running 0 4m2s

企业版(私有部署)相关配置

JuiceFS Web 控制台负责着客户端的挂载认证、配置文件下发等工作。而在私有部署环境中,控制台的地址不再是 https://juicefs.com/console,需要在 JuiceFSRuntime 中指定控制台的地址,示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
worker:
env:
- name: "BASE_URL"
# 将 <CONSOLE_URL> 替换为私有部署控制台地址,例如 http://console.juicefs.com:8080
value: "<CONSOLE_URL>/static"
fuse:
env:
- name: "BASE_URL"
# 将 <CONSOLE_URL> 替换为私有部署控制台地址,例如 http://console.juicefs.com:8080
value: "<CONSOLE_URL>/static"

使用 Dataset

Dataset 和 JuiceFSRuntime 创建完成后,由于二者同名,所以会自动绑定。Fluid 会创建一个与 Dataset 同名的 PVC,应用就可以通过 PVC 来使用 JuiceFS 了。参考我们的示范,创建一个示例如下:

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

Fluid 会在应用 pod 所在的节点上创建一个运行 JuiceFS 的客户端 pod,称为 FUSE pod,用以为应用提供数据读写的服务,如下:

$ kubectl get po |grep demo
demo-app 1/1 Running 0 31s
jfsdemo-fuse-fx7np 1/1 Running 0 31s
jfsdemo-worker-0 1/1 Running 0 10m

创建 DataLoad

在 Dataset 可用(Bound 状态)之后,接下来可以通过 DataLoad 来预热数据(也就是运行 juicefs warmup,提前将数据下载到缓存集群中)。以下是一个 DataLoad 的示例:

apiVersion: data.fluid.io/v1alpha1
kind: DataLoad
metadata:
name: jfs-load
spec:
dataset:
name: jfsdemo
namespace: default
target:
- path: /dir1

其中:

  • spec.dataset.name:指定要加速数据访问的 Dataset 名称;
  • spec.dataset.namespace:指定要加速数据访问的 Dataset 的 namespace;
  • spec.target.path:指定要加速数据访问的数据路径,可以是目录或文件;target 是列表,可以声明多个 path,达到预热多个目录的效果。path 的含义就是 juicefs warmupPATH 参数。

按照上方示范创建出 DataLoad 以后,预热任务(Kubernetes Job)就会自动创建并执行。Job 成功以后,数据就预热到缓存组中了,也就是各个 worker 节点的缓存盘中。需要注意的是,预热任务成功,并不意味着数据完整下载到了本地,这是因为缓存空间可能不够大,导致数据发生了淘汰。如果后续的使用发现命中率不符合预期,阅读「为什么预热后仍然会访问对象存储?」并排查。

如果排查发现缓存空间不足、后续 worker 进行了扩容,那么也需要重新创建 DataLoad 资源,来再次执行预热任务。

缓存组名称

在 JuiceFS 分布式缓存中,缓存数据在同一个组内共享。Fluid 中已经自动为你设置了缓存组名称,默认为 <namespace>-<runtime>,因此如果你的场景中,所有的 JuiceFS 客户端都通过 Fluid 部署、没有其他类型的挂载点(比如宿主机挂载点、CSI 驱动),那么通常不需要关注或者手动修改缓存组名称。

如果你不仅在 Fluid 中使用 JuiceFS,还需要配合其他场景使用,那么就可能需要手动覆盖、修改缓存组名。比方说:独立缓存集群并没有以 Fluid worker 的形式部署,而是另一批宿主机挂载点,这时候需要对齐缓存组名称:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
# 使用外部部署的独立缓存集群,因此将 Fluid worker 副本数设为 0
replicas: 0
worker:
options:
"cache-group": "mygroup"
fuse:
options:
"cache-group": "mygroup"

在上方的示范中,外部部署的缓存集群的组名为 mygroup,因此为了让 Fluid Runtime 能够直接利用该缓存组的数据,需要在 fuse.options 中覆盖 cache-group 参数,来加入 mygroup 这个缓存组。

Dataset 的访问模式

Dataset 的默认访问模式为 ReadOnlyMany,也支持 ReadWriteMany,可以通过 spec.accessModes 来修改,示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: demo
spec:
accessModes:
- ReadWriteMany
...

子目录配额设置

在 Dataset 中可以设置访问 JuiceFS 文件系统的子目录,同时可以为子目录设置配额。以下是一个 Dataset 的示例:

apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: jfsdemo
spec:
mounts:
- name: minio
mountPoint: "juicefs:///demo"
options:
quota: "2Gi"

其中,spec.mounts.options.quota 指定 Dataset 子目录的配额,至少为 1Gi。

商业版 4.9.2 以上支持子目录配额设置。设置子目录配额时,需要确保 JuiceFS 的版本满足要求,在 JuiceFSRuntime 中指定 FUSE 的镜像为 juicedata/juicefs-fuse:v1.0.4-4.9.2,具体镜像参考 JuiceFS 镜像仓库

FUSE pod 的配置

FUSE pod 是运行在应用 pod 所在的节点上,并为其提供数据读写服务的组件。

提示

对于 ACK AI 套件 1.0.5 之前版本的 Fluid,FUSE 的配置无法在 Runtime 中动态修改——需要删除重建整个 JuiceFSRuntime。从 1.0.5 开始,Runtime 支持动态编辑 FUSE 相关参数。

但是需要注意,修改完 JuiceFSRuntime 后,还需要删除 FUSE pod,令其重建后,改动才真正生效。

基础配置

对于 FUSE pod 本身的配置,均在 JuiceFSRuntime 中设置。

JuiceFSRuntime 的基础配置如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
fuse:
image: juicedata/juicefs-fuse
imageTag: ee-4.9.19
imagePullPolicy: IfNotPresent
podMetadata:
labels:
juicefs: "fuse"
annotations:
juicefs: "fuse"
networkMode: ContainerNetwork
env:
- name: "GOOGLE_CLOUD_PROJECT"
value: "xxx"
resources:
limits:
cpu: 2
memory: 5Gi
requests:
cpu: 1
memory: 1Gi
options:
"buffer-size": "1000"

其中:

  • spec.fuse.image:指定 FUSE pod 的镜像;
  • spec.fuse.imageTag:指定 FUSE pod 的镜像标签;
  • spec.fuse.imagePullPolicy:指定 FUSE pod 的镜像拉取策略;
  • spec.fuse.podMetadata:指定 FUSE pod 的元数据,包括 labels 和 annotations;
  • spec.fuse.networkMode:指定 FUSE pod 的网络模式,目前支持 HostNetworkContainerNetwork,默认为 HostNetwork
  • spec.fuse.env:指定 FUSE pod 的环境变量;
  • spec.fuse.resources:指定 FUSE pod 的资源限制;
  • spec.fuse.options:指定 FUSE pod 的挂载参数。

挂载点自动恢复

类似于 CSI 驱动的「挂载点自动恢复」,Fluid 也支持挂载点自动恢复。配置完成以后,如果 FUSE Pod 发生异常导致挂载点丢失,应用挂载点可以随着 FUSE Pod 重启完成而自动恢复。

推荐为所有的生产环境 Fluid 启用自动恢复,否则一旦遇到 FUSE Pod 崩溃,应用内的 JuiceFS 挂载点无法自动恢复。

以 Helm 安装方式为例,需要在 values.yaml 增加如下配置:

"values.yaml
csi:
featureGates: "FuseRecovery=true"

对于阿里云,如果通过「云原生 AI 套件」直接安装 Fluid,那么需要在「应用」->「Helm」->「ACK:Fluid」->「更新」中配置 FuseRecovery=true

上述配置完成后,应用侧还需要添加 labels:在应用 Pod 中设置 label:fuse.serverful.fluid.io/inject: "true",自动恢复就设置成功了。

Volume 配置

可以设置云盘 PVC 作为 FUSE pod 的本地缓存目录,配置如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
fuse:
volumeMounts:
- name: "juicefs-cache"
mountPath: "/var/cache" # 与 runtime.fuse.option["cache-dir"] 一致
volumes:
- name: "juicefs-cache"
persistentVolumeClaim:
claimName: <pvc-name>

其中:

  • spec.fuse.volumeMounts:指定 FUSE pod 的 volumeMounts,可以指定多个 volumeMounts,其 name 与 spec.volumes 中的对应;
  • spec.volumes:指定 volumes,可以指定多个 volumes。

调度策略配置

FUSE pod 的调度策略目前只支持 nodeSelector,另外 Fluid 会默认给 FUSE pod 加上 operator: Existstolerations,确保节点的 taint 不会影响到 FUSE pod 的调度。

nodeSelector 示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
fuse:
nodeSelector:
fluid.io/dataset: jfsdemo

FUSE pod 配置跳过 auth 操作

默认情况下,FUSE pod 中的 JuiceFS 客户端在启动时会先执行 juicefs auth 操作,该操作会连接 JuiceFS 控制台获取文件系统的配置,并将配置文件保存到 $HOME/.juicefs/$VOL_NAME.conf,然后在挂载时会获取配置文件中必要的信息。

连接控制台势必会有一些网络连接的耗时,如果想要缩短 FUSE pod/sidecar 容器的启动时间,可以通过 Secret 把配置文件提前挂载进去,以跳过 juicefs auth 操作。具体操作如下:

  1. 从任意一个已经挂载 JuiceFS 的节点上,获取当前文件系统的配置文件,路径为 $HOME/.juicefs/$VOL_NAME.conf,将其保存为 Secret,其 key 设置为 $VOL_NAME.conf

    apiVersion: v1
    kind: Secret
    metadata:
    name: auth-secret
    namespace: default # 与 Dataset 同 namespace
    type: Opaque
    stringData:
    test-fluid-2.conf: | # key 为 <文件系统名>.conf,value 为配置文件的内容
    {
    "bucket": "http://xxxxxxx:9000",
    "accesskey": "xxxxxx",
    "secretkey": "xxxxxx",
    "rootname": "test-fluid-2",
    "uuid": "ab7626b9-ba25-4f28-a9ad-808c7be8a7b0",
    "token": "b9a5274b9b49884b99e4d3664a589b030bb6faf7",
    "storage": "s3",
    "region": "cn-north-1",
    "partitions": 256,
    "replicated": false,
    "compatible": false,
    "public": false,
    "blockSize": 4096,
    "readonly": false,
    "subdir": "/",
    "password": "xxxxxx",
    "compress": "none",
    "no_encrypt": true,
    "cfg_url": "https://juicefs.com/volume/%s/mount",
    "sessiontoken": null,
    "tested": 1
    }
  2. 将上述创建的 Secret 挂载到 JuiceFSRuntime 中:

    apiVersion: data.fluid.io/v1alpha1
    kind: JuiceFSRuntime
    metadata:
    name: jfsdemo-ee
    spec:
    fuse:
    volumeMounts:
    - name: auth-config
    readOnly: false
    mountPath: "/root/.juicefs" # 容器中挂载点为 /root/.juicefs
    volumes:
    - name: auth-config
    secret:
    secretName: auth-secret # 上一步中创建的 Secret 名

Worker pod 的配置

JuiceFSRuntime 会创建 JuiceFS 的缓存 worker,以 StatefulSet 的形式运行在 Kubernetes 集群中。其作用如下:

  • JuiceFS 的 worker pod 组成分布式缓存集群,为 FUSE pod 提供分布式缓存;
  • JuiceFS worker pod 与 FUSE pod 共享本地缓存,以加速数据访问;
  • 应用被调度时,Fluid 会优先将其调度到有缓存 worker 的节点上。
提示

ACK AI 套件 1.0.4 开始,worker 配置支持热修改。也就是说配置变更后,worker pod 会自动重建以生效。

基础配置

Worker pod 的基础配置如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
replicas: 1
juicefsVersion:
image: juicedata/juicefs-fuse
imageTag: ee-4.9.19
imagePullPolicy: IfNotPresent
worker:
options:
"cache-size": "102400"
podMetadata:
labels:
juicefs: "worker"
annotations:
juicefs: "worker"
networkMode: HostNetwork
env:
- name: "GOOGLE_CLOUD_PROJECT"
value: "xxx"
resources:
limits:
cpu: 2
memory: 5Gi
requests:
cpu: 1
memory: 1Gi

其中:

  • spec.replicas:指定 worker pod 的副本数;
  • spec.juicefsVersion:指定 worker pod 的镜像版本;
  • spec.worker.options:指定 worker 的挂载参数。具体参数请参考「挂载参数」
  • spec.worker.podMetadata:指定 worker pod 的元数据,包括 labels 和 annotations;
  • spec.worker.networkMode:指定 worker pod 的网络模式,目前支持 HostNetworkContainerNetwork,默认为 HostNetwork
  • spec.worker.env:指定 worker pod 的环境变量;
  • spec.worker.resources:指定 worker pod 的资源限制。

Volume 配置

可以设置 PVC 作为 worker pod 的缓存目录,配置如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
worker:
networkMode: ContainerNetwork
volumeMounts:
- name: "juicefs-cache"
mountPath: "/var/jfsCache" # 与 worker.options["cache-dir"] 保持一致
options:
# 覆盖 tieredstore 中的缓存盘路径
"cache-dir": "/var/jfsCache"
volumes:
- name: "juicefs-cache"
persistentVolumeClaim:
claimName: <pvc-name>

其中:

  • spec.worker.volumeMounts:指定 JuiceFS worker 的 volumeMounts,可以指定多个 volumeMounts,其 name 与 spec.volumes 中的对应;
  • spec.volumes:指定 volumes,可以指定多个 volumes。

调度策略配置

Worker pod 的调度策略有很多种,其中,nodeSelector 在 JuiceFSRuntime 中配置;tolerationsnodeAffinity 需要在 Dataset 中配置;worker pod 之间默认已经配置了 podAntiAffinity,确保每个 worker pod 运行在不同的节点上,不需要额外配置。

nodeSelector 示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
worker:
nodeSelector:
fluid.io/dataset: jfsdemo

tolerationsnodeAffinity 示例如下:

apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: jfsdemo
spec:
tolerations:
- key: "fluid.io/dataset"
operator: "Equal"
value: "jfsdemo"
effect: "NoSchedule"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: "fluid.io/dataset"
operator: "In"
values:
- "jfsdemo"

问题排查

修改配置不生效

在 Runtime 中定义的所有配置,都会转化为 JuiceFS 客户端的启动参数、渲染成一个名为 script.sh 启动脚本,以 ConfigMap 的形式注入到 Pod,类似:

apiVersion: v1
kind: Pod
metadata:
name: juicefs-datasets-poc-fuse-xxx
namespace: storage
spec:
containers:
- command:
- sh
- /root/script/script.sh
...
volumeMounts:
- mountPath: /root/script
name: script
...
volumes:
- configMap:
defaultMode: 493
name: juicefs-datasets-poc-fuse-script
name: script
...

上方便是 FUSE pod 的定义示范(Worker 也是类似的),查看 juicefs-datasets-poc-fuse-script 的 ConfigMap 定义,就能看到具体的 JuiceFS 客户端挂载参数。

需要注意 FUSE 和 worker 的配置更新机制:

  • 对于 ACK AI 套件 1.0.5 之前版本的 Fluid,FUSE 的配置无法在 Runtime 中动态修改——需要删除重建整个 JuiceFSRuntime。从 1.0.5 开始,Runtime 支持动态编辑 FUSE 相关参数。但是需要注意,修改完 JuiceFSRuntime 后,还需要删除 FUSE pod,令其重建后,改动才真正生效。
  • 从 ACK AI 套件 1.0.4 开始,worker 配置支持热修改。也就是说 Runtime 中相关配置变更后,worker pod 会自动重建以生效。

如果注意到挂载参数和 Runtime 设置不符,按照你所使用的 Fluid 版本和上方介绍的更新机制来处置。