1 个不稳定版本

0.1.0 2023年9月12日

#1262文件系统


用于 puzzlefs

Apache-2.0

135KB
3.5K 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 releasecargo build --release)进行发布构建。生成的二进制文件分别在target/debug/puzzlefstarget/release/puzzlefs中。

运行测试

要运行测试,请运行make check

测试需要安装skopeoumoci。它还需要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。它还输出了镜像的清单摘要,这对于使用fs-verity验证镜像的完整性很有用。

有关其他构建选项,请运行puzzlefs build -h

挂载puzzlefs镜像

要挂载上述puzlefs镜像,我们首先需要创建一个挂载点

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

要设置一个1MB的环回设备,使用支持fs-verity的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 是库 crate
    • format 是序列化/反序列化 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 思科系统公司

依赖关系

~12–23MB
~353K SLoC