Repository: juicedata/juicefs
Pull request #3015 (2022-11-24 01:57:34)

remove duplicated docs on JuiceFS CSI Driver
CSI Driver docs has been refurbished, lets maintain csi docs in one place.

the "Mount JuiceFS in the container" part will be re-introduced in CSI docs later.

zwwhdls: I suggest to keep CSI usage and hostPath usage in this doc and keep a link to CSI docs. About `Mount JuiceFS in the container`, I don't know if it's a common usage in Kubernetes. What do you think? @timfeirg 
timfeirg: I wish all Kubernetes related usage should reside in CSI documentation, including practices like "Mount JuiceFS in the container" or hostPath, although they aren't really under the CSI realm, they are close related to Kubernetes. I want to keep all Kubernetes topics under CSI docs to reduce maintenance.

After that, we can leave references in CE/EE docs so that users don't get lost.

Patch:
diff --git a/cmd/dump.go b/cmd/dump.go
index 1f81a939..6cb809b3 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -92,10 +92,14 @@ func dump(ctx *cli.Context) (err error) {
 			w = fp
 		}
 	}
-	m := meta.NewClient(metaUri, &meta.Config{Retries: 10, Strict: true, Subdir: ctx.String("subdir")})
+	metaConf := &meta.Config{Retries: 10, Strict: true, Subdir: ctx.String("subdir")}
+	m := meta.NewClient(metaUri, metaConf)
 	if _, err := m.Load(true); err != nil {
 		return err
 	}
+	if st := m.Chroot(meta.Background, metaConf.Subdir); st != 0 {
+		return st
+	}
 	if err := m.DumpMeta(w, 1, ctx.Bool("keep-secret-key")); err != nil {
 		return err
 	}
diff --git a/cmd/gateway.go b/cmd/gateway.go
index 83fa4c31..e34989b5 100644
--- a/cmd/gateway.go
+++ b/cmd/gateway.go
@@ -189,6 +189,9 @@ func initForSvc(c *cli.Context, mp string, metaUrl string) (*vfs.Config, *fs.Fil
 	if err != nil {
 		logger.Fatalf("load setting: %s", err)
 	}
+	if st := metaCli.Chroot(meta.Background, metaConf.Subdir); st != 0 {
+		logger.Fatalf("Chroot to %s: %s", metaConf.Subdir, st)
+	}
 	registerer, registry := wrapRegister(mp, format.Name)
 
 	blob, err := NewReloadableStorage(format, metaCli, updateFormat(c))
diff --git a/cmd/mount.go b/cmd/mount.go
index 5faeab0c..b2f18652 100644
--- a/cmd/mount.go
+++ b/cmd/mount.go
@@ -552,6 +552,9 @@ func mount(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
+	if st := metaCli.Chroot(meta.Background, metaConf.Subdir); st != 0 {
+		return st
+	}
 
 	// Wrap the default registry, all prometheus.MustRegister() calls should be afterwards
 	registerer, registry := wrapRegister(mp, format.Name)
diff --git a/docs/en/deployment/how_to_use_on_kubernetes.md b/docs/en/deployment/how_to_use_on_kubernetes.md
index 08f0494c..38aeade8 100644
--- a/docs/en/deployment/how_to_use_on_kubernetes.md
+++ b/docs/en/deployment/how_to_use_on_kubernetes.md
@@ -4,385 +4,12 @@ sidebar_position: 3
 slug: /how_to_use_on_kubernetes
 ---
 
-# Use JuiceFS on Kubernetes
-
-JuiceFS is ideal for use as a storage layer for Kubernetes clusters, and currently has two common uses.
+JuiceFS is an ideal storage layer for Kubernetes, read this chapter to learn how to use JuiceFS in Kubernetes.
 
 ## JuiceFS CSI Driver
 
-:::tip
-It is recommended to use the deployment method of the JuiceFS CSI Driver in Kubernetes. For more information about the JuiceFS CSI Driver, please visit [project homepage](https://juicefs.com/docs/csi/introduction).
-:::
-
-[JuiceFS CSI Driver](https://github.com/juicedata/juicefs-csi-driver) follows the [CSI](https://github.com/container-storage-interface/spec/blob/master/spec.md) specification, implements the interface between the container orchestration system and the JuiceFS file system, and supports dynamic configured JuiceFS volumes for use by Pod.
-
-### Prerequisites
-
-- Kubernetes 1.14+
-
-### Installation
-
-There are two ways to install JuiceFS CSI Driver.
-
-#### 1. Install via Helm
-
-##### Prerequisites
-
-- Helm 3.1.0+
-
-##### Install Helm
-
-Helm is a tool for managing Kubernetes charts. Charts are packages of pre-configured Kubernetes resources.
-
-To install Helm, refer to the [Helm Installation Guide](https://helm.sh/docs/intro/install) and ensure that the `helm` binary is in the `PATH` of your shell.
-
-##### Using Helm To Deploy
-
-1. Prepare a YAML file
-
-   Create a configuration file, for example: `values.yaml`, copy and complete the following configuration information. Among them, the `backend` part is the information related to the JuiceFS file system, you can refer to ["JuiceFS Quick Start Guide"](../getting-started/README.md) for more information. If you are using a JuiceFS volume that has been created, you only need to fill in the two items `name` and `metaurl`. The `mountPod` part can specify CPU/memory limits and requests of mount pod for pods using this driver. Unneeded items should be deleted, or its value should be left blank.
-
-   :::info
-   Please refer to [documentation](https://github.com/juicedata/charts/blob/main/charts/juicefs-csi-driver/README.md#values) for all configuration items supported by Helm chart of JuiceFS CSI Driver
-   :::
-
-   ```yaml title="values.yaml"
-   storageClasses:
-   - name: juicefs-sc
-     enabled: true
-     reclaimPolicy: Retain
-     backend:
-       name: "<name>"
-       metaurl: "<meta-url>"
-       storage: "<storage-type>"
-       accessKey: "<access-key>"
-       secretKey: "<secret-key>"
-       bucket: "<bucket>"
-       # If you need to set the time zone of the JuiceFS Mount Pod, please uncomment the next line, the default is UTC time.
-       # envs: "{TZ: Asia/Shanghai}"
-     mountPod:
-       resources:
-         limits:
-           cpu: "<cpu-limit>"
-           memory: "<memory-limit>"
-         requests:
-           cpu: "<cpu-request>"
-           memory: "<memory-request>"
-   ```
-
-2. Check and update kubelet root directory
-
-   Execute the following command.
-
-   ```shell
-   ps -ef | grep kubelet | grep root-dir
-   ```
-
-   If the result is not empty, it means that the root directory (`--root-dir`) of kubelet is not the default value (`/var/lib/kubelet`) and you need to set `kubeletDir` to the current root directly of kubelet in the configuration file `values.yaml` prepared in the first step.
-
-   ```yaml
-   kubeletDir: <kubelet-dir>
-   ```
-
-3. Deploy
-
-   Execute the following three commands in sequence to deploy the JuiceFS CSI Driver through Helm.
-
-   ```sh
-   helm repo add juicefs-csi-driver https://juicedata.github.io/charts/
-   helm repo update
-   helm install juicefs-csi-driver juicefs-csi-driver/juicefs-csi-driver -n kube-system -f ./values.yaml
-   ```
-
-4. Check the deployment
-
-   - **Check pods are running**: the deployment will launch a `StatefulSet` named `juicefs-csi-controller` with replica `1` and a `DaemonSet` named `juicefs-csi-node`, so run `kubectl -n kube-system get pods -l app.kubernetes.io/name=juicefs-csi-driver` should see `n+1` (where `n` is the number of worker nodes of the Kubernetes cluster) pods is running. For example:
-
-     ```sh
-     $ kubectl -n kube-system get pods -l app.kubernetes.io/name=juicefs-csi-driver
-     NAME                       READY   STATUS    RESTARTS   AGE
-     juicefs-csi-controller-0   3/3     Running   0          22m
-     juicefs-csi-node-v9tzb     3/3     Running   0          14m
-     ```
-
-   - **Check secret**: `kubectl -n kube-system describe secret juicefs-sc-secret` will show the secret with above `backend` fields in `values.yaml`:
-
-     ```
-     Name:         juicefs-sc-secret
-     Namespace:    kube-system
-     Labels:       app.kubernetes.io/instance=juicefs-csi-driver
-                   app.kubernetes.io/managed-by=Helm
-                   app.kubernetes.io/name=juicefs-csi-driver
-                   app.kubernetes.io/version=0.7.0
-                   helm.sh/chart=juicefs-csi-driver-0.1.0
-     Annotations:  meta.helm.sh/release-name: juicefs-csi-driver
-                   meta.helm.sh/release-namespace: default
-
-     Type:  Opaque
-
-     Data
-     ====
-     access-key:  0 bytes
-     bucket:      47 bytes
-     metaurl:     54 bytes
-     name:        4 bytes
-     secret-key:  0 bytes
-     storage:     2 bytes
-     ```
-
-   - **Check storage class**: `kubectl get sc juicefs-sc` will show the storage class like this:
-
-     ```
-     NAME         PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
-     juicefs-sc   csi.juicefs.com   Retain          Immediate           false                  69m
-     ```
-
-#### 2. Install via kubectl
-
-Since Kubernetes will deprecate some old APIs when a new version is released, you need to choose the appropriate deployment configuration file.
-
-1. Check the root directory path of kubelet
-
-   Execute the following command on any non-Master node in the Kubernetes cluster.
-
-   ```shell
-   ps -ef | grep kubelet | grep root-dir
-   ```
-
-2. Deploy
-
-   - **If the check command returns a non-empty result**, it means that the root directory (`--root-dir`) of the kubelet is not the default (`/var/lib/kubelet`), so you need to update the `kubeletDir` path in the CSI Driver's deployment file and deploy.
-
-     :::note
-     Please replace `{{KUBELET_DIR}}` in the below command with the actual root directory path of kubelet.
-     :::
-
-     ```shell
-     # Kubernetes version >= v1.18
-     curl -sSL https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml | sed 's@/var/lib/kubelet@{{KUBELET_DIR}}@g' | kubectl apply -f -
-     ```
-
-     ```shell
-     # Kubernetes version < v1.18
-     curl -sSL https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s_before_v1_18.yaml | sed 's@/var/lib/kubelet@{{KUBELET_DIR}}@g' | kubectl apply -f -
-     ```
-
-   - **If the check command returns an empty result**, you can deploy directly without modifying the configuration:
-
-     ```shell
-     # Kubernetes version >= v1.18
-     kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml
-     ```
-
-     ```shell
-     # Kubernetes version < v1.18
-     kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s_before_v1_18.yaml
-     ```
-
-3. Create storage class
-
-   Create a configuration file with reference to the following content, for example: `juicefs-sc.yaml`, fill in the configuration information of the JuiceFS file system in the `stringData` section:
-
-   ```yaml title="juicefs-sc.yaml" {7-15}
-   apiVersion: v1
-   kind: Secret
-   metadata:
-     name: juicefs-sc-secret
-     namespace: kube-system
-   type: Opaque
-   stringData:
-     name: "test"
-     metaurl: "redis://juicefs.afyq4z.0001.use1.cache.amazonaws.com/3"
-     storage: "s3"
-     bucket: "https://juicefs-test.s3.us-east-1.amazonaws.com"
-     access-key: ""
-     secret-key: ""
-     # If you need to set the time zone of the JuiceFS Mount Pod, please uncomment the next line, the default is UTC time.
-     # envs: "{TZ: Asia/Shanghai}"
-   ---
-   apiVersion: storage.k8s.io/v1
-   kind: StorageClass
-   metadata:
-     name: juicefs-sc
-   provisioner: csi.juicefs.com
-   reclaimPolicy: Retain
-   volumeBindingMode: Immediate
-   parameters:
-     csi.storage.k8s.io/node-publish-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/node-publish-secret-namespace: kube-system
-     csi.storage.k8s.io/provisioner-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/provisioner-secret-namespace: kube-system
-   ```
-
-   Execute the command to deploy the storage class:
-
-   ```shell
-   kubectl apply -f ./juicefs-sc.yaml
-   ```
-
-   In addition, you can also extract the `Secret` part from the configuration file above, and append them as command line options of `kubectl`:
-
-   ```bash
-   kubectl -n kube-system create secret generic juicefs-sc-secret \
-     --from-literal=name=test \
-     --from-literal=metaurl=redis://juicefs.afyq4z.0001.use1.cache.amazonaws.com/3 \
-     --from-literal=storage=s3 \
-     --from-literal=bucket=https://juicefs-test.s3.us-east-1.amazonaws.com \
-     --from-literal=access-key="" \
-     --from-literal=secret-key=""
-   ```
-
-   In this way, the storage class configuration file `juicefs-sc.yaml` should look like the following:
-
-   ```yaml title="juicefs-sc.yaml"
-   apiVersion: storage.k8s.io/v1
-   kind: StorageClass
-   metadata:
-     name: juicefs-sc
-   provisioner: csi.juicefs.com
-   reclaimPolicy: Retain
-   parameters:
-     csi.storage.k8s.io/node-publish-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/node-publish-secret-namespace: kube-system
-     csi.storage.k8s.io/provisioner-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/provisioner-secret-namespace: kube-system
-   ```
-
-   Then deploy the storage class with `kubectl apply`:
-
-   ```shell
-   kubectl apply -f ./juicefs-sc.yaml
-   ```
-
-### Use JuiceFS to provide storage for Pods
-
-JuiceFS CSI Driver supports both static and dynamic PersistentVolume (PV). You can either manually assign the PV created previously to Pods, or dynamically create volumes by using PersistentVolumeClaim (PVC) when Pods are deployed.
-
-For example, you can use the following configuration to create a configuration file named `development.yaml`, which creates a PersistentVolume for the Nginx container by using PersistentVolumeClaim and mounts it to the directory `/config` of the container:
-
-```yaml title="development.yaml"
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: web-pvc
-spec:
-  accessModes:
-    - ReadWriteMany
-  resources:
-    requests:
-      storage: 10Pi
-  storageClassName: juicefs-sc
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: nginx-run
-spec:
-  selector:
-    matchLabels:
-      app: nginx
-  template:
-    metadata:
-      labels:
-        app: nginx
-    spec:
-      containers:
-        - name: nginx
-          image: linuxserver/nginx
-          ports:
-            - containerPort: 80
-          volumeMounts:
-            - mountPath: /config
-              name: web-data
-      volumes:
-        - name: web-data
-          persistentVolumeClaim:
-            claimName: web-pvc
-```
-
-Deploy Pods with command `kubectl apply`:
-
-```
-kubectl apply -f ./development.yaml
-```
-
-After the deployment succeeds, check the pods status:
-
-```shell
-$ kubectl get pods
-NAME                         READY   STATUS    RESTARTS   AGE
-nginx-run-7d6fb7d6df-cfsvp   1/1     Running   0          21m
-```
-
-You can simply use the `kubectl exec` command to view the file system mount status in the container:
-
-```shell
-$ kubectl exec nginx-run-7d6fb7d6df-cfsvp -- df -Th
-Filesystem     Type          Size  Used Avail Use% Mounted on
-overlay        overlay        40G  7.0G   34G  18% /
-tmpfs          tmpfs          64M     0   64M   0% /dev
-tmpfs          tmpfs         3.8G     0  3.8G   0% /sys/fs/cgroup
-JuiceFS:jfs    fuse.juicefs  1.0P  180M  1.0P   1% /config
-...
-```
-
-From the results returned from the container, you can see that it is in full compliance with expectations, and the JuiceFS volume has been mounted to the `/config` directory you specified.
-
-When a PersistentVolume is dynamically created using PersistentVolumeClaim as above, JuiceFS will create a directory with the same name as the PersistentVolume in the root directory of the file system and mount it to the container. Execute the following command to view all PersistentVolumes in the cluster:
-
-```shell
-$ kubectl get pv -A
-NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
-pvc-b670c8a1-2962-497c-afa2-33bc8b8bb05d   10Pi       RWX            Retain           Bound    default/web-pvc   juicefs-sc              34m
-```
-
-Mount the same JuiceFS storage from an external host, and you can see the PersistentVolumes currently in use and the PersistentVolumes that have been created.
-
-![](../images/pv-on-juicefs.png)
-
-### Create more JuiceFS storage classes
-
-You can repeat the previous steps to create as many storage classes as needed by using JuiceFS CSI Driver. Don't forget to modify the name of the storage class and the configuration information of the JuiceFS file system to avoid conflicts with the previously created storage classes. For example, when using Helm, you can create a configuration file named `juicefs-sc2.yaml`:
-
-```yaml title="juicefs-sc2.yaml"
-storageClasses:
-- name: juicefs-sc2
-  enabled: true
-  reclaimPolicy: Retain
-  backend:
-    name: "jfs-2"
-    metaurl: "redis://example.abc.0001.use1.cache.amazonaws.com/3"
-    storage: "s3"
-    accessKey: ""
-    secretKey: ""
-    bucket: "https://jfs2.s3.us-east-1.amazonaws.com"
-```
-
-Execute the Helm command to deploy:
-
-```shell
-helm repo add juicefs-csi-driver https://juicedata.github.io/charts/
-helm repo update
-helm upgrade juicefs-csi-driver juicefs-csi-driver/juicefs-csi-driver --install -f ./juicefs-sc2.yaml
-```
-
-View the storage classes in the cluster:
-
-```shell
-$ kubectl get sc
-NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
-juicefs-sc           csi.juicefs.com            Retain          Immediate           false                  88m
-juicefs-sc2          csi.juicefs.com            Retain          Immediate           false                  13m
-standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  128m
-```
-
-### Monitoring
-
-Please refer to ["Monitoring"](../administration/monitoring.md) to learn how to collect and show JuiceFS monitoring metrics.
-
-### Learn more
+To use JuiceFS in Kubernetes, refer to [JuiceFS CSI Driver Documentation](https://juicefs.com/docs/csi/introduction/).
 
-To learn more about the JuiceFS CSI Driver, please visit the [project homepage](https://juicefs.com/docs/csi/introduction).
 
 ## Mount JuiceFS in the container
 
diff --git a/docs/en/introduction/comparison/juicefs_vs_cephfs.md b/docs/en/introduction/comparison/juicefs_vs_cephfs.md
index 48358845..b3fa009a 100644
--- a/docs/en/introduction/comparison/juicefs_vs_cephfs.md
+++ b/docs/en/introduction/comparison/juicefs_vs_cephfs.md
@@ -6,13 +6,13 @@ slug: /comparison/juicefs_vs_cephfs
 
 ## Similarities
 
-Both are highly reliable, high-performance resilient distributed file systems with good POSIX compatibility, and can be tried in a variety of file system scenarios.
+Both are highly reliable, high-performance resilient distributed file systems with good POSIX compatibility, and can be used in a variety of scenarios.
 
 ## Differences
 
 ### System Architecture
 
-Both JuiceFS and CephFS employ an architecture that separates data and metadata, but differ greatly in component implementations.
+Both JuiceFS and CephFS employ an architecture that separates data and metadata, but differ greatly in implementations.
 
 #### CephFS
 
@@ -51,7 +51,7 @@ JuiceFS provides a libjfs library, a FUSE client application, Java SDK, etc. It
 | Client data caching             | ✕                     | ✓                  |
 | Hadoop data Locality            | ✕                     | ✓                  |
 | S3-compatible                   | ✕                     | ✓                  |
-| Quota                           | Directory level quota | Volume level quota |
+| Quota                           | Directory level quota | Filesystem (Volume) level quota |
 | Languages                       | C++                   | Go                 |
 | License                         | LGPLv2.1 & LGPLv3     | Apache License 2.0             |
 
@@ -62,7 +62,7 @@ CephFS splits files by [`object_size`](https://docs.ceph.com/en/latest/cephfs/fi
 
 #### [2] Data Compression
 
-Strictly speaking, CephFS itself does not provide data compression but relies on the BlueStore compression on the RADOS layer. JuiceFS, on the other hand, has already compressed data once before uploading a Block to the object storage to reduce the capacity cost in the object storage. In other words, if you use JuiceFS to interact with RADOS, you compress a Block both before and after it enters RADOS, twice in total. Also, as mentioned in **File Chunking**, to guarantee the overwrite performance, CephFS usually does not enable the BlueStore compression.
+Strictly speaking, CephFS itself does not provide data compression but relies on the BlueStore compression on the RADOS layer. JuiceFS, on the other hand, has already compressed data once before uploading a Block to the object storage to reduce the capacity cost in the object storage. In other words, if you use JuiceFS to interact with RADOS, you compress a Block both before and after it enters RADOS, twice in total. Also, as mentioned in **File Chunking**, to guarantee overwrite performance, CephFS usually does not enable the BlueStore compression.
 
 #### [3] Data Encryption
 
diff --git a/docs/zh_cn/deployment/how_to_use_on_kubernetes.md b/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
index f018d856..aaa72914 100644
--- a/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
+++ b/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
@@ -4,399 +4,11 @@ sidebar_position: 3
 slug: /how_to_use_on_kubernetes
 ---
 
-# Kubernetes 使用 JuiceFS 持久化数据
-
-JuiceFS 非常适合用作 Kubernetes 集群的存储层，目前有两种常见的用法。
+JuiceFS 非常适合用作 Kubernetes 集群的存储层，阅读本文以了解如何使用。
 
 ## JuiceFS CSI 驱动
 
-:::tip 提示
-推荐在 Kubernetes 中使用 JuiceFS CSI 驱动的部署方式，了解更多关于 JuiceFS CSI 驱动的信息请访问[项目主页](https://juicefs.com/docs/zh/csi/introduction)。
-:::
-
-[JuiceFS CSI 驱动](https://github.com/juicedata/juicefs-csi-driver)遵循 [CSI](https://github.com/container-storage-interface/spec/blob/master/spec.md) 规范，实现了容器编排系统与 JuiceFS 文件系统之间的接口，支持动态配置 JuiceFS 卷提供给 Pod 使用。
-
-### 版本要求
-
-- Kubernetes 1.14+
-
-### 安装
-
-以下提供了两种安装 JuiceFS CSI 驱动的方式。
-
-#### 方法一：通过 Helm 安装
-
-##### 版本要求
-
-- Helm 3.1.0+
-
-##### 安装 Helm
-
-Helm 是 Kubernetes 的包管理器，Chart 是 Helm 管理的包。你可以把它看作是 Homebrew formula，APT dpkg，或 YUM RPM 在 Kubernetes 中的等价物。
-
-请参照 [Helm 文档](https://helm.sh/docs/intro/install) 进行安装，并确保 `helm` 二进制能在 `PATH` 环境变量中找到。
-
-##### 安装 JuiceFS CSI 驱动
-
-1. 准备配置文件
-
-   创建一个配置文件，例如：`values.yaml`，复制并完善下列配置信息。其中，`backend` 部分是 JuiceFS 文件系统相关的信息，你可以参照[「JuiceFS 快速上手指南」](../getting-started/README.md)了解相关内容。如果使用的是已经提前创建好的 JuiceFS 卷，则只需填写 `name` 和 `metaurl` 这两项即可。`mountPod` 部分可以对使用此驱动的 Pod 设置 CPU 和内存的资源配置。不需要的项可以删除，或者将它的值留空。
-
-   :::info 说明
-   请参考[文档](https://github.com/juicedata/charts/blob/main/charts/juicefs-csi-driver/README.md#values)了解 JuiceFS CSI 驱动的 Helm chart 支持的所有配置项
-   :::
-
-   ```yaml title="values.yaml"
-   storageClasses:
-   - name: juicefs-sc
-     enabled: true
-     reclaimPolicy: Retain
-     backend:
-       name: "<name>"
-       metaurl: "<meta-url>"
-       storage: "<storage-type>"
-       accessKey: "<access-key>"
-       secretKey: "<secret-key>"
-       bucket: "<bucket>"
-       # 如果需要设置 JuiceFS Mount Pod 的时区请将下一行的注释符号删除，默认为 UTC 时间。
-       # envs: "{TZ: Asia/Shanghai}"
-     mountPod:
-       resources:
-         limits:
-           cpu: "<cpu-limit>"
-           memory: "<memory-limit>"
-         requests:
-           cpu: "<cpu-request>"
-           memory: "<memory-request>"
-   ```
-
-2. 检查 kubelet 根目录
-
-   执行以下命令
-
-   ```shell
-   ps -ef | grep kubelet | grep root-dir
-   ```
-
-   如果结果不为空，则代表 kubelet 的根目录（`--root-dir`）不是默认值（`/var/lib/kubelet`），需要在第一步准备的配置文件 `values.yaml` 中将 `kubeletDir` 设置为 kubelet 当前的根目录路径：
-
-   ```yaml
-   kubeletDir: <kubelet-dir>
-   ```
-
-3. 部署
-
-   依次执行以下三条命令，通过 Helm 部署 JuiceFS CSI 驱动。
-
-   ```sh
-   helm repo add juicefs-csi-driver https://juicedata.github.io/charts/
-   ```
-   ```sh
-   helm repo update
-   ```
-   ```sh
-   helm install juicefs-csi-driver juicefs-csi-driver/juicefs-csi-driver -n kube-system -f ./values.yaml
-   ```
-
-4. 检查部署状态
-
-   - **检查 Pods**：部署过程会启动一个名为 `juicefs-csi-controller` 的 `StatefulSet` 及一个 replica，以及一个名为 `juicefs-csi-node` 的 `DaemonSet`。执行命令 `kubectl -n kube-system get pods -l app.kubernetes.io/name=juicefs-csi-driver` 会看到有 `n+1` 个（`n` 指 Kubernetes 的 Node 数量）pod 在运行，例如：
-
-     ```sh
-     kubectl -n kube-system get pods -l app.kubernetes.io/name=juicefs-csi-driver
-     ```
-     ```output
-     NAME                       READY   STATUS    RESTARTS   AGE
-     juicefs-csi-controller-0   3/3     Running   0          22m
-     juicefs-csi-node-v9tzb     3/3     Running   0          14m
-     ```
-
-   - **检查 Secret**：通过命令 `kubectl -n kube-system describe secret juicefs-sc-secret` 可以看到前面 `values.yaml` 配置文件中 `backend` 部分的 secret 信息。
-
-     ```
-     Name:         juicefs-sc-secret
-     Namespace:    kube-system
-     Labels:       app.kubernetes.io/instance=juicefs-csi-driver
-                   app.kubernetes.io/managed-by=Helm
-                   app.kubernetes.io/name=juicefs-csi-driver
-                   app.kubernetes.io/version=0.7.0
-                   helm.sh/chart=juicefs-csi-driver-0.1.0
-     Annotations:  meta.helm.sh/release-name: juicefs-csi-driver
-                   meta.helm.sh/release-namespace: default
-
-     Type:  Opaque
-
-     Data
-     ====
-     access-key:  0 bytes
-     bucket:      47 bytes
-     metaurl:     54 bytes
-     name:        4 bytes
-     secret-key:  0 bytes
-     storage:     2 bytes
-     ```
-
-   - **检查存储类（Storage Class）**：通过命令 `kubectl get sc juicefs-sc` 会看到类似下面的存储类信息。
-
-     ```
-     NAME         PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
-     juicefs-sc   csi.juicefs.com   Retain          Immediate           false                  69m
-     ```
-
-#### 方法二：通过 kubectl 安装
-
-由于 Kubernetes 在版本变更过程中会废弃部分旧的 API，因此需要根据你使用 Kubernetes 版本选择适用的部署文件。
-
-1. 检查 kubelet 根目录
-
-   在 Kubernetes 集群中任意一个非 Master 节点上执行以下命令：
-
-   ```shell
-   ps -ef | grep kubelet | grep root-dir
-   ```
-
-2. 部署
-
-   - **如果上一步检查命令返回的结果不为空**，则代表 kubelet 的根目录（`--root-dir`）不是默认值（`/var/lib/kubelet`），因此需要在 CSI 驱动的部署文件中更新 `kubeletDir` 路径并部署：
-
-     :::note 注意
-     请将下述命令中的 `{{KUBELET_DIR}}` 替换成 kubelet 当前的根目录路径。
-     :::
-
-     ```shell
-     # Kubernetes 版本 >= v1.18
-     curl -sSL https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml | sed 's@/var/lib/kubelet@{{KUBELET_DIR}}@g' | kubectl apply -f -
-     ```
-
-     ```shell
-     # Kubernetes 版本 < v1.18
-     curl -sSL https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s_before_v1_18.yaml | sed 's@/var/lib/kubelet@{{KUBELET_DIR}}@g' | kubectl apply -f -
-     ```
-
-   - **如果前面检查命令返回的结果为空**，无需修改配置，可直接部署：
-
-     ```shell
-     # Kubernetes 版本 >= v1.18
-     kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml
-     ```
-
-     ```shell
-     # Kubernetes 版本 < v1.18
-     kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s_before_v1_18.yaml
-     ```
-
-3. 创建存储类
-
-   参考以下内容创建一个配置文件，例如：`juicefs-sc.yaml`，在 `stringData` 部分填写 JuiceFS 文件系统的配置信息：
-
-   ```yaml title="juicefs-sc.yaml" {7-15}
-   apiVersion: v1
-   kind: Secret
-   metadata:
-     name: juicefs-sc-secret
-     namespace: kube-system
-   type: Opaque
-   stringData:
-     name: "test"
-     metaurl: "redis://juicefs.afyq4z.0001.use1.cache.amazonaws.com/3"
-     storage: "s3"
-     bucket: "https://juicefs-test.s3.us-east-1.amazonaws.com"
-     access-key: ""
-     secret-key: ""
-     # 如果需要设置 JuiceFS Mount Pod 的时区请将下一行的注释符号删除，默认为 UTC 时间。
-     # envs: "{TZ: Asia/Shanghai}"
-   ---
-   apiVersion: storage.k8s.io/v1
-   kind: StorageClass
-   metadata:
-     name: juicefs-sc
-   provisioner: csi.juicefs.com
-   reclaimPolicy: Retain
-   volumeBindingMode: Immediate
-   parameters:
-     csi.storage.k8s.io/node-publish-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/node-publish-secret-namespace: kube-system
-     csi.storage.k8s.io/provisioner-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/provisioner-secret-namespace: kube-system
-   ```
-
-   执行命令，部署存储类：
-
-   ```shell
-   kubectl apply -f ./juicefs-sc.yaml
-   ```
-
-   另外，你也可以将上述配置文件中 `Secret` 部分抽离出来，通过 `kubectl` 在命令行上创建：
-
-   ```shell
-   kubectl -n kube-system create secret generic juicefs-sc-secret \
-     --from-literal=name=test \
-     --from-literal=metaurl=redis://juicefs.afyq4z.0001.use1.cache.amazonaws.com/3 \
-     --from-literal=storage=s3 \
-     --from-literal=bucket=https://juicefs-test.s3.us-east-1.amazonaws.com \
-     --from-literal=access-key="" \
-     --from-literal=secret-key=""
-   ```
-
-   这样一来，存储类的配置文件 `juicefs-sc.yaml` 应该像下面这样：
-
-   ```yaml title="juicefs-sc.yaml"
-   apiVersion: storage.k8s.io/v1
-   kind: StorageClass
-   metadata:
-     name: juicefs-sc
-   provisioner: csi.juicefs.com
-   reclaimPolicy: Retain
-   parameters:
-     csi.storage.k8s.io/node-publish-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/node-publish-secret-namespace: kube-system
-     csi.storage.k8s.io/provisioner-secret-name: juicefs-sc-secret
-     csi.storage.k8s.io/provisioner-secret-namespace: kube-system
-   ```
-
-   然后通过 `kubectl apply` 部署存储类：
-
-   ```shell
-   kubectl apply -f ./juicefs-sc.yaml
-   ```
-
-### 使用 JuiceFS 为 Pod 提供存储
-
-JuiceFS CSI 驱动同时支持静态和动态 PersistentVolume（PV），你既可以将提前创建的 PV 手动分配给 Pods，也可以在部署 Pods 时通过 PersistentVolumeClaim（PVC）动态地创建卷。
-
-例如，可以使用下面的配置创建一个名为 `development.yaml` 的配置文件，它通过 PVC 为 Nginx 容器创建持久化卷，并挂载到了容器的 `/config` 目录：
-
-```yaml title="development.yaml"
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: web-pvc
-spec:
-  accessModes:
-    - ReadWriteMany
-  resources:
-    requests:
-      storage: 10Pi
-  storageClassName: juicefs-sc
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: nginx-run
-spec:
-  selector:
-    matchLabels:
-      app: nginx
-  template:
-    metadata:
-      labels:
-        app: nginx
-    spec:
-      containers:
-        - name: nginx
-          image: linuxserver/nginx
-          ports:
-            - containerPort: 80
-          volumeMounts:
-            - mountPath: /config
-              name: web-data
-      volumes:
-        - name: web-data
-          persistentVolumeClaim:
-            claimName: web-pvc
-```
-
-通过 `kubectl apply` 部署 Pods：
-
-```
-kubectl apply -f ./development.yaml
-```
-
-部署成功以后，查看 pods 状态：
-
-```shell
-kubectl get pods
-```
-```output
-NAME                         READY   STATUS    RESTARTS   AGE
-nginx-run-7d6fb7d6df-cfsvp   1/1     Running   0          21m
-```
-
-可以简单地通过 `kubectl exec` 命令查看容器中的文件系统挂载情况：
-
-```shell
-kubectl exec nginx-run-7d6fb7d6df-cfsvp -- df -Th
-```
-```output
-Filesystem     Type          Size  Used Avail Use% Mounted on
-overlay        overlay        40G  7.0G   34G  18% /
-tmpfs          tmpfs          64M     0   64M   0% /dev
-tmpfs          tmpfs         3.8G     0  3.8G   0% /sys/fs/cgroup
-JuiceFS:jfs    fuse.juicefs  1.0P  180M  1.0P   1% /config
-...
-```
-
-从容器中返回的结果可以看到，完全符合预期，JuiceFS 卷已经挂载到了指定的 `/config` 目录。
-
-像上面这样通过 PVC 动态创建 PV 时，JuiceFS 会在文件系统根目录创建与 PV 同名的目录并挂载到容器中。执行下列命令，可以查看集群中所有 PV：
-
-```shell
-kubectl get pv -A
-```
-```output
-NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
-pvc-b670c8a1-2962-497c-afa2-33bc8b8bb05d   10Pi       RWX            Retain           Bound    default/web-pvc   juicefs-sc              34m
-```
-
-通过外部主机挂载同一个 JuiceFS 存储，可以看到当前正在使用的 PV 以及曾经创建的 PV。
-
-![](../images/pv-on-juicefs.png)
-
-### 创建更多 JuiceFS 存储类
-
-你可以根据实际需要重复前面的步骤，通过 JuiceFS CSI 驱动创建任意数量的存储类。但要注意修改存储类的名称以及 JuiceFS 文件系统的配置信息，避免与已创建的存储类冲突。例如，使用 Helm 时可以创建一个名为 `juicefs-sc2.yaml` 的配置文件：
-
-```yaml title="juicefs-sc2.yaml"
-storageClasses:
-- name: juicefs-sc2
-  enabled: true
-  reclaimPolicy: Retain
-  backend:
-    name: "jfs-2"
-    metaurl: "redis://example.abc.0001.use1.cache.amazonaws.com/3"
-    storage: "s3"
-    accessKey: ""
-    secretKey: ""
-    bucket: "https://jfs2.s3.us-east-1.amazonaws.com"
-```
-
-执行 Helm 命令进行部署：
-
-```shell
-helm repo add juicefs-csi-driver https://juicedata.github.io/charts/
-helm repo update
-helm upgrade juicefs-csi-driver juicefs-csi-driver/juicefs-csi-driver --install -f ./juicefs-sc2.yaml
-```
-
-查看集群中存储类的情况：
-
-```shell
-kubectl get sc
-```
-```shell
-NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
-juicefs-sc           csi.juicefs.com            Retain          Immediate           false                  88m
-juicefs-sc2          csi.juicefs.com            Retain          Immediate           false                  13m
-standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  128m
-```
-
-### 监控
-
-请查看[「监控」](../administration/monitoring.md)文档了解如何收集及展示 JuiceFS 监控指标
-
-### 了解更多
-
-了解更多关于 JuiceFS CSI 驱动的信息，请访问[项目主页](https://juicefs.com/docs/zh/csi/introduction)。
+在 Kubernetes 中使用 JuiceFS，请阅读[「JuiceFS CSI 驱动文档」](https://juicefs.com/docs/zh/csi/introduction)。
 
 ## 在容器中挂载 JuiceFS
 
diff --git a/docs/zh_cn/introduction/comparison/juicefs_vs_cephfs.md b/docs/zh_cn/introduction/comparison/juicefs_vs_cephfs.md
index bb670ded..3257e63e 100644
--- a/docs/zh_cn/introduction/comparison/juicefs_vs_cephfs.md
+++ b/docs/zh_cn/introduction/comparison/juicefs_vs_cephfs.md
@@ -51,7 +51,7 @@ JuiceFS 主要实现一个 libjfs 库和 FUSE 客户端程序、Java SDK 等，
 | 客户端数据缓存          | ✕                 | ✓             |
 | Hadoop 数据本地性       | ✕                 | ✓             |
 | S3 兼容                 | ✕                 | ✓             |
-| 配额                    | 目录级配额        | Volume 级配额 |
+| 配额                    | 目录级配额        | 文件系统（Volume）级配额 |
 | 开发语言                | C++               | Go            |
 | 开源协议                | LGPLv2.1 & LGPLv3 | Apache License 2.0        |
 
diff --git a/pkg/meta/base.go b/pkg/meta/base.go
index 4cebfc1a..0d403653 100644
--- a/pkg/meta/base.go
+++ b/pkg/meta/base.go
@@ -1214,6 +1214,32 @@ func (m *baseMeta) Check(ctx Context, fpath string, repair bool, recursive bool)
 	return
 }
 
+func (m *baseMeta) Chroot(ctx Context, subdir string) syscall.Errno {
+	for subdir != "" {
+		ps := strings.SplitN(subdir, "/", 2)
+		if ps[0] != "" {
+			var attr Attr
+			var inode Ino
+			r := m.Lookup(ctx, m.root, ps[0], &inode, &attr)
+			if r == syscall.ENOENT {
+				r = m.Mkdir(ctx, m.root, ps[0], 0777, 0, 0, &inode, &attr)
+			}
+			if r != 0 {
+				return r
+			}
+			if attr.Typ != TypeDirectory {
+				return syscall.ENOTDIR
+			}
+			m.root = inode
+		}
+		if len(ps) == 1 {
+			break
+		}
+		subdir = ps[1]
+	}
+	return 0
+}
+
 func (m *baseMeta) CompactAll(ctx Context, threads int, bar *utils.Bar) syscall.Errno {
 	var wg sync.WaitGroup
 	ch := make(chan cchunk, 1000000)
diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go
index 09926e9b..7a700d99 100644
--- a/pkg/meta/interface.go
+++ b/pkg/meta/interface.go
@@ -355,6 +355,8 @@ type Meta interface {
 	GetPaths(ctx Context, inode Ino) []string
 	// Check integrity of an absolute path and repair it if asked
 	Check(ctx Context, fpath string, repair bool, recursive bool) syscall.Errno
+	// Change root to a directory specified by subdir
+	Chroot(ctx Context, subdir string) syscall.Errno
 
 	// OnMsg add a callback for the given message type.
 	OnMsg(mtype uint32, cb MsgCallback)
diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go
index 594dcb76..6cdd24f8 100644
--- a/pkg/meta/redis.go
+++ b/pkg/meta/redis.go
@@ -202,8 +202,7 @@ func newRedisMeta(driver, addr string, conf *Config) (Meta, error) {
 	}
 	m.en = m
 	m.checkServerConfig()
-	m.root, err = lookupSubdir(m, conf.Subdir)
-	return m, err
+	return m, nil
 }
 
 func (m *redisMeta) Shutdown() error {
diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go
index 09f8b801..d57e1e3b 100644
--- a/pkg/meta/sql.go
+++ b/pkg/meta/sql.go
@@ -227,9 +227,7 @@ func newSQLMeta(driver, addr string, conf *Config) (Meta, error) {
 		db:       engine,
 	}
 	m.en = m
-	m.root, err = lookupSubdir(m, conf.Subdir)
-
-	return m, err
+	return m, nil
 }
 
 func (m *dbMeta) Shutdown() error {
diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go
index 10fa01c3..6a9186ce 100644
--- a/pkg/meta/tkv.go
+++ b/pkg/meta/tkv.go
@@ -89,8 +89,7 @@ func newKVMeta(driver, addr string, conf *Config) (Meta, error) {
 		client:   client,
 	}
 	m.en = m
-	m.root, err = lookupSubdir(m, conf.Subdir)
-	return m, err
+	return m, nil
 }
 
 func (m *kvMeta) Shutdown() error {
diff --git a/pkg/meta/utils.go b/pkg/meta/utils.go
index c40b3638..86f910b7 100644
--- a/pkg/meta/utils.go
+++ b/pkg/meta/utils.go
@@ -18,7 +18,6 @@ package meta
 
 import (
 	"bytes"
-	"fmt"
 	"net/url"
 	"runtime/debug"
 	"sort"
@@ -128,33 +127,6 @@ func align4K(length uint64) int64 {
 	return int64((((length - 1) >> 12) + 1) << 12)
 }
 
-func lookupSubdir(m Meta, subdir string) (Ino, error) {
-	var root Ino = 1
-	for subdir != "" {
-		ps := strings.SplitN(subdir, "/", 2)
-		if ps[0] != "" {
-			var attr Attr
-			var inode Ino
-			r := m.Lookup(Background, root, ps[0], &inode, &attr)
-			if r == syscall.ENOENT {
-				r = m.Mkdir(Background, root, ps[0], 0777, 0, 0, &inode, &attr)
-			}
-			if r != 0 {
-				return 0, fmt.Errorf("lookup subdir %s: %s", ps[0], r)
-			}
-			if attr.Typ != TypeDirectory {
-				return 0, fmt.Errorf("%s is not a redirectory", ps[0])
-			}
-			root = inode
-		}
-		if len(ps) == 1 {
-			break
-		}
-		subdir = ps[1]
-	}
-	return root, nil
-}
-
 type plockRecord struct {
 	Type  uint32
 	Pid   uint32


Test patch:
diff --git a/cmd/mdtest.go b/cmd/mdtest.go
index af1e67bc..c95ddf93 100644
--- a/cmd/mdtest.go
+++ b/cmd/mdtest.go
@@ -203,6 +203,9 @@ func initForMdtest(c *cli.Context, mp string, metaUrl string) *fs.FileSystem {
 	if err != nil {
 		logger.Fatalf("load setting: %s", err)
 	}
+	if st := m.Chroot(meta.Background, metaConf.Subdir); st != 0 {
+		logger.Fatalf("Chroot to %s: %s", metaConf.Subdir, st)
+	}
 	registerer, registry := wrapRegister(mp, format.Name)
 
 	blob, err := NewReloadableStorage(format, m, updateFormat(c))
diff --git a/pkg/meta/load_dump_test.go b/pkg/meta/load_dump_test.go
index 26e7956b..55978949 100644
--- a/pkg/meta/load_dump_test.go
+++ b/pkg/meta/load_dump_test.go
@@ -212,6 +212,7 @@ func testLoadDump(t *testing.T, name, addr string) {
 		testDump(t, m, 1, sampleFile, "test.dump")
 		m.Shutdown()
 		m = NewClient(addr, &Config{Retries: 10, Strict: true, Subdir: "d1"})
+		_ = m.Chroot(Background, "d1")
 		testDump(t, m, 1, subSampleFile, "test_subdir.dump")
 		testDump(t, m, 0, sampleFile, "test.dump")
 		_ = m.Shutdown()
@@ -240,9 +241,8 @@ func TestLoadDump_MemKV(t *testing.T) {
 		_ = os.Remove(settingPath)
 		m := testLoad(t, "memkv://user:pass@test/jfs", sampleFile)
 		if kvm, ok := m.(*kvMeta); ok { // memkv will be empty if created again
-			var err error
-			if kvm.root, err = lookupSubdir(kvm, "d1"); err != nil {
-				t.Fatalf("lookup subdir d1: %s", err)
+			if st := kvm.Chroot(Background, "d1"); st != 0 {
+				t.Fatalf("Chroot to subdir d1: %s", st)
 			}
 		}
 		testDump(t, m, 1, subSampleFile, "test_subdir.dump")