1 个不稳定版本
0.1.0 | 2023年9月12日 |
---|
#702 in 文件系统
150KB
4K SLoC
PuzzleFS
PuzzleFS 是一种下一代容器文件系统。
设计目标
- 在我们想要的时候进行计算,即
- 镜像构建应该快速
- 镜像挂载/读取应该快速
- 中间可选的“规范”步骤
- 不需要全树遍历
- 使用overlay的巧妙使用,无需mtree风格的文件系统遍历
- casync风格的generate-a-tar-then-diff更适合通用用途,其中你不想有特殊的文件系统设置
- 简单到足以在内核中解码
- Cisco工作于此的主要动机是直接挂载支持
摘要
Puzzlefs 是一种容器文件系统,旨在解决现有 OCI 格式的局限性。项目的目标主要是减少重复、可重复的镜像构建、直接挂载支持和内存安全性保证,部分灵感来源于 OCIv2 设计文档。
通过使用内容定义的块化算法 FastCDC,实现了减少重复。此实现允许层之间共享块。从现有层开始构建新层允许重用大部分块。
项目的另一个目标是可重复的镜像构建,这是通过定义镜像格式的规范表示来实现的。
直接挂载支持是 puzzlefs 的关键特性,与 fs-verity 一起,它提供了数据完整性。目前,puzzlefs 作为用户空间文件系统(FUSE)实现。正在开发一个只读内核文件系统驱动程序。
最后,内存安全性对 puzzlefs 至关重要,因此我们决定用 Rust 实现。另一个目标是共享用户空间和内核空间中的相同代码,以提供一种安全实现。
OCIv2 设计文档
https://hackmd.io/@cyphar/ociv2-brainstorm
大部分我认为这解决了那里的问题,除了两个
-
显式最小元数据:这部分基本上没有被处理,因为我没有认真考虑过;我们没有理由不能从规范中删除例如块设备,或者至少添加一个不建议使用的注释。也许我们应该使mtime等成为可选的?但这样规范化的难度会增加。也许这应该在图像构建时指定,类似于我们的设计中使用的分块算法。
-
懒加载支持:这似乎与“直接挂载”支持直接相冲突,至少如果直接挂载代码要驻留在内核中;我们可能不想直接在内核中实现懒加载,因为它涉及网络和许多其他东西。然而,使用FUSE做这个相对容易,这表明我们可能应该选择一个好的实现语言(例如rust :),这样我们就可以在内核和用户空间中使用相同的代码,从而轻松支持这一功能。
入门指南
构建依赖
Puzzlefs是用rust编写的,您可以从https://www.rust-lang.net.cn/tools/install下载。它需要一个nightly工具链,您可以使用rustup toolchain install nightly
命令添加。
需要capnp工具从capnproto模式语言自动生成rust代码。这是在构建时使用capnpc crate完成的。
如何构建
运行make
(或cargo build
)进行调试构建,以及make release
(cargo build --release
)进行发布构建。生成的二进制文件分别在target/debug/puzzlefs
和target/release/puzzlefs
中。
运行测试
要运行测试,请运行make check
。
测试需要安装skopeo和umoci。它还需要root权限来运行test_fs_verity
测试。
构建puzzlefs镜像
要构建puzzlefs镜像,您需要指定一个目录,其中包含您希望在镜像中包含的根文件系统。例如
$ tree /tmp/example-rootfs
/tmp/example-rootfs
├── algorithms
│ └── binary-search.txt
└── lorem_ipsum.txt
2 directories, 2 files
然后运行
$ cargo run --release -- build /tmp/example-rootfs /tmp/puzzlefs-image puzzlefs_example
puzzlefs image manifest digest: 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5
这将构建一个包含上述根文件系统的puzzlefs镜像,位于/tmp/puzzlefs-image
,标签为puzzlefs_example
。它还输出了镜像的manifest摘要,这对于使用fs-verity验证镜像的完整性非常有用。
有关其他构建选项,请运行puzzlefs build -h
。
挂载puzzlefs镜像
要挂载上述puzzlefs镜像,首先我们需要创建一个挂载点
mkdir /tmp/mounted-image
然后使用puzzlefs镜像的位置、镜像标签和挂载点运行puzzlefs mount
$ cargo run --release -- mount /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image
如果一切顺利,您将在mount
的输出中看到一个fuse
条目
$ mount
...
/dev/fuse on /tmp/mounted-image type fuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)
并在日志中看到以下消息
$ journalctl --since "2 min ago" | grep puzzlefs
Aug 14 10:30:27 archlinux-cisco puzzlefs[55544]: Mounting /tmp/mounted-image
挂载点还包含rootfs
$ tree /tmp/mounted-image
/tmp/mounted-image
├── algorithms
│ └── binary-search.txt
└── lorem_ipsum.txt
2 directories, 2 files
要获取额外的挂载选项,请运行cargo run -- mount -h
。
启用fs-verity的挂载
如果您想以fs-verity
真实性保护挂载文件系统,首先运行以下命令来启用fs-verity
$ cargo run --release -- enable-fs-verity /tmp/puzzlefs-image puzzlefs_example 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5
这会使数据和元数据文件变为只读。任何读取损坏数据的操作都将失败。
然后使用--digest
选项运行挂载
$ cargo run --release -- mount --digest 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5 /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image
PuzzleFS现在确保它打开的每个文件都启用了fs-verity,并且fs-verity度量与存储在清单中的fs-verity数据相匹配。图像清单的fs-verity摘要与通过--digest
选项传递到命令行的摘要进行比较。
这仅在底层文件系统(PuzzleFS镜像所在文件系统)支持并启用了fsverity
的情况下才有效。否则,在运行enable-fs-verity
时可能会遇到如下错误
Error: fs error: Inappropriate ioctl for device (os error 25)
Caused by:
Inappropriate ioctl for device (os error 25)
要检查fs-verity是否已启用,请使用tune2fs
$ mount | grep -w '/'
/dev/mapper/MyVolGroup-root on / type ext4 (rw,relatime)
$ sudo tune2fs -l /dev/mapper/MyVolGroup-root | grep verity
Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum verity
要设置一个支持fs-verity
的1MB循环设备(ext4文件系统),并将其挂载到/mnt
下,请运行
$ mktemp -u
/tmp/tmp.2CDDHVPLXp
$ touch /tmp/tmp.2CDDHVPLXp
$ dd if=/dev/zero of=/tmp/tmp.2CDDHVPLXp bs=1k count=1024
1024+0 records in
1024+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.00203188 s, 516 MB/s
$ sudo losetup -f --show /tmp/tmp.2CDDHVPLXp
/dev/loop1
$ sudo mkfs -t ext4 -F -b4096 -O verity /dev/loop1
mke2fs 1.47.0 (5-Feb-2023)
Filesystem too small for a journal
Discarding device blocks: done
Creating filesystem with 256 4k blocks and 128 inodes
Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
$ sudo mount /dev/loop1 /mnt
$ sudo chown -R $(id -u):$(id -g) /mnt
$ sudo tune2fs -l /dev/loop1 | grep verity
Filesystem features: ext_attr resize_inode dir_index filetype extent 64bit flex_bg metadata_csum_seed sparse_super large_file huge_file dir_nlink extra_isize metadata_csum verity
现在将puzzlefs镜像复制到/mnt
并再次尝试执行verity设置命令。
调试挂载问题
当在后台挂载puzzlefs文件系统(即不带-f
标志)时,错误将被记录到日志中,例如
$ journalctl --since "2 min ago" | grep puzzlefs
Jul 13 18:37:30 archlinux-cisco puzzlefs[305462]: mount_background failed: fs error: fs error: Inappropriate ioctl for device (os error 25)
为调试目的,您可以使用RUST_LOG
环境变量以及挂载的-f
标志
$ RUST_LOG=DEBUG cargo run --release -- mount -f /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image
[2023-07-13T16:08:27Z INFO fuser::session] Mounting /tmp/mounted-image
[2023-07-13T16:08:27Z DEBUG fuser::mnt::fuse_pure] fusermount:
[2023-07-13T16:08:27Z DEBUG fuser::mnt::fuse_pure] fusermount:
[2023-07-13T16:08:27Z DEBUG fuser::request] FUSE( 2) ino 0x0000000000000000 INIT kernel ABI 7.38, capabilities 0x73fffffb, max readahead 131072
[2023-07-13T16:08:27Z DEBUG fuser::request] INIT response: ABI 7.8, flags 0x1, max readahead 131072, max write 16777216
...
挂载点就绪时的通知
前台挂载(mount -f
)
可以将命名管道传递给mount
命令。从该管道读取是阻塞操作,等待直到puzzlefs信号挂载点就绪。如果挂载操作成功,则将s
字符写入管道,否则写入f
。这受到了squashfuse问题的启发。
以下脚本展示了如何等待puzzlefs挂载点就绪。该脚本假设在/tmp/puzzlefs-image
处有可用的puzzlefs镜像,并且/tmp/mounted-image
目录已经存在。
#!/bin/bash
FIFO=$(mktemp -u)
mkfifo "$FIFO"
cargo run --release -- mount -i "$FIFO" -f /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image&
STATUS=$(head -c1 "$FIFO")
if [ "$STATUS" = "s" ]; then
echo "Mountpoint contains:"
ls /tmp/mounted-image
else
echo "Mounting puzzlefs on /tmp/mounted-image failed"
fi
后台挂载
在后台挂载时,puzzlefs使用匿名管道在原始进程和为其启动的守护进程之间进行通信,以等待挂载点可用。这意味着puzzlefs mount
命令仅在挂载点就绪后才会完成执行。
卸载puzzlefs镜像
如果您已将-f
标志指定给mount
,只需按Ctrl-C
即可。
否则,运行fusermount -u /tmp/mounted-image
。您需要安装fuse
包。
检查puzzlefs镜像
$ cd /tmp/puzzlefs-image
$ cat index.json | jq .
{
"schemaVersion": -1,
"manifests": [
{
"digest": "sha256:0efa2a4b490abb02a5b9b5f2d43c8262643dba48c67f14b236df0a6f1ea745d8",
"size": 272,
"media_type": "application/vnd.puzzlefs.image.rootfs.v1",
"annotations": {
"org.opencontainers.image.ref.name": "puzzlefs_example"
}
}
],
"annotations": {}
}
digest
指定了puzzlefs镜像清单,需要使用capnp工具
和清单模式进行解码(假设您已将puzzlefs克隆到~/puzzlefs
)
$ capnp convert binary:json ~/puzzlefs/format/manifest.capnp Rootfs < blobs/sha256/0efa2a4b490abb02a5b9b5f2d43c8262643dba48c67f14b236df0a6f1ea745d8
{ "metadatas": [{ "digest": [102, 197, 227, 96, 136, 156, 147, 144, 139, 154, 248, 228, 29, 161, 252, 228, 118, 222, 21, 44, 132, 0, 214, 164, 80, 74, 121, 156, 26, 85, 123, 57],
"offset": "0",
"compressed": false }],
"fsVerityData": [
{ "digest": [102, 197, 227, 96, 136, 156, 147, 144, 139, 154, 248, 228, 29, 161, 252, 228, 118, 222, 21, 44, 132, 0, 214, 164, 80, 74, 121, 156, 26, 85, 123, 57],
"verity": [224, 180, 63, 193, 142, 198, 24, 175, 78, 42, 126, 227, 253, 187, 102, 162, 31, 77, 85, 252, 205, 137, 198, 216, 26, 213, 113, 238, 144, 79, 93, 244] },
{ "digest": [239, 32, 68, 39, 210, 105, 37, 83, 131, 158, 224, 24, 162, 25, 96, 90, 140, 95, 158, 194, 97, 2, 153, 175, 54, 197, 216, 193, 115, 121, 62, 22],
"verity": [196, 54, 71, 79, 3, 104, 3, 253, 163, 243, 85, 213, 67, 235, 144, 210, 20, 206, 160, 209, 75, 164, 93, 22, 79, 84, 41, 119, 20, 84, 64, 164] } ],
"manifestVersion": "1" }
metadatas
包含一个层列表(在这种情况下只有一个),可以进一步解码(通过十进制转十六进制获取blob的sha)
$ capnp convert binary:json ~/puzzlefs/format/metadata.capnp InodeVector < blobs/sha256/66c5e360889c93908b9af8e41da1fce476de152c8400d6a4504a799c1a557b39
{"inodes": [
{ "ino": "1",
"mode": {"dir": {
"entries": [
{ "ino": "2",
"name": [97, 108, 103, 111, 114, 105, 116, 104, 109, 115] },
{ "ino": "3",
"name": [108, 111, 114, 101, 109, 95, 105, 112, 115, 117, 109, 46, 116, 120, 116] } ],
"lookBelow": false }},
"uid": 1000,
"gid": 1000,
"permissions": 493 },
{ "ino": "2",
"mode": {"dir": {
"entries": [{ "ino": "4",
"name": [98, 105, 110, 97, 114, 121, 45, 115, 101, 97, 114, 99, 104, 46, 116, 120, 116] }],
"lookBelow": false }},
"uid": 1000,
"gid": 1000,
"permissions": 493 },
{ "ino": "3",
"mode": {"file": {"chunks": [{ "blob": {
"digest": [239, 32, 68, 39, 210, 105, 37, 83, 131, 158, 224, 24, 162, 25, 96, 90, 140, 95, 158, 194, 97, 2, 153, 175, 54, 197, 216, 193, 115, 121, 62, 22],
"offset": "0",
"compressed": false },
"len": "865" }]}},
"uid": 1000,
"gid": 1000,
"permissions": 420 },
{ "ino": "4",
"mode": {"file": {"chunks": [{ "blob": {
"digest": [239, 32, 68, 39, 210, 105, 37, 83, 131, 158, 224, 24, 162, 25, 96, 90, 140, 95, 158, 194, 97, 2, 153, 175, 54, 197, 216, 193, 115, 121, 62, 22],
"offset": "865",
"compressed": false },
"len": "278" }]}},
"uid": 1000,
"gid": 1000,
"permissions": 420 } ]}
实现
此工作空间包含一个库和一个可执行crate
puzzlefs-lib
是库crateformat
是用于序列化和反序列化puzzlefs格式的模块builder
是用于构建puzzlefs镜像的模块extractor
是用于提取puzzlefs镜像的模块reader
是用于挂载puzzlefs镜像的fuse模块
exe/
是上述功能的可执行前端
贡献
贡献需要通过所有静态分析。
此外,所有提交必须在描述中包含一条Signed-off-by:
行。这表明你证明以下陈述,即开发者证书起源)。您可以通过使用git commit -s --amend
自动将此行添加到您的提交中。
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
许可协议
puzzlefs 在Apache License, Version 2.0下发布,并
版权所有 (C) 2020-2021 思科系统公司
依赖关系
~15–26MB
~402K SLoC