
1. 为什么要将 3FS 对接到 Fluid
3FS 是 DeepSeek 开源的分布式存储系统,因其极优异的性能测试结果,而被津津乐道,star 量快速飙升。
我所在的团队也对 3FS 展开了技术上的跟踪,寻找合适的应用场景,发挥 AI 硬件基础设施的最大价值。
我们线上推理、训练服务使用的存储系统都是通过 Fluid 进行管理的,使用 Fluid 可以很方便地创建出 PVC,在使用存储的节点上自动进行挂载,十分方便。
在对接 Fluid 之前,我们已经在 IB + H100 环境下部署好了 3FS 存储系统,并进行了一些测试,为了方便进行更多的测试、创建存储进行使用,我们需要使用 Fluid 将 3FS 对接到 Kubernetes 中。
2. 编译 3FS 的 builder 镜像
2.1 为什么要单独提供 3FS 的 builder 镜像
避免安装依赖时,影响主机的本地配置。
同时,推荐使用服务器环境进行编译,需要的编译资源很多。低内存的配置会直接导致编译失败,CPU 核数太少会导致编译时间过长。
方便部署,3FS 依赖的动态库文件很多,在 builder 镜像中能够提供完整的依赖环境。
只需要拷贝编译好的二进制文件到 builder 镜像中,就可以直接运行。
2.2 编写 Dockerfile
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
| # Base image
FROM ubuntu:22.04
# Arguments
ARG FOUNDATIONDB_TAG=7.3.59
ARG FOUNDATIONDB_VERSION=${FOUNDATIONDB_TAG}-1
ARG LIBFUSE_TAG=fuse-3.16.1
ARG LIBFUSE_VERSION=3.16.1
# Install system dependencies and build tools
RUN apt update && \
apt install -y \
cmake libuv1-dev liblz4-dev liblzma-dev libdouble-conversion-dev \
libprocps-dev libdwarf-dev libunwind-dev libaio-dev libgflags-dev \
libgoogle-glog-dev libgtest-dev libgmock-dev clang-format-14 clang-14 \
clang-tidy-14 lld-14 libgoogle-perftools-dev google-perftools libssl-dev \
ccache gcc-12 g++-12 libboost-all-dev git meson ninja-build lsb-release wget && \
wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb && \
apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb && \
apt update && \
apt install -y -V libarrow-dev && \
rm -rf /var/lib/apt/lists/* apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# Install FoundationDB client
RUN wget https://github.com/apple/foundationdb/releases/download/${FOUNDATIONDB_TAG}/foundationdb-clients_${FOUNDATIONDB_VERSION}_amd64.deb && \
dpkg -i ./foundationdb-clients_${FOUNDATIONDB_VERSION}_amd64.deb && \
rm -f foundationdb-clients_${FOUNDATIONDB_VERSION}_amd64.deb
# Build and install libfuse
RUN wget https://github.com/libfuse/libfuse/releases/download/${LIBFUSE_TAG}/fuse-${LIBFUSE_VERSION}.tar.gz && \
tar -zxvf fuse-${LIBFUSE_VERSION}.tar.gz && \
cd fuse-${LIBFUSE_VERSION} && \
mkdir build && \
cd build && \
meson setup .. && \
ninja && \
ninja install && \
cd ../.. && \
rm -rf fuse-${LIBFUSE_VERSION} fuse-${LIBFUSE_VERSION}.tar.gz
# Set up environment variables
ENV PATH="/root/.cargo/bin:${PATH}"
WORKDIR /app
|
2.3 编译并推送镜像
1
| docker build -t shaowenchen/3fs-builder:latest . --push
|
如果使用的是 nerdctl 进行编译,还需要配置一下 BuildKit,参考 使用 Nerdctl 构建多架构镜像
。
3. 制作 ThinRuntime 镜像
Fluid 提供了一种快速对接 mount 类型存储的方式就是 ThinRuntime。Fluid 提供的存储配置、管理能力,就可以快速赋能给新的存储系统。
3.1 编写 fluid_config_init.py 脚本
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
| #!/usr/bin/env python
import json
rawStr = ""
try:
with open("/etc/fluid/config/config.json", "r") as f:
rawStr = f.readlines()
except:
pass
if rawStr == "":
try:
with open("/etc/fluid/config.json", "r") as f:
rawStr = f.readlines()
except:
pass
rawStr = rawStr[0]
script = """
#!/bin/sh
set -ex
MNT_FROM=$mountPoint
TOKEN=$(echo $MNT_FROM | awk -F'@' '{print $1}')
RDMA=$(echo $MNT_FROM | awk -F'@' '{print $2}' | awk -F'://' '{print $2}')
echo $TOKEN > /opt/3fs/etc/token.txt
sed -i "s#RDMA://0.0.0.0:8000#${RDMA}#g" /opt/3fs/etc/hf3fs_fuse_main_launcher.toml
MNT_TO=$targetPath
trap "umount ${MNT_TO}" SIGTERM
mkdir -p ${MNT_TO}
sed -i "s#/3fs/stage#${MNT_TO}#g" /opt/3fs/etc/hf3fs_fuse_main_launcher.toml
LD_LIBRARY_PATH=/opt/3fs/bin /opt/3fs/bin/hf3fs_fuse_main --launcher_cfg /opt/3fs/etc/hf3fs_fuse_main_launcher.toml
"""
obj = json.loads(rawStr)
with open("/mount-3fs.sh", "w") as f:
f.write('mountPoint="%s"\n' % obj["mounts"][0]["mountPoint"])
f.write('targetPath="%s"\n' % obj["targetPath"])
f.write(script)
|
这段脚本的作用就是将 Fluid 动态提供的参数,渲染到 3FS 的配置文件中,然后启动 3FS Fuse 服务。
从 Fluid v1.1 版本开始,Fluid 使用 /etc/fluid/config/config.json
作为配置文件,而不是更早版本中使用的 /etc/fluid/config.json
文件。为了兼容不同 Fluid 版本使用的配置文件路径不同,我在脚本中做了一些兼容处理。
3.2 编写 entrypoint.sh 脚本
1
2
3
4
5
6
7
| #!/usr/bin/env bash
set +x
echo "sleep inf" > /mount-3fs.sh
python3 /fluid_config_init.py
chmod u+x /mount-3fs.sh
bash /mount-3fs.sh
|
3.3 编译 hf3fs_fuse_main
1
| docker run -it --rm -v $(pwd):/app shaowenchen/demo-3fsbuilder:latest bash
|
1
2
3
| git clone https://github.com/deepseek-ai/3FS
cd 3FS
git submodule update --init --recursive
|
1
| cmake -S . -B build -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_C_COMPILER=clang-14 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
这里的 -j 100 是为了加快编译速度,具体的值可以根据自己的 CPU 机器配置来调整,3FS 社区设置的是 32。
1
| cmake --build build -j 100
|
1
2
3
4
| ls 3FS/build/bin/
admin_cli hf3fs_fuse_main mgmtd_main monitor_collector_main storage_bench
hf3fs-admin meta_main migration_main simple_example_main storage_main
|
对接 Fluid 时,只需要 hf3fs_fuse_main 二进制文件即可。
3.4 准备 hf3fs_fuse_main_launcher.toml 配置文件
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| allow_other = true
cluster_id = 'stage'
mountpoint = '/3fs/stage'
token_file = '/opt/3fs/etc/token.txt'
[client]
default_compression_level = 0
default_compression_threshold = '128KB'
default_log_long_running_threshold = '0ns'
default_report_metrics = false
default_send_retry_times = 1
default_timeout = '1s'
enable_rdma_control = false
force_use_tcp = false
[client.io_worker]
num_event_loop = 1
rdma_connect_timeout = '5s'
read_write_rdma_in_event_thread = false
read_write_tcp_in_event_thread = false
tcp_connect_timeout = '1s'
wait_to_retry_send = '100ms'
[client.io_worker.connect_concurrency_limiter]
max_concurrency = 4
[client.io_worker.ibsocket]
buf_ack_batch = 8
buf_signal_batch = 8
buf_size = 16384
drain_timeout = '5s'
drop_connections = 0
event_ack_batch = 128
max_rd_atomic = 16
max_rdma_wr = 128
max_rdma_wr_per_post = 32
max_sge = 1
min_rnr_timer = 1
record_bytes_per_peer = false
record_latency_per_peer = false
retry_cnt = 7
rnr_retry = 0
send_buf_cnt = 32
sl = 0
start_psn = 0
timeout = 14
[client.io_worker.transport_pool]
max_connections = 1
[client.processor]
enable_coroutines_pool = true
max_coroutines_num = 256
max_processing_requests_num = 4096
response_compression_level = 1
response_compression_threshold = '128KB'
[client.rdma_control]
max_concurrent_transmission = 64
[client.thread_pool]
bg_thread_pool_stratetry = 'SHARED_QUEUE'
collect_stats = false
enable_work_stealing = false
io_thread_pool_stratetry = 'SHARED_QUEUE'
num_bg_threads = 2
num_connect_threads = 2
num_io_threads = 2
num_proc_threads = 2
proc_thread_pool_stratetry = 'SHARED_QUEUE'
[ib_devices]
allow_no_usable_devices = false
allow_unknown_zone = true
default_network_zone = 'UNKNOWN'
default_pkey_index = 0
default_roce_pkey_index = 0
default_traffic_class = 0
device_filter = []
fork_safe = true
prefer_ibdevice = true
skip_inactive_ports = true
skip_unusable_device = true
subnets = []
[mgmtd_client]
accept_incomplete_routing_info_during_mgmtd_bootstrapping = true
auto_extend_client_session_interval = '10s'
auto_heartbeat_interval = '10s'
auto_refresh_interval = '10s'
enable_auto_extend_client_session = true
enable_auto_heartbeat = false
enable_auto_refresh = true
mgmtd_server_addresses = ["RDMA://0.0.0.0:8000"]
work_queue_size = 100
|
配置中需要注意的有两点:
- RDMA 地址,最终会由 Fluid 动态注入,在镜像中的值应该能被唯一识别,方便进行 sed 替换
- device_filter 为空时,默认会使用全部 RDMA 设备,可能导致 IB 和 RoCE 设备混用,最终挂载失败
3.5 编写 Dockerfile
1
2
3
4
5
6
7
8
9
| FROM shaowenchen/demo-3fsbuilder:latest
RUN apt-get install -y python3
COPY bin /opt/3fs/bin
RUN chmod +x /opt/3fs/bin/* && mkdir -p /var/log/3fs
COPY etc /opt/3fs/etc
COPY ./fluid_config_init.py /
COPY ./entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT []
|
3.6 编写并推送 ThinRuntime 镜像
1
2
3
4
5
6
7
8
9
10
11
| tree -L 3 .
.
├── Dockerfile
├── bin
│ └── hf3fs_fuse_main
├── entrypoint.sh
├── etc
│ ├── hf3fs_fuse_main_launcher.toml
│ └── token.txt
└── fluid_config_init.py
|
token.txt 在镜像中是空的。
1
| docker build -t shaowenchen/demo-fluid-3fs:latest .
|
4. 使用 Fluid 挂载 3FS 存储
4.1 创建 ThinRuntimeProfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: ThinRuntimeProfile
metadata:
name: 3fs
spec:
fileSystemType: 3fs
fuse:
image: shaowenchen/demo-fluid-3fs
imageTag: latest
imagePullPolicy: Always
command:
- "/usr/local/bin/entrypoint.sh"
EOF
|
4.2 创建 Dataset
1
2
3
4
5
6
7
8
9
10
| kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
name: demo-3fs
spec:
mounts:
- mountPoint: my3fsTOKEN@RDMA://x.x.x.x:8000
name: demo-3fs
EOF
|
这里的 mountPoint 是由 token 和 RDMA 地址组成的,token 是 3FS 的认证信息,RDMA 地址是 3FS 的服务地址。
4.3 创建 ThinRuntime
1
2
3
4
5
6
7
8
| kubectl apply -f - <<EOF
apiVersion: data.fluid.io/v1alpha1
kind: ThinRuntime
metadata:
name: demo-3fs
spec:
profileName: 3fs
EOF
|
4.4 创建测试 Pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: demo-3fs
spec:
containers:
- name: demo-3fs
image: shaowenchen/demo-ubuntu
volumeMounts:
- mountPath: /data
name: demo-3fs
volumes:
- name: demo-3fs
persistentVolumeClaim:
claimName: demo-3fs
EOF
|
5. 性能测试
3FS 在 DD 下的测试数据并不好,这里为了方便观测 RDMA 、SSD 的极限性能,使用了 FIO 工具进行测试。目前,我们尝试 3FS 在各种场景下的适用性,后面会专门写一篇文章输出测试数据。
1
| kubectl exec -it demo-3fs -- bash
|
1
2
| apt-get update
apt-get install -y fio
|
主机上
1
2
3
4
| fio -numjobs=128 -fallocate=none -iodepth=2 -ioengine=libaio -direct=1 -rw=read -bs=4M --group_reporting -size=100M -time_based -runtime=30 -name=2depth_128file_4M_direct_read_bw -directory=/3fs/stage/fio-read
Run status group 0 (all jobs):
READ: bw=12.0GiB/s (12.9GB/s), 12.0GiB/s-12.0GiB/s (12.9GB/s-12.9GB/s), io=361GiB (388GB), run=30029-30029msec
|
Pod 上
fio -numjobs=128 -fallocate=none -iodepth=2 -ioengine=libaio -direct=1 -rw=read -bs=4M --group_reporting -size=100M -time_based -runtime=30 -name=2depth_128file_4M_direct_read_bw -directory=/data/fio-read
Run status group 0 (all jobs):
READ: bw=12.1GiB/s (12.0GB/s), 12.1GiB/s-12.1GiB/s (12.0GB/s-12.0GB/s), io=363GiB (390GB), run=30030-30030msec
两个测试结果基本一致,读取速度都在 12GB/s 左右,正好是测试环境全部磁盘的读取极限,2 块盘,单盘读取速度 6GB/s。
主机上
1
2
3
4
| fio -numjobs=128 -fallocate=none -iodepth=2 -ioengine=libaio -direct=1 -rw=write -bs=4M --group_reporting -size=100M -time_based -runtime=30 -name=2depth_128file_4M_direct_write_bw -directory=/3fs/stage/fio-write
Run status group 0 (all jobs):
WRITE: bw=1623MiB/s (1702MB/s), 1623MiB/s-1623MiB/s (1702MB/s-1702MB/s), io=47.9GiB (51.5GB), run=30238-30238msec
|
Pod
1
2
3
4
| fio -numjobs=128 -fallocate=none -iodepth=2 -ioengine=libaio -direct=1 -rw=write -bs=4M --group_reporting -size=100M -time_based -runtime=30 -name=2depth_128file_4M_direct_write_bw -directory=/data/fio-write
Run status group 0 (all jobs):
WRITE: bw=1610MiB/s (1688MB/s), 1610MiB/s-1610MiB/s (1688MB/s-1688MB/s), io=47.6GiB (51.1GB), run=30259-30259msec
|
两个测试结果基本一致,写入速度都在 1.6GB/s 左右,与测试环境单盘 4GB/s 的写入速度有些差距。
6. 总结
本篇介绍了如何将 3FS 对接到 Fluid 中,主要内容如下:
- 为了方便编译和运行 3FS,最好打包一个 builder 镜像,shaowenchen/3fs-builder:latest
- Fluid 提供了一种快速对接 mount 类型存储的方式就是 ThinRuntime,文中提供了 3FS 的 ThinRuntime 镜像 shaowenchen/demo-fluid-3fs:latest
- 通过 ThinRuntimeProfile、Dataset、ThinRuntime 的创建,可以将 3FS 挂载到 Pod 中,避免手动挂载的繁琐操作
- 通过性能测试发现,主机和 Pod 挂载的 3FS 在读取速度上基本一致,可以放心使用