FUSE(filesystem in userspace)是指用户态的文件系统。通过 FUSE 内核模块的支持,开发者只需要根据 FUSE 提供的接口实现具体的文件操作就可以实现一个文件系统,FUSE 包含一个内核模块和一个用户空间守护进程(FUSE daemon)。内核模块加载时被注册成 Linux 虚拟文件系统的一个 FUSE 文件系统驱动,此外还注册了一个 /dev/fuse 的块设备。FUSE daemon 通过 /dev/fuse 读取请求,并将结果写入 /dev/fuse,这个 FUSE 设备就充当了 FUSE daemon 与内核通信的桥梁。
在 Kubernetes 环境中,如果需要在 Pod 中运行 FUSE daemon,通常是将其设置为特权容器。当 Pod 为特权时,自然所有的权限都会有,甚至也直接享用宿主机的设备。但非特权 Pod 想要运行用户态的文件系统有点困难,主要需要两点:
- 挂载权限;
- 对 /dev/fuse 设备的读写权限;
本篇文章主要讲解在没有特权的情况下,如何在 Pod 中运行用户态文件系统。
挂载权限
首先,mount 属于管理级别的系统调用,需要 CAP_SYS_ADMIN 权限,参考 capability 文档:
CAP_SYS_ADMIN
Note: this capability is overloaded; see Notes to kernel
developers, below.
* Perform a range of system administration operations
including: quotactl(2), mount(2), umount(2),
pivot_root(2), swapon(2), swapoff(2), sethostname(2),
and setdomainname(2);
CAP_SYS_ADMIN 可以在 Pod 的 .securityContext.capabilities 中设置,如下:
securityContext:
capabilities:
add:
- SYS_ADMIN
其次,有的系统开启了 Linux 内核安全模块 AppArmor,默认的 AppArmor 配置也是没有 mount 权限的,需要额外配置 mount 权限,如下:
#include <tunables/global>
profile app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
mount,
umount,
capability sys_admin,
...
}
在每台节点上配置好之后,在 pod 中加入 container.apparmor.security.beta.kubernetes.io/app: localhost/app 的注解,以声明使用这份 AppArmor 配置,详细信息可以参考《如何使用 AppArmor 限制应用的权限》。
FUSE 设备
一个用户态的文件系统包含一个内核模块和一个用户空间 daemon 进程。内核模块加载时被注册成 Linux 虚拟文件系统的一个 fuse 文件系统驱动。此外,还注册了一个 /dev/fuse 的块设备。该块设备作为 fuse daemon 进程与内核通信的桥梁。而对于用户空间的 fuse daemon 来说,访问 /dev/fuse 设备是至关重要的。
在 Kubernetes 环境中,如果要将宿主机的某个块设备挂载进 pod 中,可以使用 Device Plugins。而 Device Plugins 需要第三方服务自己提供,实现起来也比较简单。
对于 FUSE 设备的 Device Plugins 来说,社区也有很多实现,不过都大同小异,只需要在 Device Plugins 接口 Allocate 中将宿主机的 /dev/fuse 目录挂载进容器的 /dev/fuse 并给与 rwm 权限即可。比如:
func (m *FuseDevicePlugin) Allocate(ctx context.Context, reqs *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) {
devs := m.devs
var responses pluginapi.AllocateResponse
for _, req := range reqs.ContainerRequests {
for _, id := range req.DevicesIDs {
log.Printf("Allocate device: %s", id)
if !deviceExists(devs, id) {
return nil, fmt.Errorf("invalid allocation request: unknown device: %s", id)
}
}
response := new(pluginapi.ContainerAllocateResponse)
response.Devices = []*pluginapi.DeviceSpec{
{
ContainerPath: "/dev/fuse",
HostPath: "/dev/fuse",
Permissions: "rwm",
},
}
responses.ContainerResponses = append(responses.ContainerResponses, response)
}
return &responses, nil
}
上述 Device Plugins 的完整代码实现详见zwwhdls/node-device-plugin。
在 Pod 中使用只需要在 resources 中申明即可,比如:
apiVersion: v1
kind: Pod
metadata:
name: fuse
spec:
containers:
- name: test
image: centos
command: [ "sleep", "infinity" ]
resources:
limits:
hdls.me/fuse: "1"
requests:
hdls.me/fuse: "1"
总结
本文主要讲解了非特权 Pod 如何运行用户态文件系统,主要需要给与挂载所需权限即 CAP_SYS_ADMIN 并将宿主机的块设备 /dev/fuse 挂载进 Pod 中,其中挂载块设备需要做一些开发工作,实现一个 Device Plugin。然后就可以愉快地在非特权 Pod 中运行 FUSE daemon 了。