17 个稳定版本
1.11.1 | 2024 年 7 月 15 日 |
---|---|
1.10.1 | 2024 年 1 月 24 日 |
1.9.0 | 2023 年 12 月 20 日 |
1.8.0 | 2023 年 9 月 13 日 |
1.0.0 | 2021 年 12 月 23 日 |
52 在 文件系统 中
308 每月下载量
580KB
11K SLoC
virtiofsd
使用 Rust 编写的 virtio-fs vhost-user 设备守护进程。
从源码构建
要求
本项目依赖于 libcap-ng 和 libseccomp。您可以通过构建各自的源代码或从您的发行版中安装相应的开发包来获取这些依赖项(如果可用)
- Fedora/CentOS/RHEL
dnf install libcap-ng-devel libseccomp-devel
- Debian/Ubuntu
apt install libcap-ng-dev libseccomp-dev
编译
virtiofsd 使用 Rust 编写,因此您需要安装 Rust 来编译它,并且它使用 cargo 来管理项目和其依赖项。安装 Rust 后,您可以通过运行以下命令将其编译为二进制文件:
cargo build --release
CI 构建的二进制文件
每次合并新代码时,CI 管道都会上传 virtiofsd 的调试二进制文件。这是任何人都可以下载和测试 virtiofsd 而无需安装 Rust 工具链的便捷方式。
调试二进制文件仅针对基于 x86_64 的 Linux 系统。
贡献
用法
此程序必须以 root 用户或用户命名空间内的“伪造”root 用户身份运行(参见 以非特权用户身份运行)。
程序在启动过程中尽可能降低权限,尽管它必须能够以任何 uid/gid 创建和访问文件
- 通过
seccomp(2)
限制调用系统调用的能力。 - Linux
capabilities(7)
被移除。virtiofsd 仅保留以下功能:CAP_CHOWN
、CAP_DAC_OVERRIDE
、CAP_FOWNER
、CAP_FSETID
、CAP_SETGID
、CAP_SETUID
、CAP_MKNOD
、CAP_SETFCAP
(如果使用--inode-file-handles
,则还包括CAP_DAC_READ_SEARCH
)。
virtiofsd [FLAGS] [OPTIONS] --fd <fd>|--socket-path <socket-path> --shared-dir <shared-dir>
标志
-h, --help
打印帮助信息。
-V, --version
打印版本信息。
--syslog
记录到syslog。默认:stderr。
--print-capabilities
打印vhost-user.json后端程序功能并退出。
--allow-direct-io
尊重由虚拟机应用程序传递下来的O_DIRECT
标志。
--announce-submounts
告知虚拟机哪些目录是挂载点。如果在共享目录中挂载了多个文件系统,virtiofsd会直接将inode ID传递给虚拟机,因为这些ID仅在单个文件系统中是唯一的,如果共享目录中挂载了多个文件系统,虚拟机可能会遇到重复项。--announce-submounts
通过为每个子挂载报告不同的设备号来解决这个问题。
此外,在运行--announce-submounts
时,客户端为要同步的每个子挂载发送一个SYNCFS
请求,因此virtiofsd将在每个子挂载上调用syncfs()
。另一方面,在没有--announce-submounts
的情况下运行时,客户端只为根挂载发送一个SYNCFS
请求,这可能导致数据丢失/损坏。
--no-killpriv-v2
禁用KILLPRIV V2
支持。如果共享目录是NFS文件系统,则需要此操作。KILLPRIV V2
支持默认禁用。
--killpriv-v2
启用KILLPRIV V2
支持。默认禁用。
--no-readdirplus
禁用对READDIRPLUS
操作的支持。
--writeback
启用写回缓存。
--xattr
启用对扩展属性的支持。
--posix-acl
启用对posix ACLs的支持(意味着--xattr)。
--security-label
启用对安全标签(SELinux)的支持。
--preserve-noatime
始终保留O_NOATIME
。
默认情况下,virtiofsd会隐式清理O_NOATIME
,以防止在没有正确的能力访问所有导出的文件时出现潜在权限错误(通常在以无权限用户身份运行且使用--sandbox none
时,这意味着它不会设置CAP_FOWNER
能力)。
可以使用--preserve-noatime
选项来覆盖此行为并保留客户端指定的O_NOATIME
标志。
选项
--shared-dir <shared-dir>
共享目录路径。
--tag <tag>
virtio设备宣传的标签。
设置此选项将启用VHOST_USER_PROTOCOL_F_CONFIG的广播。但是,您的虚拟机管理程序的vhost-user前端可能不会协商此功能并且(或)忽略此值。值得注意的是,QEMU当前(截至8.1)忽略CONFIG功能。从7.1到8.0版本的QEMU在尝试记录关于不支持此功能的警告时可能会崩溃。
--socket-group <socket-group>
vhost-user套接字的组名。
--socket-path <socket-path>
vhost-user套接字路径。
--fd <fd>
监听套接字的文件描述符。
--log-level <log-level>
日志级别(错误、警告、信息、调试、跟踪、关闭)。
默认:info。
--thread-pool-size <thread-pool-size>
最大线程池大小。值“0”将禁用池。
默认:0。
--rlimit-nofile <rlimit-nofile>
设置最大文件描述符数。如果软限制大于1M或传递了参数--rlimit-nofile=0
,则不更改最大文件描述符数。
默认:min(1000000, /proc/sys/fs/nr_open
)。
--modcaps=<modcaps>
修改功能列表,例如:--modcaps=+sys_admin:-chown
。虽然这不是强制性的,但建议始终使用 =
符号,否则这将失败 --modcaps -mknod
,因为它将被解释为两个选项,而不是预期的 --modcaps=-mknod
。
--sandbox <sandbox>
沙箱机制以隔离守护进程(命名空间、chroot、无)。
-
命名空间:程序切换到新的文件系统命名空间(
namespaces(7)
)并调用pivot_root(2)
使共享目录树成为根。还会创建一个新的挂载(mount_namespaces(7)
)、pid(pid_namespaces(7)
)和网络命名空间(network_namespaces(7)
)以隔离进程。 -
chroot:程序调用
chroot(2)
使共享目录树成为根。此模式旨在容器环境中,其中容器运行时已经设置了命名空间,而程序没有权限自行创建命名空间。 -
无:不隔离守护进程(不建议)。
命名空间 和 chroot 沙箱模式都防止由于符号链接和其他可能导致文件在共享目录之外的文件系统对象而导致的“文件系统逃逸”。
默认:命名空间。
--seccomp <seccomp>
当seccomp发现不允许的系统调用时要采取的操作(无、kill、log、trap)。
默认:kill。
--cache <cache>
文件系统应使用的缓存策略(auto、always、metadata、never)。
默认:auto。
--allow-mmap
对于带有 --cache={metadata, never}
的共享目录,允许包含在共享目录中的文件进行 mmap
。
--inode-file-handles=<inode-file-handles>
何时使用文件句柄来引用inode而不是 O_PATH
文件描述符(never、prefer、mandatory)。
-
never:从不使用文件句柄,始终使用
O_PATH
文件描述符。 -
prefer:尝试生成文件句柄,但回退到
O_PATH
文件描述符,如果底层文件系统不支持文件句柄或CAP_DAC_READ_SEARCH
不可用。当共享目录下有各种不同的文件系统,其中一些不支持文件句柄时很有用。 -
mandatory:始终使用文件句柄。如果底层文件系统不支持文件句柄或
CAP_DAC_READ_SEARCH
不可用,它将失败。
使用文件句柄可以减少virtiofsd保持打开的文件描述符数量,这不仅有助于资源,在virtiofsd应该只为在虚拟机中打开的文件打开文件描述符的情况下也很重要,例如,绕过NFS的愚蠢重命名(参见 NFS FAQ,第D2节:“什么是‘愚蠢的重命名’?”)。
默认:never。
--xattrmap <xattrmap>
为在主机和虚拟机之间转换扩展属性添加自定义规则(例如,:map::user.virtiofs.:
)。有关详细信息,请参阅 扩展属性映射。
--uid-map=:namespace_uid:host_uid:count:
当以非root用户运行virtiofsd时,将从主机映射一系列UID到命名空间。为了使用此选项,必须通过subuid(5)
设置从属用户ID的范围。virtiofsd使用newuidmap(1)
进行非平凡情况的处理,这需要有效的subuid来进行映射。如果未提供此选项,virtiofsd将为当前uid设置1-to-1映射。
namespace_uid:用户命名空间内UID范围的开头。host_uid:用户命名空间外UID范围的开头。count:范围长度(用户命名空间内外)。
例如,假设调用UID为1000,/etc/subuid的内容为:1000:100000:65536,这创建从100000开始的65536个子UID,即属于实际UID 1000的(包含)范围[100000, 165535]。此范围可以通过--uid-map=:0:100000:65536:映射到virtiofsd的用户命名空间内的UID [0, 65535](即 guest中看到的)。或者,您可以将自己的UID映射到命名空间中的单个UID:例如,--uid-map=:0:1000:1:将UID 1000映射到命名空间中root的UID(因此guest)。
--gid-map=:namespace_gid:host_gid:count:
当以非root用户运行virtiofsd时,将从主机映射一系列GID到命名空间。为了使用此选项,必须通过subgid(5)
设置从属组ID的范围。virtiofsd使用newgidmap(1)
进行非平凡情况的处理,这需要有效的subgid来进行映射。如果未提供此选项,virtiofsd将为当前gid设置1-to-1映射。
namespace_gid:用户命名空间内GID范围的开头。host_gid:用户命名空间外GID范围的开头。count:范围长度(用户命名空间内外)。
例如,假设调用GID为1000,/etc/subgid的内容为:1000:100000:65536,这创建从100000开始的65536个子GID,即属于实际GID 1000的(包含)范围[100000, 165535]。此范围可以通过--gid-map=:0:100000:65536:映射到virtiofsd的用户命名空间内的GID [0, 65535](即 guest中看到的)。或者,您可以将自己的GID映射到命名空间中的单个GID:例如,--gid-map=:0:1000:1:将GID 1000映射到命名空间中root的GID(因此guest)。
--migration-mode=<find-paths>
定义了如何执行迁移,即如何将内部状态表示为目的地实例,以及如何获取该表示。请注意(当使用QEMU时),需要QEMU版本8.2或更高版本才能使用virtio-fs迁移。
virtiofsd在内部持有所有由guest索引或打开的inode的引用。在迁移过程中,这些引用需要转移到目的地;如何进行此操作由此开关确定
- find-paths:遍历共享目录(穷举搜索)以找到源实例持有的所有inode的路径(相对于共享目录),将这些路径传输到目的地,并让目的地实例打开这些路径。这允许在不要求特殊权限的情况下进行迁移,无论源和目的地是否使用相同的共享目录;但它以I/O(通过共享目录的DFS)为代价,并且在迁移过程中第三方更改共享目录中的元数据(例如重命名、解除链接、移除权限)时容易受到攻击,这可能导致数据丢失和/或损坏。
此参数在迁移的目标端被忽略。
--migration-on-error=<abort|guest-error>
控制如何在迁移过程中响应错误。
在迁移过程中,一些被虚拟机索引或打开的inode可能无法迁移:要么源实例无法构造出目标实例如何找到/打开某些inode的指令,要么目标实例发现自己无法遵循这些指令。在所有情况下,目标实例都会收到这些inode的通知,然后根据此参数的值决定如何处理。
-
abort:无论目标实例遇到任何此类错误,它都会向vhost-user前端(例如QEMU)返回一个硬错误,导致迁移终止。执行将回到源虚拟机上继续。
-
guest-error:迁移允许完成,但所有受影响的inode都被标记为无效。虚拟机将无法访问任何此类inode,只能收到错误。
请注意,此参数仅用于目标实例;在迁移的源端,其值被忽略。
--migration-verify-handles
确保迁移目标打开与源相同的inode。这仅当源和目标都将在同一文件系统上的同一共享目录中使用时才有效。
在迁移过程中,源实例通知目标实例虚拟机已索引或打开的所有inode,并让目标实例重新打开它们。此开关使源为每个此类inode生成一个文件句柄,并将其发送到目标,允许目标重新生成它已打开的inode的相同文件句柄,并验证它们是否相等,以证明它们是同一inode。
(文件句柄是每个文件系统的inode的唯一标识符,除了inode ID之外,还包括一个生成ID,以防止inode ID重用。)
使用此选项可以防止在迁移过程中外部实体重命名或替换inode,否则可能会导致数据丢失或损坏,因此当virtiofsd之外的进程有写入共享目录的权限时,应始终使用此选项。然而,再次强调,它仅当源和目标使用相同的共享目录时才有效;尽管在网络文件系统中,这并不要求它们在同一个主机上运行。
此参数在迁移的目标端被忽略。
--migration-confirm-paths
在切换到目标之前,仔细检查inode的身份,这可以在共享目录有第三方写入权限的情况下提高迁移的健壮性。
当通过相对于共享目录的路径表示迁移的inode时,在切换到目标期间,请再次确认每个路径是否仍然与相应的inode匹配。如果路径不匹配,请尝试通过查看/proc/self/fd中的相应符号链接进行纠正。
请注意,此选项需要在迁移切换阶段的暂停期间访问虚拟机索引或打开的每个inode,这可能会使该阶段的时间延长。
此参数在迁移的目标端被忽略。
示例
在vhost-user UNIX域套接字/tmp/vfsd.sock
上导出/mnt
host# virtiofsd --socket-path=/tmp/vfsd.sock --shared-dir /mnt \
--announce-submounts --inode-file-handles=mandatory &
host# qemu-system \
-blockdev file,node-name=hdd,filename=<your image> \
-device virtio-blk,drive=hdd \
-chardev socket,id=char0,path=/tmp/vfsd.sock \
-device vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=myfs \
-object memory-backend-memfd,id=mem,size=4G,share=on \
-numa node,memdev=mem \
-accel kvm -m 4G
guest# mount -t virtiofs myfs /mnt
有关将virtiofs配置添加到现有qemu命令行的信息,请参阅常见问题解答。
作为非特权用户运行
在没有root权限的情况下运行时,virtiofsd需要一个用户命名空间(参见user_namespaces(7)
),以便能够在虚拟机内部切换任意用户/组ID。如果UID/GID没有被映射(即,未编写uid_map
和gid_map
文件),virtiofsd将在用户命名空间中失败。在用户命名空间内运行virtiofsd有许多选项。例如
假设调用UID和GID是1000,且/etc/subuid
和/etc/subgid
的内容如下:
1000:100000:65536
使用 podman-unshare(1)
可以配置用户命名空间,使得调用用户的 UID(即 1000)和主要 GID(即 0)分别看起来是 UID 0 和 GID 0。与该用户和组匹配的任何范围在 /etc/subuid
和 /etc/subgid
中,也通过 newuidmap(1)
和 newgidmap(1)
辅助器映射为自身。
host$ podman unshare -- virtiofsd --socket-path=/tmp/vfsd.sock --shared-dir /mnt \
--announce-submounts --sandbox chroot &
使用 lxc-usernsexec(1)
,我们可以让调用用户不在映射中,而将用户命名空间中的根用户映射到用户和组 100000。
host$ lxc-usernsexec -m b:0:100000:65536 -- virtiofsd --socket-path=/tmp/vfsd.sock \
--shared-dir /mnt --announce-submounts --sandbox chroot &
为了获得与 podman-unshare(1)
相同的行为,我们需要运行
host$ lxc-usernsexec -m b:0:1000:1 -m b:1:100000:65536 -- virtiofsd --socket-path=/tmp/vfsd.sock \
--shared-dir /mnt --announce-submounts --sandbox chroot &
我们也可以选择 --sandbox none
而不是 --sandbox chroot
。
限制
-
在虚拟机内部,无法在共享目录中创建块或字符设备节点。
-
virtiofsd 不能使用文件句柄(
--inode-file-handles
需要CAP_DAC_READ_SEARCH
),因此需要大量的文件描述符。另外,在 NFS 上,不使用文件句柄可能会导致某些文件被删除后仍存在隐藏文件(参见 NFS FAQ,第 D2 节:"什么是 'silly rename'?")。 -
virtiofsd 无法增加
RLIMIT_NOFILE
。
常见问题解答(FAQ)
- 如何在虚拟机内部不能修改的目录中只读共享?为了完成此操作,您需要导出一个只读挂载点,例如,导出
share
mkdir ro-share
mount -o bind,ro share ro-share
virtiofsd --shared-dir ro-share ...
- 如何使用同一个 virtiofsd 共享多个目录?目前,virtiofsd 只支持共享单个目录,但可以通过子挂载实现此功能,例如,导出
share0
、share1
mkdir -p share/{sh0,sh1}
mount -o bind share0 share/sh0
mount -o bind share1 share/sh1
virtiofsd --announce-submounts --shared-dir share ...
请注意使用 --announce-submounts
以防止数据丢失/损坏。
-
如何将 virtiofs 设备添加到现有的 qemu 命令行
如果命令中还没有添加
-object memory-backend-memfd,id=mem
和-numa node,memdev=mem
或memory-backend=mem
属性,则需要添加它们。如果已配置了不同的内存后端,则应将其更改为
memory-backend-memfd
。-object memory-backend-memfd
必须 具有选项share=on
,并且size=
必须 与-m
定义的内存大小相匹配。对于每个virtiofs设备挂载,添加一个
-chardev socket,id=${MATCHING_ID},path=${VIRTIOFSD_SOCKET_PATH}
和-device vhost-user-fs-pci,queue-size=1024,chardev=${MATCHING_ID},tag=${VIRTIOFS_TAG}
,并使用适当的值替换shell风格的变量。
SELinux支持
可以通过运行带有选项"--security-label"的virtiofsd来启用对SELinux的支持。但这样会尝试在主机上保存客机的安全上下文到xattr security.selinux上,如果主机上的SELinux策略不允许virtiofsd执行此操作,则可能会失败。
因此,建议将客机的"security.selinux" xattr重新映射到主机上的"trusted.virtiofs.security.selinux"。将以下选项添加到命令行。
"--xattrmap=:map:security.selinux:trusted.virtiofs.:"
这将确保同一文件上客机和主机的SELinux xattr保持独立,不会互相干扰。并允许主机和客机实施各自独立的SELinux策略。
在主机上设置受信任的xattr需要CAP_SYS_ADMIN。因此,需要将此功能添加到守护进程。将以下选项添加到命令行。
"--modcaps=+sys_admin"
受信任的xattr不是namespaced的。因此,virtiofsd需要在init_user_ns中具有CAP_SYS_ADMIN。换句话说,不应使用用户namespaces,virtiofsd应以CAP_SYS_ADMIN运行。
赋予CAP_SYS_ADMIN增加了系统风险。现在virtiofsd更强大,如果受到损害,它可以对宿主机系统造成很大破坏。因此,在做出决定时,请记住这个权衡。
依赖项
~8–17MB
~219K SLoC