This page looks best with JavaScript enabled

Managing Data in Kubernetes with Fluid and JuiceFS

 ·  ☕ 7 min read

1. Introduction to Fluid

Below is the Fluid architecture diagram from https://github.com/fluid-cloudnative/fluid:

Fluid abstracts two concepts:

  • Dataset, a collection of data, the abstraction from the user’s point of view
  • Runtime, the abstraction of the real services such as data storage and acceleration

Fluid mainly solves the problems of using traditional caching systems on Kubernetes:

  1. It describes the Dataset, a collection of data, through a CRD and provides lifecycle management
  2. Relying on a Runtime backend, it provides a localized distributed caching service to Kubernetes cluster applications through a PVC

The workflow when using Fluid:

  1. Define the Dataset, setting the access credentials, storage location, read/write mode, and so on
  2. Define the Runtime; the runtime controller automatically binds the Dataset and Runtime with the same name via AddOwner; then creates a worker and configures the Runtime-related resources; and creates a PV with the ${NAMESPACE}- prefix and a PVC with the same name
  3. When a Pod mounts the PVC, it first creates a fuse pod on the node and mounts the /runtime-mnt/juicefs/xxx directory onto the host; then Fluid’s CSI Controller mounts that directory into the Pod.

The lifecycle of the Dataset and Runtime is described in Fluid’s code repository; see https://github.com/fluid-cloudnative/fluid/blob/master/docs/zh/dev/runtime_dev_guide.md

Below is the lifecycle of the Dataset

Below is the lifecycle of the Runtime

When in use:

Every request a Pod makes to a mounted file directory is forwarded to the fuse pod, which converts file I/O into network I/O to access the backend runtime storage.

2. Deploying Fluid

  • Create a namespace
1
kubectl create ns fluid-system
  • Add the Helm Repo
1
2
helm repo add fluid https://fluid-cloudnative.github.io/charts
helm repo update
  • Deploy Fluid
1
helm install --namespace fluid-system fluid fluid/fluid --devel

Since the juicefs client version used by my format is juicefs version 1.1.1+2023-11-28.437f4e6, in order for the image version in the work/fuse pod to match (you can of course configure it), I am using the --devel version here, that is, the current 1.0.0 beta version.

1
2
3
4
helm list --namespace fluid-system

NAME 	NAMESPACE   	REVISION	UPDATED                                	STATUS  	CHART               	APP VERSION
fluid	fluid-system	1       	2024-01-25 21:48:14.902476965 +0800 CST	deployed	fluid-1.0.0-alpha.17	1.0.0-719fc87
  • Check the Pod status
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
kubectl -n fluid-system get pod

NAME                                        READY   STATUS    RESTARTS   AGE
csi-nodeplugin-fluid-b8k9l                  2/2     Running   0          9m33s
csi-nodeplugin-fluid-gzl6w                  2/2     Running   0          9m33s
csi-nodeplugin-fluid-p5whc                  2/2     Running   0          9m33s
csi-nodeplugin-fluid-pwplp                  2/2     Running   0          9m33s
csi-nodeplugin-fluid-xs9kc                  2/2     Running   0          9m33s
csi-nodeplugin-fluid-xwwlm                  2/2     Running   0          9m33s
dataset-controller-6978c55675-2rtdr         1/1     Running   0          9m33s
fluid-webhook-76d4c5fd45-bbmw7              1/1     Running   0          9m33s
fluidapp-controller-697656949c-487mv        1/1     Running   0          9m33s
juicefsruntime-controller-fbf45c44f-vtlcf   1/1     Running   0          5m17s

3. Environment Preparation

  • Start a Redis instance for JuiceFS to use
1
mkdir -p /data/test/redis-data && cd /data/test
1
nerdctl run -d --security-opt apparmor=unconfined --security-opt seccomp=unconfined --name redis --network host -v $PWD/redis-data:/data -e REDIS_PASSWORD=mypassword redis:6
  • Configure the Redis environment variables
1
2
3
4
export REDIS_IP=x.x.x.x
export REDIS_PORT=6379
export REDIS_USER=default
export REDIS_PASSWORD=mypassword
  • Configure the bucket environment variables
1
2
3
4
5
6
export ACCESS_KEY=xxx
export SECRET_KEY=xxx
export BUCKET=xxx
export ENDPOINT=xxx
export BUCKET_ENPOINT=$BUCKET.$ENDPOINT
export PROVIDER=xxx
  • Create a filesystem
1
2
3
4
5
6
export REDIS_DIRECTSERVER=redis://${REDIS_USER}:${REDIS_PASSWORD}@${REDIS_IP}:${REDIS_PORT}/1
juicefs format \
    --storage ${PROVIDER} \
    --bucket ${BUCKET_ENPOINT}\
    ${REDIS_DIRECTSERVER} \
    juicefs-direct-demo

The filesystem needs to be initialized in advance; otherwise, when it is used in the cluster, you will get an error saying the .stats file cannot be found.

  • Set a test namespace
1
export NAMESPACE=shaowen-test
  • [Optional] Mount the filesystem locally
1
juicefs mount -d --buffer-size 2000 --max-uploads 150 ${REDIS_DIRECTSERVER} ./${NAMESPACE}-direct --cache-dir=/data/jfs-${NAMESPACE}
  • [Optional] Enter the directory and create a few test files
1
2
cd ${NAMESPACE}-direct
echo "123" > test.txt

4. Configuring the Dataset

  • Create a namespace
1
kubectl create ns ${NAMESPACE}
  • Create the Secret
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: juicefs-direct-secret
  namespace: ${NAMESPACE}
type: Opaque
stringData:
  metaurl: redis://${REDIS_USER}:${REDIS_PASSWORD}@${REDIS_IP}:6379/1
  access-key: ${ACCESS_KEY}
  secret-key: ${SECRET_KEY}
EOF

Redis requires a username to be set; the default is default, and it must be stated explicitly.

  • Create the Dataset
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
  name: juicefs-direct-demo
  namespace: ${NAMESPACE}
spec:
  accessModes:
    - ReadWriteMany
  mounts:
    - name: juicefs-direct-demo
      mountPoint: "juicefs:///"
      options:
        bucket: ${BUCKET_ENPOINT}
        storage: ${PROVIDER}
      encryptOptions:
        - name: metaurl
          valueFrom:
            secretKeyRef:
              name: juicefs-direct-secret
              key: metaurl
        - name: access-key
          valueFrom:
            secretKeyRef:
              name: juicefs-direct-secret
              key: access-key
        - name: secret-key
          valueFrom:
            secretKeyRef:
              name: juicefs-direct-secret
              key: secret-key
EOF

bucket should be the full form of Bucket.Endpoint, not just a bucket name. The default accessModes is ReadOnlyMany, that is, read-only mode; here it is changed to ReadWriteMany. In addition, what is mounted here is the root / directory of JuiceFS; in production you can mount different subdirectories along project or application boundaries.

  • Check the Dataset
1
2
3
4
kubectl -n ${NAMESPACE} get dataset

NAME                  UFS TOTAL SIZE   CACHED   CACHE CAPACITY   CACHED PERCENTAGE   PHASE      AGE
juicefs-direct-demo                                                                  NotBound   4s

At this point there is no Runtime with the same name configured yet, so the status is NotBound.

5. Configuring the Runtime

  • Create the Runtime
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
  name: juicefs-direct-demo
  namespace: ${NAMESPACE}
spec:
  replicas: 1
  tieredstore:
    levels:
      - mediumtype: SSD
        path: /cache
        quota: 40960   # 40GiB
EOF

There are many parameters that can be configured here; refer to the CRD definition documentation under the api directory of the corresponding branch at https://github.com/fluid-cloudnative/fluid. Different Fluid versions may have different parameters, so watch out for the distinction.

  • Check the Runtime status
1
2
3
4
kubectl -n ${NAMESPACE} get juicefsruntime

NAME                  WORKER PHASE   FUSE PHASE   AGE
juicefs-direct-demo   Ready                       96s

It may take a while to become Ready, because the worker needs to be created.

  • Check the Pod status
1
2
3
4
kubectl -n ${NAMESPACE} get pod

NAME                           READY   STATUS    RESTARTS   AGE
juicefs-direct-demo-worker-0   1/1     Running   0          115s

The worker has no anomalies and is running normally.

  • Check the Dataset status
1
2
3
4
kubectl -n ${NAMESPACE} get dataset

NAME                  UFS TOTAL SIZE   CACHED   CACHE CAPACITY   CACHED PERCENTAGE   PHASE   AGE
juicefs-direct-demo   1.01GiB                   40.00KiB                             Bound   33m
  • Check the PVC
1
2
3
4
kubectl -n shaowen-test get pvc

NAME                  STATUS   VOLUME                             CAPACITY   ACCESS MODES   STORAGECLASS   AGE
juicefs-direct-demo   Bound    shaowen-test-juicefs-direct-demo   100Pi      RWX            fluid          5m21s

RWX means ReadWriteMany, that is, read-write mode.

6. Creating a Workload

  • Create the workload
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: juicefs-direct-demo
  namespace: ${NAMESPACE}
spec:
  containers:
    - name: demo
      image: shaowenchen/demo:ubuntu
      volumeMounts:
        - mountPath: /data/jfs
          name: data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: juicefs-direct-demo
EOF
  • Check the workload
1
2
3
4
5
6
kubectl -n ${NAMESPACE} get pod juicefs-direct-demo

NAME                             READY   STATUS    RESTARTS   AGE
juicefs-direct-demo              1/1     Running   0          52m
juicefs-direct-demo-fuse-mkz4x   1/1     Running   0          52m
juicefs-direct-demo-worker-0     1/1     Running   0          54m
  • Enter the workload and inspect the data directory
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
kubectl -n ${NAMESPACE} exec -it juicefs-direct-demo bash

ls -al /data/jfs/

total 7
drwxrwxrwx 2 root root 4096 Jan 25 12:33 .
drwxr-xr-x 3 root root   25 Jan 25 12:53 ..
-r-------- 1 root root    0 Jan 25 12:53 .accesslog
-r-------- 1 root root 1627 Jan 25 12:53 .config
-r--r--r-- 1 root root    0 Jan 25 12:53 .stats
dr-xr-xr-x 2 root root    0 Jan 25 12:53 .trash
-rw-r--r-- 1 root root    4 Jan 25 12:33 test.txt
  • Run a quick benchmark

The JuiceFS directory is mounted in the Pod

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
juicefs bench --block-size 4096 --big-file-size 1024 --threads 5 ./

+------------------+------------------+---------------+
|       ITEM       |       VALUE      |      COST     |
+------------------+------------------+---------------+
|   Write big file |     207.88 MiB/s |  24.63 s/file |
|    Read big file |     761.77 MiB/s |   6.72 s/file |
| Write small file |    136.8 files/s | 36.56 ms/file |
|  Read small file |    293.7 files/s | 17.03 ms/file |
|        Stat file |   9007.3 files/s |  0.56 ms/file |
|   FUSE operation | 89312 operations |    0.97 ms/op |
|      Update meta |  1595 operations |    1.57 ms/op |
|       Put object |  1780 operations |  111.32 ms/op |
|       Get object |  1780 operations |   75.37 ms/op |
+------------------+------------------+---------------+

微信公众号
WRITTEN BY
微信公众号