This page looks best with JavaScript enabled

Using JuiceFS to Store Elasticsearch Data

 ·  ☕ 8 min read

1. Storage Options

There are three storage approaches:

  • Isolate by directory and share a single JuiceFS

Elasticsearch nodes share one JuiceFS instance, mounting different Elasticsearch nodes through subdirectories.

/0/ corresponds to node Node-0
/1/ corresponds to node Node-1
/2/ corresponds to node Node-2

The main advantage of this approach is that it is easy to scale and simple to configure.

  • Isolate node data with JuiceFS

Every Elasticsearch node connects to an independent JuiceFS instance, so each node has its own storage backend.

Bucket-0 corresponds to node Node-0
Bucket-1 corresponds to node Node-1
Bucket-2 corresponds to node Node-2

The main advantage of this approach is higher reliability and better performance.

  • Combine with SSD for hot/cold data separation

JuiceFS does not match SSD in performance, so a hybrid approach — hot data on SSD, cold data on JuiceFS — is also a good choice.

The main advantage of this approach is that it balances cost and performance, but operations become more complex.

The current business requirement is to use Elasticsearch to query warm and cold data. The data volume is fairly large, reaching 10 TB of text data, and a 10-second API response time is acceptable, so the directory-isolation approach was adopted.

2. Creating the PVC That Elasticsearch Needs

2.1. Setting Environment Variables

  • Set the bucket information
1
2
3
4
5
6
export ACCESS_KEY=xxx
export SECRET_KEY=xxx
export BUCKET=xxx
export ENDPOINT=ks3-cn-beijing-internal.ksyun.com
export BUCKET_ENPOINT=$BUCKET.$ENDPOINT
export PROVIDER=ks3
  • Set Redis
1
2
export REDIS_PASSWORD=xxx
export REDIS_ENDPOINT=x.x.x.x:6379/0
  • Set the Elasticsearch PVC
1
2
export NAMESPACE=xxx
export PVC_NAME=elasticsearch-data-es-jfs-test-es-default-0

Note here that PVC_NAME is generated by the ECK Operator from the Elasticsearch name. The Elasticsearch instance created here is named es-jfs-test, so PVC_NAME is elasticsearch-data-es-jfs-test-es-default-0, which represents the storage volume of the first node.

2.2 Formatting JuiceFS

1
2
3
4
5
juicefs format \
    --storage $PROVIDER \
    --bucket $BUCKET_ENPOINT \
    redis://:$REDIS_PASSWORD@$REDIS_ENDPOINT \
    es-jfs-test

2.3 Creating the PVC

  • 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: $PVC_NAME
  namespace: $NAMESPACE
spec:
  accessModes:
    - ReadWriteMany
  mounts:
    - name: es-jfs-test
      mountPoint: "juicefs://0/"
      options:
        bucket: $BUCKET_ENPOINT
        storage: $PROVIDER
      encryptOptions:
        - name: metaurl
          valueFrom:
            secretKeyRef:
              name: es-jfs-test
              key: metaurl
        - name: access-key
          valueFrom:
            secretKeyRef:
              name: es-jfs-test
              key: access-key
        - name: secret-key
          valueFrom:
            secretKeyRef:
              name: es-jfs-test
              key: secret-key
EOF

Note that the MountPoint format is juicefs://0/, which means the first node. When creating the second node, it should be changed to juicefs://1/.

  • Create the Runtime
 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
kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
  name: $PVC_NAME
  namespace: $NAMESPACE
spec:
  replicas: 1
  juicefsVersion:
    image: juicedata/juicefs-fuse
    imageTag: ce-v1.1.0
  fuse:
    image: juicedata/juicefs-fuse
    imageTag: ce-v1.1.0
    cleanPolicy: OnDemand
  worker:
    resources:
      limits:
        cpu: 15
        memory: 200Gi
  tieredstore:
    levels:
      - mediumtype: SSD
        path: /cache
        quota: 40960   # 40GiB
EOF
  • Create a test Pod

Creating a test Pod is not just to verify that the PVC can be mounted correctly, but also to bring the PVC into the Bound state ahead of time, so that Elasticsearch does not create the PVC again.

 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: $PVC_NAME
  namespace: $NAMESPACE
spec:
  containers:
    - name: demo
      image: shaowenchen/demo:ubuntu
      volumeMounts:
        - mountPath: /data/jfs
          name: data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: $PVC_NAME
EOF

Reference Creating a PVC backed by JuiceFS in Kubernetes

3. Deploying Elasticsearch

Below is a simplified deployment procedure.

3.1 Installing the ECK Operator

Because the latest version of the ECK Operator does not support Kubernetes v1.26.9, the version installed here is ECK Operator v2.11.1.

1
kubectl create -f https://raw.githubusercontent.com/shaowenchen/ops-hub/master/observation/v2.11.1-eck-crds.yaml
1
kubectl apply -f https://raw.githubusercontent.com/shaowenchen/ops-hub/master/observation/v2.11.1-eck-operator.yaml

3.2 Deploying the Elasticsearch Instance

Note that count here is set to 1, meaning there is only one node. If you need to use multiple nodes, you must create the corresponding PVCs in advance and make sure they are in the Bound state.

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  namespace: data-center
  name: es-jfs-test
spec:
  version: 8.3.2
  image: elasticsearch:8.3.2
  http:
    tls:
      selfSignedCertificate:
        disabled: true
  nodeSets:
  - name: default
    count: 3
    config:
      node.store.allow_mmap: false
    podTemplate:
      spec:
        initContainers:
        - name: sysctl
          securityContext:
            privileged: true
            runAsUser: 0
          command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144']
        - name: install-plugins
          command:
            - sh
            - -c
            - |
              bin/elasticsearch-plugin install --batch https://get.infini.cloud/elasticsearch/analysis-ik/8.3.2
          securityContext:
            runAsUser: 0
            runAsGroup: 0
        containers:
        - name: elasticsearch
          env:
            - name: "ES_JAVA_OPTS"
              value: "-Xms30g -Xmx30g"
          resources:
            requests:
              cpu: 10
              memory: 20Gi
            limits:
              cpu: 50
              memory: 500Gi
EOF

3.3 Viewing the Elasticsearch Password

1
2
3
kubectl -n $NAMESPACE get secret es-jfs-test-es-elastic-user -o go-template='{{.data.elastic | base64decode}}'

xxx

The default username is elastic

4. Deploying Metricbeat

To be able to see Elasticsearch metric data in Kibana, Metricbeat needs to be deployed.

 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
33
34
35
36
kubectl apply -f - <<EOF
apiVersion: beat.k8s.elastic.co/v1beta1
kind: Beat
metadata:
  name: es-jfs-test
  namespace: $NAMESPACE
spec:
  type: metricbeat
  version: 8.3.2
  elasticsearchRef:
    name: es-jfs-test
  config:
    metricbeat:
      autodiscover:
        providers:
          - type: kubernetes
            scope: cluster
            hints.enabled: true
            templates:
              - config:
                  - module: kubernetes
                    metricsets:
                      - event
                    period: 10s
    processors:
    - add_cloud_metadata: {}
    logging.json: true
  deployment:
    podTemplate:
      spec:
        serviceAccountName: metricbeat
        automountServiceAccountToken: true
        # required to read /etc/beat.yml
        securityContext:
          runAsUser: 0
EOF
 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
33
34
35
36
37
38
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: metricbeat
rules:
- apiGroups: [""]
  resources:
  - nodes
  - namespaces
  - events
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
  resources:
  - jobs
  verbs: ["get", "list", "watch"]
- apiGroups: ["extensions"]
  resources:
  - replicasets
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources:
  - statefulsets
  - deployments
  - replicasets
  verbs: ["get", "list", "watch"]
- apiGroups:
  - ""
  resources:
  - nodes/stats
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get
EOF
1
2
3
4
5
6
7
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metricbeat
  namespace: $NAMESPACE
EOF
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metricbeat
subjects:
- kind: ServiceAccount
  name: metricbeat
  namespace: $NAMESPACE
roleRef:
  kind: ClusterRole
  name: metricbeat
  apiGroup: rbac.authorization.k8s.io
EOF

5. Deploying Kibana

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  namespace: $NAMESPACE
  name: es-jfs-test
spec:
  version: 8.3.2
  count: 1
  image: elastic/kibana:8.3.2
  elasticsearchRef:
    name: es-jfs-test
  http:
    tls:
      selfSignedCertificate:
        disabled: true
EOF

Edit the Kibana Service and change the type to NodePort.

1
kubectl -n $NAMESPACE edit svc es-jfs-test-kb-http

You can now log in to Kibana with the password of the elastic user above.

6. Inspecting the JuiceFS File Directory

To get an intuitive look at Elasticsearch’s file directory, I mounted the JuiceFS above on a test host for verification.

  • Mount JuiceFS on the test host
1
2
3
4
5
6
juicefs mount -d \
    --storage $PROVIDER \
    --bucket $BUCKET_ENPOINT \
    redis://:$REDIS_PASSWORD@$REDIS_ENDPOINT \
    es-jfs-test \
    /data/ops/es-jfs-test
  • View the directory structure
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
tree -L 2
.
├── 0
│   ├── indices
│   ├── mytest
│   ├── node.lock
│   ├── nodes
│   ├── snapshot_cache
│   ├── _state
│   └── test
├── 1
│   ├── indices
│   ├── node.lock
│   ├── nodes
│   ├── snapshot_cache
│   └── _state
└── 2
    ├── indices
    ├── node.lock
    ├── nodes
    ├── snapshot_cache
    └── _state

12 directories, 8 files
  • View the directory size

After the deployment was complete, the business side started importing data for testing.

1
2
3
du -sh 0

284M	0
1
2
3
du -sh 1

322M	1
1
2
3
du -sh 2

324M	2

7. Summary

AI-related machines have very high CPU and memory specifications, but people usually only pay attention to the utilization of the GPU and NPU compute cards. This leads to very low CPU and memory utilization on AI compute machines.

Recently there was a business requirement to query large volumes of corpus data after tokenization with Elasticsearch, but the team was very cost-sensitive and unwilling to use SSD as the data storage for Elasticsearch.

This post mainly records the process of connecting Elasticsearch to JuiceFS storage, in the hope that it helps everyone get up and running quickly.

8. References

https://github.com/elastic/cloud-on-k8s/tree/2.11
https://www.elastic.co/guide/en/cloud-on-k8s/2.11/k8s-quickstart.html


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