Skip to content

34.JuiceFS

JuiceFS 是一款面向云原生设计的高性能分布式文件系统,在 Apache 2.0 开源协议下发布。提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。

img

简介

JuiceFS 采用 「数据」与「元数据」分离存储 的架构,从而实现文件系统的分布式设计。文件数据本身会被切分保存在对象存储(例如 Amazon S3),而元数据则可以保存在 Redis、MySQL、TiKV、SQLite 等多种数据库中,你可以根据场景与性能要求进行选择。

JuiceFS 提供了丰富的 API,适用于各种形式数据的管理、分析、归档、备份,可以在不修改代码的前提下无缝对接大数据、机器学习、人工智能等应用平台,为其提供海量、弹性、低价的高性能存储。运维人员不用再为可用性、灾难恢复、监控、扩容等工作烦恼,专注于业务开发,提升研发效率。同时运维细节的简化,对 DevOps 极其友好。

核心特性

  • POSIX 兼容:像本地文件系统一样使用,无缝对接已有应用,无业务侵入性;
  • HDFS 兼容:完整兼容 HDFS API,提供更强的元数据性能;
  • S3 兼容:提供 S3 网关 实现 S3 协议兼容的访问接口;
  • 云原生:通过 Kubernetes CSI 驱动 轻松地在 Kubernetes 中使用 JuiceFS;
  • 分布式设计:同一文件系统可在上千台服务器同时挂载,高性能并发读写,共享数据;
  • 强一致性:确认的文件修改会在所有服务器上立即可见,保证强一致性;
  • 强悍性能:毫秒级延迟,近乎无限的吞吐量(取决于对象存储规模),查看性能测试结果;
  • 数据安全:支持传输中加密(encryption in transit)和静态加密(encryption at rest),查看详情;
  • 文件锁:支持 BSD 锁(flock)和 POSIX 锁(fcntl);
  • 数据压缩:支持 LZ4 和 Zstandard 压缩算法,节省存储空间。

应用场景

JuiceFS 为海量数据存储设计,可以作为很多分布式文件系统和网络文件系统的替代,特别是以下场景:

  • 大数据分析:HDFS 兼容;与主流计算引擎(Spark、Presto、Hive 等)无缝衔接;无限扩展的存储空间;运维成本几乎为 0;性能远好于直接对接对象存储。
  • 机器学习:POSIX 兼容,可以支持所有机器学习、深度学习框架;方便的文件共享还能提升团队管理、使用数据效率。
  • Kubernetes:JuiceFS 支持 Kubernetes CSI;为容器提供解耦的文件存储,令应用服务可以无状态化;方便地在容器间共享数据。
  • 共享工作区:可以在任意主机挂载;没有客户端并发读写限制;POSIX 兼容已有的数据流和脚本操作。
  • 数据备份:在无限平滑扩展的存储空间备份各种数据,结合共享挂载功能,可以将多主机数据汇总至一处,做统一备份。

数据隐私

JuiceFS 是开源软件,你可以在 GitHub 找到完整的源代码。在使用 JuiceFS 存储数据时,数据会按照一定的规则被拆分成数据块并保存在你自己定义的对象存储或其它存储介质中,数据所对应的元数据则存储在你自己定义的数据库中。

架构

JuiceFS 整体上主要由三个部分组成。

img

  • 客户端(Client):所有文件读写,乃至于碎片合并、回收站文件过期删除等后台任务,均在客户端中发生。所以客户端需要同时与对象存储和元数据引擎打交道。客户端支持众多接入方式:

  • 通过 FUSE,JuiceFS 文件系统能够以 POSIX 兼容的方式挂载到服务器,将海量云端存储直接当做本地存储来使用。

  • 通过 Hadoop Java SDK,JuiceFS 文件系统能够直接替代 HDFS,为 Hadoop 提供低成本的海量存储。
  • 通过 Kubernetes CSI 驱动,JuiceFS 文件系统能够直接为 Kubernetes 提供海量存储。
  • 通过 S3 网关,使用 S3 作为存储层的应用可直接接入,同时可使用 AWS CLI、s3cmd、MinIO client 等工具访问 JuiceFS 文件系统。
  • 通过 WebDAV 服务,以 HTTP 协议,以类似 RESTful API 的方式接入 JuiceFS 并直接操作其中的文件。

  • 数据存储(Data Storage):文件将会切分上传保存在对象存储服务,既可以使用公有云的对象存储,也可以接入私有部署的自建对象存储。JuiceFS 支持几乎所有的公有云对象存储,同时也支持 OpenStack Swift、Ceph、MinIO 等私有化的对象存储。

  • 元数据引擎(Metadata Engine):用于存储文件元数据(metadata),包含以下内容:

  • 常规文件系统的元数据:文件名、文件大小、权限信息、创建修改时间、目录结构、文件属性、符号链接、文件锁等。

  • JuiceFS 独有的元数据:文件的 chunk 及 slice 映射关系、客户端 session 等。

JuiceFS 采用多引擎设计,目前已支持 Redis、TiKV、MySQL/MariaDB、PostgreSQL、SQLite 等作为元数据服务引擎,也将陆续实现更多元数据存储引擎。

JuiceFS 如何存储文件

与传统文件系统只能使用本地磁盘存储数据和对应的元数据的模式不同,JuiceFS 会将数据格式化以后存储在对象存储,同时会将文件的元数据存储在专门的元数据服务中,这样的架构让 JuiceFS 成为一个强一致性的高性能分布式文件系统。

任何存入 JuiceFS 的文件都会被拆分成一个或多个 「Chunk」(最大 64 MiB)。而每个 Chunk 又由一个或多个 「Slice」 组成。Chunk 的存在是为了对文件做切分,优化大文件性能,而 Slice 则是为了进一步优化各类文件写操作,二者同为文件系统内部的逻辑概念。Slice 的长度不固定,取决于文件写入的方式。每个 Slice 又会被进一步拆分成 「Block」(默认大小上限为 4 MiB),成为最终上传至对象存储的最小存储单元

img

所以我们在对象存储平台的文件浏览器中找不到存入 JuiceFS 的源文件,存储桶中只有一个 chunks 目录和一堆数字编号的目录和文件,这正是经过 JuiceFS 拆分存储的数据块。与此同时,文件与 Chunks、Slices、Blocks 的对应关系等元数据信息存储在元数据引擎中。正是这样的分离设计,让 JuiceFS 文件系统得以高性能运作。

img

JuiceFS 的存储设计,还有着以下技术特点:

  • 对于任意大小的文件,JuiceFS 都不进行合并存储,这也是为了性能考虑,避免读放大。
  • 提供强一致性保证,但也可以根据场景需要与缓存功能一起调优,比如通过设置出更激进的元数据缓存,牺牲一部分一致性,换取更好的性能。。
  • 支持并默认开启「回收站」功能,删除文件后保留一段时间才彻底清理,最大程度避免误删文件导致事故。

安装

JuiceFS 是采用 Go 语言开发的,所以具有良好的跨平台能力,支持在几乎所有主流架构的各类操作系统上运行,包括且不限于 Linux、macOS、Windows 等。

JuiceFS 客户端只有一个二进制文件,可以下载预编译的版本直接解压使用,也可以用源代码手动编译,也可以直接使用一键安装脚本 curl -sSL https://d.juicefs.com/install | sh - 自动下载安装最新版 JuiceFS 客户端。

如果你在 Mac 下面使用,需要先安装 FUSE for macOS,这是因为 macOS 默认不支持 FUSE 接口。

➜ juicefs --version
juicefs version 1.0.4+2023-04-06.f1c475d

单机模式

JuiceFS 文件系统由「对象存储」和「数据库」共同驱动,除了对象存储,还支持使用本地磁盘、WebDAV 和 HDFS 等作为底层存储。这里我们首先使用本地磁盘和 SQLite 数据库快速创建一个单机文件系统用以了解和体验 JuiceFS。

当然首先需要安装 JuiceFS 的客户端,然后接下来我们就可以使用 juicefs format 命令来创建一个 JuiceFS 文件系统了,该命令的格式为:

juicefs format [command options] META-URL NAME

从命令可以看出格式化文件系统需要提供 3 种信息:

  • [command options]:设定文件系统的存储介质,留空则默认使用本地磁盘作为存储介质,路径为 $HOME/.juicefs/local/var/jfsC:/jfs/local
  • META-URL:用来设置元数据存储,即数据库相关的信息,通常是数据库的 URL 或文件路径
  • NAME:是文件系统的名称

比如我们这里创建一个名为 ydzsfs 的文件系统,则可以使用如下所示的命令:

➜ juicefs format sqlite3://ydzsfs.db ydzsfs
2023/04/25 15:36:44.287211 juicefs[218656] <INFO>: Meta address: sqlite3://ydzsfs.db [interface.go:401]
2023/04/25 15:36:44.288042 juicefs[218656] <INFO>: Data use file:///home/ubuntu/.juicefs/local/ydzsfs/ [format.go:434]
2023/04/25 15:36:44.400391 juicefs[218656] <INFO>: Volume is formatted as {
  "Name": "ydzsfs",
  "UUID": "67a050b2-9a40-4852-882c-24c092c03b4a",
  "Storage": "file",
  "Bucket": "/home/ubuntu/.juicefs/local/",
  "BlockSize": 4096,
  "Compression": "none",
  "TrashDays": 1,
  "MetaVersion": 1
} [format.go:471]

从返回的信息中可以看到,该文件系统使用 SQLite 作为元数据存储引擎,数据库文件位于当前目录,文件名为 ydzsfs.db,保存了 ydzsfs 文件系统的所有信息,它构建了完善的表结构,将用作所有数据的元信息的存储。

img

由于没有指定任何存储相关的选项,客户端默认使用本地磁盘作为存储介质,根据返回的信息, ydzsfs 的存储路径为 file:///home/ubuntu/.juicefs/local/ydzsfs/,即当前用户主目录下的 .juicefs/local/ydzsfs/

➜ ls -la ~/.juicefs/local/ydzsfs
total 12
drwxr-xr-x 2 ubuntu ubuntu 4096 Apr 25 15:36 .
drwxr-xr-x 3 ubuntu ubuntu 4096 Apr 25 15:36 ..
-rw-r--r-- 1 ubuntu ubuntu   36 Apr 25 15:36 juicefs_uuid

这样我们就成功创建了一个文件系统了,接下来我们就可以使用 juicefs mount 命令来挂载文件系统了,该命令的一般格式为:

juicefs mount [command options] META-URL MOUNTPOINT

与创建文件系统的命令类似,挂载文件系统需要提供以下信息:

  • [command options]:用来指定文件系统相关的选项,例如:-d 可以实现后台挂载;
  • META-URL:用来设置元数据存储,即数据库相关的信息,通常是数据库的 URL 或文件路径;
  • MOUNTPOINT:指定文件系统的挂载点。

由于 SQLite 是单文件数据库,挂载时要注意数据库文件的的路径,JuiceFS 同时支持相对路径和绝对路径。比如我们这里可以使用以下命令将 ydzsfs 文件系统挂载到 ./jfs 文件夹:

➜ juicefs mount sqlite3://ydzsfs.db ./jfs
2023/04/25 15:39:52.365555 juicefs[220965] <INFO>: Meta address: sqlite3://ydzsfs.db [interface.go:401]
2023/04/25 15:39:52.366833 juicefs[220965] <INFO>: Data use file:///home/ubuntu/.juicefs/local/ydzsfs/ [mount.go:431]
2023/04/25 15:39:52.367117 juicefs[220965] <INFO>: Disk cache (/home/ubuntu/.juicefs/cache/67a050b2-9a40-4852-882c-24c092c03b4a/): capacity (102400 MB), free ratio (10%), max pending pages (15) [disk_cache.go:94]
2023/04/25 15:39:52.378120 juicefs[220965] <INFO>: Create session 1 OK with version: 1.0.4+2023-04-06.f1c475d [base.go:289]
2023/04/25 15:39:52.378749 juicefs[220965] <INFO>: Prometheus metrics listening on 127.0.0.1:9567 [mount.go:161]
2023/04/25 15:39:52.378819 juicefs[220965] <INFO>: Mounting volume ydzsfs at ./jfs ... [mount_unix.go:181]
2023/04/25 15:39:52.378851 juicefs[220965] <WARNING>: setpriority: permission denied [fuse.go:427]
2023/04/25 15:39:52.868233 juicefs[220965] <INFO>: OK, ydzsfs is ready at ./jfs [mount_unix.go:45]

默认情况下,客户端会在前台挂载文件系统,程序会一直运行在当前终端进程中,使用 Ctrl + C 组合键或关闭终端窗口,文件系统会被卸载。

为了让文件系统可以在后台保持挂载,你可以在挂载时指定 -d--background 选项,即让客户端在守护进程中挂载文件系统:

➜ juicefs mount sqlite3://ydzsfs.db ~/jfs -d
2023/04/25 15:41:15.438132 juicefs[222009] <INFO>: Meta address: sqlite3://ydzsfs.db [interface.go:401]
2023/04/25 15:41:15.439334 juicefs[222009] <INFO>: Data use file:///home/ubuntu/.juicefs/local/ydzsfs/ [mount.go:431]
2023/04/25 15:41:15.439513 juicefs[222009] <INFO>: Disk cache (/home/ubuntu/.juicefs/cache/67a050b2-9a40-4852-882c-24c092c03b4a/): capacity (102400 MB), free ratio (10%), max pending pages (15) [disk_cache.go:94]
2023/04/25 15:41:15.941069 juicefs[222009] <INFO>: OK, ydzsfs is ready at /home/ubuntu/jfs [mount_unix.go:45]

接下来,任何存入挂载点 ~/jfs 的文件,都会按照 JuiceFS 的文件存储格式被拆分成特定的「数据块」并存入 $HOME/.juicefs/local/ydzsfs 目录中,相对应的「元数据」会全部存储在 ydzsfs.db 数据库中。

最后执行以下命令可以将挂载点 ~/jfs 卸载:

➜ juicefs umount ~/jfs

当然,在你能够确保数据安全的前提下,也可以在卸载命令中添加 --force-f 参数,强制卸载文件系统。

使用对象存储

通过前面的基本介绍我们可以对 JuiceFS 的工作方式有一个基本的认识,接下来我们仍然使用 SQLite 存储元数据,但是把本地存储换成「对象存储」,做一个更有实用价值的方案。

几乎所有主流的云计算平台都有提供对象存储服务,如亚马逊 S3、阿里云 OSS 等,JuiceFS 支持几乎所有的对象存储服务。一般来说,创建对象存储通常只需要 2 个环节:

  • 创建 Bucket 存储桶,拿到 Endpoint 地址;
  • 创建 Access Key ID 和 Access Key Secret,即对象存储 API 的访问密钥。

以腾讯云 COS 为例,创建好的资源大概像下面这样:

  • Bucket Endpoint:https://myjfs-1304979731.cos.ap-shanghai.myqcloud.com
  • Access Key ID:ABCDEFGHIJKLMNopqXYZ
  • Access Key Secret:ZYXwvutsrqpoNMLkJiHgfeDCBA

我们这里以腾讯云 COS 服务为例来进行演示,首先创建一个 Bucket 存储桶,命名为 myjfs,然后创建一个子账号,命名为 juicefs,并为其创建一个 API 密钥,如下图所示:

# 使用你自己所使用的对象存储信息替换下方相关参数
➜ juicefs format --storage cos \
    --bucket https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com \
    --access-key xxxx \
    --secret-key xxx \
    sqlite3://myjfs.db myjfs
2023/04/25 15:56:18.198284 juicefs[233378] <INFO>: Meta address: sqlite3://myjfs.db [interface.go:401]
2023/04/25 15:56:18.198941 juicefs[233378] <INFO>: Data use cos://myjfs-1304979731/myjfs/ [format.go:434]
2023/04/25 15:56:18.740526 juicefs[233378] <INFO>: Volume is formatted as {
  "Name": "myjfs",
  "UUID": "720c4b39-547e-43d8-8b22-02229f443194",
  "Storage": "cos",
  "Bucket": "https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com",
  "AccessKey": "xxxx",
  "SecretKey": "removed",
  "BlockSize": 4096,
  "Compression": "none",
  "KeyEncrypted": true,
  "TrashDays": 1,
  "MetaVersion": 1
} [format.go:471]

在上述命令中,我们指定了对象存储的相关配置信息:

  • --storage:设置存储类型,比如 cos、oss、s3 等;
  • --bucket:设置对象存储的 Endpoint 地址;
  • --access-key:设置对象存储 API 访问密钥 Access Key ID;
  • --secret-key:设置对象存储 API 访问密钥 Access Key Secret。

创建完成即可进行挂载:

➜ juicefs mount sqlite3://myjfs.db ~/jfs -d
2023/04/25 16:01:40.718645 juicefs[237796] <INFO>: Meta address: sqlite3://myjfs.db [interface.go:401]
2023/04/25 16:01:40.719901 juicefs[237796] <INFO>: Data use cos://myjfs-1304979731/myjfs/ [mount.go:431]
2023/04/25 16:01:40.720136 juicefs[237796] <INFO>: Disk cache (/home/ubuntu/.juicefs/cache/720c4b39-547e-43d8-8b22-02229f443194/): capacity (102400 MB), free ratio (10%), max pending pages (15) [disk_cache.go:94]
2023/04/25 16:01:41.221218 juicefs[237796] <INFO>: OK, myjfs is ready at /home/ubuntu/jfs [mount_unix.go:45]

挂载命令与使用本地存储时完全一样,这是因为创建文件系统时,对象存储相关的信息已经写入了 myjfs.db 数据库,因此客户端不需要额外提供对象存储认证信息,也没有本地配置文件。

相比使用本地磁盘,SQLite 和对象存储的组合实用价值更高。从应用的角度看,这种形式等同于将容量几乎无限的对象存储接入到了本地计算机,让你可以像使用本地磁盘那样使用云存储。

进一步的,该文件系统的所有数据都存储在云端的对象存储,因此可以把 myjfs.db 数据库复制到其他安装了 JuiceFS 客户端的计算机上进行挂载和读写。也就是说,任何一台计算机只要能够读取到存储了元数据的数据库,那么它就能够挂载读写该文件系统。

比如现在我们在 ~/jfs 目录下面任意创建一些文件:

➜ echo "Hello JuiceFS" > hello.txt

正常创建完成后该文件会按照 JuiceFS 的文件存储格式被拆分成特定的「数据块」并上传到对象存储中去,相对应的「元数据」会全部存储在 myjfs.db 数据库中。

img

很显然,SQLite 这种单文件数据库很难实现被多台计算机同时访问。如果把 SQLite 改为 Redis、PostgreSQL、MySQL 等能够通过网络被多台计算机同时读写访问的数据库,那么就可以实现 JuiceFS 文件系统的分布式挂载读写。

分布式模式

前面我们通过采用「对象存储」和「SQLite」数据库的组合,实现了一个可以在任意主机上挂载的文件系统。得益于对象存储可以被网络上任何有权限的计算机访问的特点,我们只需要把 SQLite 数据库文件复制到任何想要访问该存储的计算机,就可以实现在不同计算机上访问同一个 JuiceFS 文件系统。

很显然,想要依靠在计算机之间复制 SQLite 数据库的方式进行文件系统共享,虽然可行,但文件的实时性是得不到保证的。受限于 SQLite 这种单文件数据库无法被多个计算机同时读写访问的情况,为了能够让一个文件系统可以在分布式环境中被多个计算机同时挂载读写,我们需要采用支持通过网络访问的数据库,比如 Redis、PostgreSQL、MySQL 等。

接下来我们将 SQLite 数据库替换成基于网络的数据库,从而实现 JuiceFS 文件系统的分布式挂载读写。JuiceFS 目前支持的基于网络的数据库有:

  • 键值数据库:Redis、TiKV
  • 关系型数据库:PostgreSQL、MySQL、MariaDB

不同的数据库性能和稳定性表现也各不相同,比如 Redis 是内存型键值数据库,性能极为出色,但可靠性相对较弱。PostgreSQL 是关系型数据库,相比之下性能没有内存型强悍,但它的可靠性要更强。

我们这里以 Redis 为例来演示分布式模式的使用,我们就直接在 K8s 集群中部署一个简单的 Redis 服务来进行说明:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        image: redis/redis-stack-server:6.2.6-v6
        imagePullPolicy: IfNotPresent
        name: redis
        ports:
          - containerPort: 6379
            protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  ports:
    - name: redis-port
      port: 6379
      targetPort: 6379
  selector:
    app: redis
  type: NodePort

直接应用该资源清单即可:

➜ kubectl apply -f redis.yaml
➜ kubectl get svc redis -n kube-gpt
NAME      TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE
redis     NodePort   10.103.134.144   <none>        6379:32199/TCP    19d

然后接下来我们就可以利用前面的对象存储和这里的 Redis 来创建一个分布式的 JuiceFS 文件系统了,使用如下所示命令:

➜ juicefs format --storage cos \
    --bucket https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com \
    --access-key xxxx \
    --secret-key xxxx \
    redis://10.103.134.144:6379/1 myjfs
2023/04/25 16:21:41.847487 juicefs[252491] <INFO>: Meta address: redis://10.103.134.144:6379/1 [interface.go:401]
2023/04/25 16:21:41.849176 juicefs[252491] <WARNING>: AOF is not enabled, you may lose data if Redis is not shutdown properly. [info.go:83]
2023/04/25 16:21:41.849459 juicefs[252491] <INFO>: Ping redis: 217.108µs [redis.go:2904]
2023/04/25 16:21:41.850047 juicefs[252491] <INFO>: Data use cos://myjfs-1304979731/myjfs/ [format.go:434]
2023/04/25 16:21:42.263986 juicefs[252491] <INFO>: Volume is formatted as {
  "Name": "myjfs",
  "UUID": "6fb832cc-06a1-4b18-b9fc-087dbf67a105",
  "Storage": "cos",
  "Bucket": "https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com",
  "AccessKey": "xxxxxxxx",
  "SecretKey": "removed",
  "BlockSize": 4096,
  "Compression": "none",
  "KeyEncrypted": true,
  "TrashDays": 1,
  "MetaVersion": 1
} [format.go:471]

文件系统创建完毕以后,包含对象存储密钥等信息会完整的记录到数据库中,JuiceFS 客户端只要拥有数据库地址、用户名和密码信息,就可以挂载读写该文件系统,所以 JuiceFS 客户端不需要本地配置文件。

由于这个文件系统的「数据」和「元数据」都存储在基于网络的服务中,因此在任何安装了 JuiceFS 客户端的计算机上都可以同时挂载该文件系统进行共享读写,例如:

➜ juicefs mount redis://10.103.134.144:6379/1 ~/jfs -d
2023/04/25 16:25:40.254487 juicefs[255369] <INFO>: Meta address: redis://10.103.134.144:6379/1 [interface.go:401]
2023/04/25 16:25:40.255762 juicefs[255369] <WARNING>: AOF is not enabled, you may lose data if Redis is not shutdown properly. [info.go:83]
2023/04/25 16:25:40.255971 juicefs[255369] <INFO>: Ping redis: 164.248µs [redis.go:2904]
2023/04/25 16:25:40.256553 juicefs[255369] <INFO>: Data use cos://myjfs-1304979731/myjfs/ [mount.go:431]
2023/04/25 16:25:40.256743 juicefs[255369] <INFO>: Disk cache (/home/ubuntu/.juicefs/cache/6fb832cc-06a1-4b18-b9fc-087dbf67a105/): capacity (102400 MB), free ratio (10%), max pending pages (15) [disk_cache.go:94]
2023/04/25 16:25:40.757806 juicefs[255369] <INFO>: OK, myjfs is ready at /home/ubuntu/jfs [mount_unix.go:45]

数据强一致性保证

对于多客户端同时挂载读写同一个文件系统的情况,JuiceFS 提供「关闭再打开(close-to-open)」一致性保证,即当两个及以上客户端同时读写相同的文件时,客户端 A 的修改在客户端 B 不一定能立即看到。但是,一旦这个文件在客户端 A 写入完成并关闭,之后在任何一个客户端重新打开该文件都可以保证能访问到最新写入的数据,不论是否在同一个节点。

调大缓存提升性能

由于「对象存储」是基于网络的存储服务,不可避免会产生访问延时。为了解决这个问题,JuiceFS 提供并默认启用了缓存机制,即划拨一部分本地存储作为数据与对象存储之间的一个缓冲层,读取文件时会异步地将数据缓存到本地存储。

缓存机制让 JuiceFS 可以高效处理海量数据的读写任务,默认情况下,JuiceFS 会在 $HOME/.juicefs/cache/var/jfsCache 目录设置 100GiB 的缓存。在速度更快的 SSD 上设置更大的缓存空间可以有效提升 JuiceFS 的读写性能。

你可以使用 --cache-dir 调整缓存目录的位置,使用 --cache-size 调整缓存空间的大小,例如:

juicefs mount
    --background \
    --cache-dir /mycache \
    --cache-size 512000 \
    redis://tom:mypassword@xxxx:6379/1 \
    ~/jfs

注意:JuiceFS 进程需要具有读写 --cache-dir 目录的权限。

上述命令将缓存目录设置在了 /mycache 目录,并指定缓存空间为 500GiB

当挂载好文件系统以后可以通过 juicefs bench 命令对文件系统进行基础的性能测试和功能验证,确保 JuiceFS 文件系统能够正常访问且性能符合预期。

➜ juicefs bench ~/jfs
Cleaning kernel cache, may ask for root privilege...
  Write big blocks count: 1024 / 1024 [==============================================================]  done
   Read big blocks count: 1024 / 1024 [==============================================================]  done
Write small blocks count: 100 / 100 [==============================================================]  done
 Read small blocks count: 100 / 100 [==============================================================]  done
  Stat small files count: 100 / 100 [==============================================================]  done
Benchmark finished!
BlockSize: 1 MiB, BigFileSize: 1024 MiB, SmallFileSize: 128 KiB, SmallFileCount: 100, NumThreads: 1
Time used: 16.4 s, CPU: 50.4%, Memory: 432.8 MiB
+------------------+------------------+---------------+
|       ITEM       |       VALUE      |      COST     |
+------------------+------------------+---------------+
|   Write big file |     266.43 MiB/s |   3.84 s/file |
|    Read big file |     220.25 MiB/s |   4.65 s/file |
| Write small file |     14.6 files/s | 68.50 ms/file |
|  Read small file |   1172.6 files/s |  0.85 ms/file |
|        Stat file |   4252.0 files/s |  0.24 ms/file |
|   FUSE operation | 17835 operations |    1.00 ms/op |
|      Update meta |   326 operations |    2.98 ms/op |
|       Put object |   356 operations |  214.20 ms/op |
|       Get object |   256 operations |  116.36 ms/op |
|    Delete object |     0 operations |    0.00 ms/op |
| Write into cache |   356 operations |    2.94 ms/op |
|  Read from cache |   100 operations |    0.07 ms/op |
+------------------+------------------+---------------+

运行 juicefs bench 命令以后会根据指定的并发度(默认为 1)往 JuiceFS 文件系统中写入及读取 N 个大文件(默认为 1)及 N 个小文件(默认为 100),并统计读写的吞吐和单次操作的延迟,以及访问元数据引擎的延迟。

测试后可以去对象存储中查看多了很多数据了。

img

生产环境部署

为了保证 JuiceFS 文件系统能符合生产环境的要求,这里我们给出了如下一些生产环境部署的建议。

  1. 监控指标收集与可视化

务必收集 JuiceFS 客户端的监控指标并通过 Grafana 可视化。

  1. 元数据自动备份

元数据自动备份是自 JuiceFS v1.0.0 版本开始加入的特性

元数据对 JuiceFS 文件系统非常关键,一旦丢失或损坏将可能影响大批文件甚至整个文件系统。因此必须对元数据进行定期备份。

元数据自动备份特性默认开启,备份间隔为 1 小时,备份的元数据会经过压缩后存储至对应的对象存储中(与文件系统的数据隔离)。备份由 JuiceFS 客户端执行,备份期间会导致其 CPU 和内存使用量上升,默认情况下可认为会在所有客户端中随机选择一个执行备份操作。

特别注意默认情况下当文件系统的文件数达到一百万时,元数据自动备份功能将会关闭,需要配置一个更大的备份间隔(--backup-meta 选项)才会再次开启。备份间隔每个客户端独立配置,设置 --backup-meta 0 则表示关闭元数据自动备份特性。

注意:备份元数据所需的时间取决于具体的元数据引擎,不同元数据引擎会有不同的性能表现。

  1. 回收站

回收站是自 JuiceFS v1.0.0 版本开始加入的特性

回收站默认开启,文件被删除后的保留时间默认配置为 1 天,可以有效防止数据被误删除时造成的数据丢失风险。

不过回收站开启以后也可能带来一些副作用,如果应用需要经常删除文件或者频繁覆盖写文件,会导致对象存储使用量远大于文件系统用量。这本质上是因为 JuiceFS 客户端会将对象存储上被删除的文件或者覆盖写时产生的需要垃圾回收的数据块持续保留一段时间。因此,在部署 JuiceFS 至生产环境时就应该考虑好合适的回收站配置,回收站保留时间可以通过以下方式配置(如果将 --trash-days 设置为 0 则表示关闭回收站特性):

  • 新建文件系统:通过 juicefs format--trash-days <value> 选项设置
  • 已有文件系统:通过 juicefs config--trash-days <value> 选项修改

  • 客户端后台任务

同一个 JuiceFS 文件系统的所有客户端在运行过程中共享一个后台任务集,每个任务定时执行,且具体执行的客户端随机选择。具体的后台任务包括:

  • 清理待删除的文件和对象
  • 清理回收站中的过期文件和碎片
  • 清理长时间未响应的客户端会话
  • 自动备份元数据

由于这些任务执行时会占用一定资源,因此可以为业务较繁重的客户端配置 --no-bgjob 选项来禁止其参与后台任务。

注意:请保证至少有一个 JuiceFS 客户端可以执行后台任务

  1. 客户端日志滚动

当后台运行 JuiceFS 挂载点时,客户端默认会将日志输出到本地文件中。取决于挂载文件系统时的运行用户,本地日志文件的路径稍有区别。root 用户对应的日志文件路径是 /var/log/juicefs.log,非 root 用户的日志文件路径是 $HOME/.juicefs/juicefs.log

本地日志文件默认不会滚动,生产环境中为了确保日志文件不占用过多磁盘空间需要手动配置。以下是一个日志滚动的示例配置:

# /etc/logrotate.d/juicefs
/var/log/juicefs.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

通过 logrotate -d /etc/logrotate.d/juicefs 命令可以验证配置文件的正确性。


前面我们已经学习了 JuiceFS 的基本使用,接下来我们来了解下如何在 Kubernetes 集群中使用 JuiceFS。JuiceFS 非常适合用作 Kubernetes 集群的存储层,。

以 hostPath 方式挂载 JuiceFS

如果你仅仅需要在 Kubernetes 容器中简单使用 JuiceFS,没有其他任何复杂要求(比如隔离性、权限控制),那么完全可以以 hostPath 卷的方式使用 JuiceFS,搭建起来也十分简单。

首先安装前文的方式在 Kubernetes 节点上统一安装、挂载 JuiceFS。

然后在 Pod 的资源清单文件定义中使用 hostPath 卷,直接将宿主机上的 JuiceFS 子目录挂载到容器中即可:

apiVersion: v1
kind: Pod
metadata:
  name: juicefs-app
spec:
  containers:
    - ...
      volumeMounts:
        - name: jfs-data
          mountPath: /opt/app-data
  volumes:
    - name: jfs-data
      hostPath:
        path: /jfs/myapp/  # 假设挂载点为 /jfs
        type: Directory

相比以 CSI 驱动的方式来使用 JuiceFS,hostPath 更为简单直接,出问题也更易排查,但也要注意:

  • 为管理方便,一般所有容器都在使用同一个宿主机挂载点,缺乏隔离可能导致数据安全问题,未来也无法在不同应用中单独调整 JuiceFS 挂载参数。请谨慎评估。
  • 所有节点都需要提前挂载 JuiceFS,因此集群加入新节点,需要在初始化流程里进行安装和挂载,否则新节点没有 JuiceFS 挂载点,容器将无法创建。
  • 宿主机上的 JuiceFS 挂载进程所占用的系统资源(如 CPU、内存等)不受 Kubernetes 控制,有可能占用较多宿主机资源。可以考虑用 system-reserved 来适当调整 Kubernetes 的系统资源预留值,为 JuiceFS 挂载进程预留更多资源。
  • 如果宿主机上的 JuiceFS 挂载进程意外退出,将会导致应用 Pod 无法正常访问挂载点,此时需要重新挂载 JuiceFS 文件系统并重建应用 Pod。作为对比,JuiceFS CSI 驱动提供 「挂载点自动恢复」功能来解决这个问题。

如果你使用 Docker 作为 Kubernetes 容器运行环境,最好令 JuiceFS 先于 Docker 启动,否则在节点重启的时候,偶尔可能出现容器启动时,JuiceFS 尚未挂载好的情况,此时便会因该依赖问题启动失败。以 systemd 为例,可以用下方 unit file 来配置启动顺序:

# /etc/systemd/system/docker.service.d/override.conf
[Unit]
# 请使用下方命令确定 JuiceFS 挂载服务的名称(例如 jfs.mount):
# systemctl list-units | grep "\.mount"
After=network-online.target firewalld.service containerd.service jfs.mount

以 CSI 驱动方式挂载 JuiceFS

JuiceFS CSI 驱动遵循 CSI 规范,实现了容器编排系统与 JuiceFS 文件系统之间的接口。在 Kubernetes 下,JuiceFS 可以用 PersistentVolume 的形式提供给 Pod 使用。

CSI 驱动运行模式

整体上 JuiceFS CSI 驱动包含以下组件:JuiceFS CSI Controller(StatefulSet) 以及 JuiceFS CSI Node Service(DaemonSet)

容器挂载模式

CSI 默认采用容器挂载(Mount Pod)模式,也就是让 JuiceFS 客户端运行在独立的 Pod 中,其架构如下:

img

采用独立 Mount Pod 来运行 JuiceFS 客户端,并由 CSI Node Service 来管理 Mount Pod 的生命周期。这样的架构提供如下好处:

  • 多个 Pod 共用 PV 时,不会新建 Mount Pod,而是对已有的 Mount Pod 做引用计数,计数归零时删除 Mount Pod。
  • CSI 驱动组件与客户端解耦,方便 CSI 驱动自身的升级。

在同一个节点上,一个 PVC 会对应一个 Mount Pod,而使用了相同 PV 的容器,则可以共享一个 Mount Pod。PVC、PV、Mount Pod 之间的关系如下图所示:

img

CSI 驱动默认以容器挂载模式运行,但特定场景下该模式不一定适用,因此 CSI 驱动还提供 Sidecar 模式以及进程挂载模式。

Sidecar 模式

Mount Pod 需要由 CSI Node 创建,考虑到 CSI Node 是一个 DaemonSet 组件,如果你的 Kubernetes 集群不支持部署 DaemonSet(比如一些云服务商提供的 Serverless Kubernetes 服务),那么 CSI Node 将无法部署,也就无法正常使用 CSI 驱动。对于这种情况,可以选择使用 CSI 驱动的 Sidecar 模式,让 JuiceFS 客户端运行在 Sidecar 容器中。

以 Sidecar 模式安装 CSI 驱动,所部署的组件只有 CSI Controller,不再需要 CSI Node。对于需要使用 CSI 驱动的 Kubernetes 命名空间,CSI Controller 会监听容器变动,检查是否使用了 JuiceFS PVC,并根据情况为其注入 Sidecar 容器。

img

创建和使用的流程大致如下:

  • CSI Controller 启动时,向 API Server 注册 Webhook;
  • 应用 Pod 指定使用 JuiceFS PVC;
  • API Server 在创建应用 Pod 前调用 CSI Controller 的 Webhook 接口;
  • CSI Controller 向应用 Pod 中注入 Sidecar 容器,容器中运行着 JuiceFS 客户端;
  • API Server 创建应用 Pod,Sidecar 容器启动后运行 JuiceFS 客户端执行挂载,应用容器启动后可直接访问文件系统。

使用 Sidecar 模式需要注意:

  • 运行环境需要支持 FUSE,也就是支持以特权容器(Privileged)运行;
  • 不同于 Mount Pod 的容器挂载方式,Sidecar 容器注入进了应用 Pod,因此将无法进行任何复用,大规模场景下,请尤其注意资源规划和分配;
  • Sidecar 容器和应用容器的挂载点共享是通过 hostPath 实现的,是一个有状态服务,如果 Sidecar 容器发生意外重启,应用容器中的挂载点不会自行恢复,需要整个 Pod 重新创建(相较下,Mount Pod 模式则支持挂载点自动恢复);
  • 不要直接从 Mount Pod 模式升级成 Sidecar 模式。已有的 Mount Pod 在 Sidecar 模式下将无法回收。并且一般而言,考虑到 Sidecar 不支持复用,我们不推荐从 Mount Pod 模式迁移为 Sidecar 模式;
  • 对于启用了 Sidecar 注入的命名空间,CSI Controller 会监听该命名空间下创建的所有容器,检查 PVC 的使用并查询获取相关信息。如果希望最大程度地减小开销,可以在该命名空间下,对不使用 JuiceFS PV 的应用 Pod 打上 disable.sidecar.juicefs.com/inject: true 标签,让 CSI Controller 忽略这些不相关的容器。

进程挂载模式

相较于采用独立 Mount Pod 的容器挂载方式或 Sidecar 模式,CSI 驱动还提供无需独立 Pod 的进程挂载模式,在这种模式下,CSI Node Service 容器中将会负责运行一个或多个 JuiceFS 客户端,该节点上所有需要挂载的 JuiceFS PV,均在 CSI Node Service 容器中以进程模式执行挂载。

img

由于所有 JuiceFS 客户端均在 CSI Node Service 容器中运行,CSI Node Service 将需要更大的资源声明,推荐将其资源请求调大到至少 1 CPU 和 1GiB 内存,资源约束调大到至少 2 CPU 和 5GiB 内存,或者根据实际场景资源占用进行调整。

在 Kubernetes 中,容器挂载模式无疑是更加推荐的 CSI 驱动用法,但脱离 Kubernetes 的某些场景,则可能需要选用进程挂载模式。在 v0.10 之前,JuiceFS CSI 驱动仅支持进程挂载模式。而 v0.10 及之后版本则默认为容器挂载模式。

CSI 驱动安装

这里我们选择使用 Helm 来进行安装,首先配置 JuiceFS CSI 驱动的 Helm chart

➜ helm repo add juicefs https://juicedata.github.io/charts/
➜ helm repo update
➜ helm fetch --untar juicefs/juicefs-csi-driver
➜ cd juicefs-csi-driver
# values.yaml 中包含安装 CSI 驱动的所有配置,安装前可以进行梳理,并按需修改
➜ cat values.yaml

首先我们需要检查下 kubelet 根目录:

➜ ps -ef | grep kubelet | grep root-dir

如果结果不为空或者 /var/lib/kubelet,则代表该集群的 kubelet 的根目录(--root-dir)做了修改,需要在 values.yaml 中将 kubeletDir 根据实际情况进行设置:

kubeletDir: <kubelet-dir>

这里我们还是以腾讯云 COS 和元数据服务存入 Redis 为例进行说明,首先我们需要在腾讯云 COS 中创建一个 Bucket,我这里的地址为 https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com,然后获取到 COS 的 SecretID 和 SecretKey,对应的 Redis 服务如下所示:

➜ kubectl get svc redis -n kube-gpt
NAME    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
redis   NodePort   10.103.134.144   <none>        6379:32199/TCP   21d

然后我们就可以将这些信息填入到 values.yaml 中:

controller:
  provisioner: true
  replicas: 1
storageClasses:
  - name: "juicefs-sc"
    enabled: true

    backend:
      name: "myjfs"
      metaurl: "redis://redis.kube-gpt.svc.cluster.local:6379/1"
      storage: "cos"
      bucket: "https://myjfs-1304979731.cos.ap-nanjing.myqcloud.com"
      # -- Access key for object storage
      accessKey: "xxxx"
      # -- Secret key for object storage
      secretKey: "xxxx"

    pathPattern: "${.PVC.namespace}-${.PVC.name}"

然后我们就可以使用 Helm 来安装 CSI 驱动:

➜ helm upgrade --install juicefs-csi-driver juicefs/juicefs-csi-driver -n kube-system -f ./values.yaml
Release "juicefs-csi-driver" does not exist. Installing it now.
NAME: juicefs-csi-driver
LAST DEPLOYED: Thu Apr 27 15:06:36 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Guide on how to configure a StorageClass or PV and start using the driver are here:
https://juicefs.com/docs/csi/guide/pv

For quickly test, you can use this file to test:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: juicefs-pvc
  namespace: "kube-system"
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 10Pi
  storageClassName: juicefs-sc
---
apiVersion: v1
kind: Pod
metadata:
  name: juicefs-app
  namespace: "kube-system"
spec:
  containers:
  - args:
    - -c
    - while true; do echo $(date -u) >> /data/out.txt; sleep 5; done
    command:
    - /bin/sh
    image: busybox
    name: app
    volumeMounts:
    - mountPath: /data
      name: juicefs-pv
  volumes:
  - name: juicefs-pv
    persistentVolumeClaim:
      claimName: juicefs-pvc

部署完成后查看对应的 CSI 驱动 Pod 状态:

➜ kubectl get pods -n kube-system -l app=juicefs-csi-node
NAME                     READY   STATUS    RESTARTS   AGE
juicefs-csi-node-5zwph   3/3     Running   0          11m
juicefs-csi-node-cdlhc   3/3     Running   0          11m
juicefs-csi-node-hbzd4   3/3     Running   0          11m
➜ kubectl get secret juicefs-sc-secret -n kube-system
NAME                TYPE     DATA   AGE
juicefs-sc-secret   Opaque   6      108s
➜ kubectl get sc juicefs-sc
NAME         PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
juicefs-sc   csi.juicefs.com   Delete          Immediate           false                  2m6s

可以看到安装成功后自动创建一个 StorageClass,后面我们就可以直接使用该 StorageClass 来自动创建 PV:

➜ kubectl get sc juicefs-sc -oyaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: juicefs-sc
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
  juicefs/mount-cpu-limit: 5000m
  juicefs/mount-cpu-request: 1000m
  juicefs/mount-memory-limit: 5Gi
  juicefs/mount-memory-request: 1Gi
  pathPattern: ${.PVC.namespace}-${.PVC.name}
provisioner: csi.juicefs.com
reclaimPolicy: Delete
volumeBindingMode: Immediate

部署完成后我们可以安装上面的提示来创建一个 PVC 和 Pod 进行测试下:

# test-juicefs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: juicefs-pvc
  namespace: kube-system
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: juicefs-sc
---
apiVersion: v1
kind: Pod
metadata:
  name: juicefs-app
  namespace: kube-system
spec:
  containers:
    - args:
        - -c
        - while true; do echo $(date -u) >> /data/out.txt; sleep 5; done
      command:
        - /bin/sh
      image: busybox
      name: app
      volumeMounts:
        - mountPath: /data
          name: juicefs-pv
  volumes:
    - name: juicefs-pv
      persistentVolumeClaim:
        claimName: juicefs-pvc

直接应用上面的资源文件即可:

➜ kubectl apply -f test-juicefs.yaml
persistentvolumeclaim/juicefs-pvc created
pod/juicefs-app created

默认情况下 JuiceFS CSI 采用容器挂载(Mount Pod)模式,会让 JuiceFS 客户端运行在独立的 Pod 中,所以当我们创建上面的 Pod 后,会先创建一个独立的 Mount Pod:

➜ kubectl get pods -n kube-system -l app.kubernetes.io/name=juicefs-mount
NAME                                                            READY   STATUS    RESTARTS   AGE
juicefs-node3-pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao   1/1     Running   0          3m18s
# 查看日志
➜ kubectl logs -f juicefs-node3-pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao -n kube-system
Defaulted container "jfs-mount" out of: jfs-mount, jfs-format (init)
2023/04/27 07:39:48.443089 juicefs[7] <INFO>: Meta address: redis://redis.kube-gpt.svc.cluster.local:6379/1 [interface.go:401]
2023/04/27 07:39:48.446024 juicefs[7] <WARNING>: AOF is not enabled, you may lose data if Redis is not shutdown properly. [info.go:83]
2023/04/27 07:39:48.446198 juicefs[7] <INFO>: Ping redis: 129.071µs [redis.go:2904]
2023/04/27 07:39:48.446722 juicefs[7] <INFO>: Data use cos://myjfs-1304979731/myjfs/ [mount.go:431]
2023/04/27 07:39:48.447048 juicefs[7] <INFO>: Disk cache (/var/jfsCache/6fb832cc-06a1-4b18-b9fc-087dbf67a105/): capacity (102400 MB), free ratio (10%), max pending pages (15) [disk_cache.go:94]
2023/04/27 07:39:48.447738 juicefs[7] <INFO>: Create session 4 OK with version: 1.0.4+2023-04-06.f1c475d9 [base.go:289]
2023/04/27 07:39:48.448652 juicefs[7] <INFO>: Prometheus metrics listening on [::]:9567 [mount.go:161]
2023/04/27 07:39:48.448757 juicefs[7] <INFO>: Mounting volume myjfs at /jfs/pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao ... [mount_unix.go:181]
2023/04/27 07:39:48.948324 juicefs[7] <INFO>: OK, myjfs is ready at /jfs/pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao [mount_unix.go:45]

可以看到 Mount Pod 已经成功挂载了 JuiceFS 文件系统,接下来我们再看下我们创建的 Pod:

➜ kubectl get pods juicefs-app -n kube-system
NAME          READY   STATUS    RESTARTS   AGE
juicefs-app   1/1     Running   0          7m9s
➜ kubectl get pvc juicefs-pvc -n kube-system
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
juicefs-pvc   Bound    pvc-24163145-c1ee-4394-a6a6-1bdc89755f12   1Gi        RWX            juicefs-sc     7m35s
➜ kubectl get pv pvc-24163145-c1ee-4394-a6a6-1bdc89755f12
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
pvc-24163145-c1ee-4394-a6a6-1bdc89755f12   1Gi        RWX            Delete           Bound    kube-system/juicefs-pvc   juicefs-sc              8m2s

可以看到我们创建的 Pod 和 PVC 都已经成功绑定到了 PV 上,接下来我们进入 Pod 中查看下:

➜ kubectl exec -it juicefs-app -n kube-system -- sh
/ # ls /data
out.txt
/ # cat /data/out.txt
Thu Apr 27 07:46:19 UTC 2023
Thu Apr 27 07:46:24 UTC 2023
Thu Apr 27 07:46:29 UTC 2023
Thu Apr 27 07:46:34 UTC 2023
Thu Apr 27 07:46:39 UTC 2023
Thu Apr 27 07:46:44 UTC 2023
Thu Apr 27 07:46:50 UTC 2023
# ......

这个时候 Pod 中的 /data 目录已经成功挂载了 JuiceFS 文件系统,我们可以前往 Mount Pod 中查看下:

➜ kubectl exec -it juicefs-node3-pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao -n kube-system -- sh
/ # ls /jfs/pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao
kube-system-juicefs-pvc
/ # ls /jfs/pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao/kube-system-juicefs-pvc
out.txt
/ # cat /jfs/pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao/kube-system-juicefs-pvc/out.txt
Thu Apr 27 07:48:56 UTC 2023
Thu Apr 27 07:49:01 UTC 2023
Thu Apr 27 07:49:06 UTC 2023
# ......

当然现在在对象存储中去查看也是可以看到文件的:

img

到这里就表明我们已经成功的将 JuiceFS 文件系统挂载到了 Kubernetes 中。

静态 PV

上面我们安装 JuiceFS CSI Driver 后自动创建了一个 StorageClass,所以我们可以直接使用这个 StorageClass 来创建 PVC,但是如果我们想要使用静态 PV 的话,我们就需要手动创建 PV 和 PVC,下面我们就来看下如何创建静态 PV。

首先手动创建一个 PV,创建所需的资源定义示范如下:

# test-static-juicefs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: juicefs-static-pv
  labels:
    juicefs-name: ten-gb-fs
spec:
  # 目前 JuiceFS CSI 驱动不支持设置存储容量,填写任意有效值即可
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  csi:
    # 在先前的安装步骤中,已经创建了名为 csi.juicefs.com 的 CSIDriver
    # 可以使用 kubectl get csidriver csi.juicefs.com 查看
    driver: csi.juicefs.com
    # volumeHandle 需要保证集群内唯一,因此一般直接用 PV 名即可
    volumeHandle: juicefs-pv
    fsType: juicefs
    # 在先前的步骤中已经创建好文件系统认证信息(Secret),在这里引用
    # 如果要在静态配置下使用不同的认证信息,甚至使用不同的 JuiceFS 文件系统,则需要创建不同的 Secret
    nodePublishSecretRef:
      name: juicefs-sc-secret
      namespace: kube-system
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: juicefs-static-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  # 静态配置下必须指定 storageClassName 为空字符串
  # 代表该 PV 不采用任何 StorageClass,而是直接使用 selector 所指定的 PV
  storageClassName: ""
  # 由于目前 JuiceFS CSI 驱动不支持设置存储容量,此处 requests.storage 填写任意小于等于 PV capacity 的有效值即可
  resources:
    requests:
      storage: 10Gi
  selector:
    matchLabels:
      juicefs-name: ten-gb-fs

然后再创建一个应用 Pod,并在其中引用上面的 PVC:

apiVersion: v1
kind: Pod
metadata:
  name: juicefs-app
  namespace: default
spec:
  containers:
    - args:
        - -c
        - while true; do echo $(date -u) >> /data/out.txt; sleep 5; done
      command:
        - /bin/sh
      image: busybox
      name: app
      volumeMounts:
        - mountPath: /data
          name: data
      resources:
        requests:
          cpu: 10m
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: juicefs-static-pvc

同样直接应用上面的 YAML 文件即可:

➜ kubectl apply -f test-static-juicefs.yaml
persistentvolume/juicefs-static-pv created
persistentvolumeclaim/juicefs-static-pvc created
pod/juicefs-app created

正常创建后 PVC 和 PV 就会绑定在一起了,Pod 也能正常运行了:

➜ kubectl get pv juicefs-static-pv
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                        STORAGECLASS   REASON   AGE
juicefs-static-pv   10Gi       RWX            Retain           Bound    default/juicefs-static-pvc                           9s
➜ kubectl get pvc juicefs-static-pvc
NAME                 STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
juicefs-static-pvc   Bound    juicefs-static-pv   10Gi       RWX                           15s
➜ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
juicefs-app   1/1     Running   0          58s

这个时候其实还会自动创建一个 Mount Pod,用来管理我们这里的静态 PV 的挂载:

➜ kubectl get pods -n kube-system -l app.kubernetes.io/name=juicefs-mount
NAME                                                            READY   STATUS    RESTARTS   AGE
juicefs-node2-juicefs-pv-szelya                                 1/1     Running   0          104s
juicefs-node3-pvc-24163145-c1ee-4394-a6a6-1bdc89755f12-geuaao   1/1     Running   0          29m
➜ kubectl exec -it juicefs-node2-juicefs-pv-szelya   -n kube-system -- /bin/sh
Defaulted container "jfs-mount" out of: jfs-mount, jfs-format (init)
# ls /jfs/juicefs-pv-szelya
out.txt
# tail -f /jfs/juicefs-pv-szelya/out.txt
Thu Apr 27 08:08:43 UTC 2023
Thu Apr 27 08:08:48 UTC 2023
Thu Apr 27 08:08:53 UTC 2023
# ......

在 JuiceFS 挂载点看到上方容器写入的 out.txt 了。

此外如果想要在不同命名空间中共享同一个文件系统,只需要让不同 PV 使用相同的文件系统认证信息(Secret)即可:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
  namespace: ns1
  labels:
    pv-name: mypv1
spec:
  csi:
    nodePublishSecretRef:
      name: juicefs-sc-secret
      namespace: kube-system
  ...
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv2
  namespace: ns2
  labels:
    pv-name: mypv2
spec:
  csi:
    nodePublishSecretRef:
      name: juicefs-sc-secret
      namespace: kube-system
  ...

这样也就不会重复创建 Mount Pod 了。默认情况下,仅在多个应用 Pod 使用相同 PV 时,Mount Pod 才会被复用。如果你希望进一步降低开销,可以更加激进地复用 Mount Pod,让使用相同 StorageClass 创建出来的所有 PV,都复用同一个 Mount Pod(当然复用只能发生在同一个节点)。不同的应用 Pod,将会绑定挂载点下不同的路径,实现一个挂载点为多个应用容器提供服务。

为相同 StorageClass PV 复用 Mount Pod,需要为 CSI Node Service 添加 STORAGE_CLASS_SHARE_MOUNT 这个环境变量:

➜ kubectl -n kube-system set env -c juicefs-plugin daemonset/juicefs-csi-node STORAGE_CLASS_SHARE_MOUNT=true

但是高度复用意味着更低的隔离程度,如果 Mount Pod 发生意外,挂载点异常,影响面也会更大,因此如果你决定启用该复用策略,请务必同时启用「挂载点自动恢复」(JuiceFS CSI 驱动自 v0.10.7 开始支持挂载点自动恢复:当 Mount Pod 遭遇故障,重启或重新创建 Mount Pod 以后,应用容器也能继续工作。),以及合理增加 「Mount Pod 的资源请求」。