1 个不稳定版本

0.1.0 2023年9月12日

#702 in 文件系统

Apache-2.0

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 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。它还输出了镜像的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 是库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 思科系统公司

依赖关系

~15–26MB
~402K SLoC