非特權 Pod 如何運行用戶態(tài)文件系統(tǒng)
FUSE(filesystem in userspace)是指用戶態(tài)的文件系統(tǒng)。通過 FUSE 內核模塊的支持,開發(fā)者只需要根據 FUSE 提供的接口實現具體的文件操作就可以實現一個文件系統(tǒng),FUSE 包含一個內核模塊和一個用戶空間守護進程(FUSE daemon)。內核模塊加載時被注冊成 Linux 虛擬文件系統(tǒng)的一個 FUSE 文件系統(tǒng)驅動,此外還注冊了一個 /dev/fuse 的塊設備。FUSE daemon 通過 /dev/fuse 讀取請求,并將結果寫入 /dev/fuse,這個 FUSE 設備就充當了 FUSE daemon 與內核通信的橋梁。
在 Kubernetes 環(huán)境中,如果需要在 Pod 中運行 FUSE daemon,通常是將其設置為特權容器。當 Pod 為特權時,自然所有的權限都會有,甚至也直接享用宿主機的設備。但非特權 Pod 想要運行用戶態(tài)的文件系統(tǒng)有點困難,主要需要兩點:
- 掛載權限;
- 對 /dev/fuse 設備的讀寫權限;
本篇文章主要講解在沒有特權的情況下,如何在 Pod 中運行用戶態(tài)文件系統(tǒng)。
掛載權限
首先,mount 屬于管理級別的系統(tǒng)調用,需要 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
其次,有的系統(tǒng)開啟了 Linux 內核安全模塊 AppArmor,默認的 AppArmor 配置也是沒有 mount 權限的,需要額外配置 mount 權限,如下:
#include <tunables/global>
profile app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
mount,
umount,
capability sys_admin,
...
}
在每臺節(jié)點上配置好之后,在 pod 中加入 container.apparmor.security.beta.kubernetes.io/app: localhost/app 的注解,以聲明使用這份 AppArmor 配置,詳細信息可以參考《如何使用 AppArmor 限制應用的權限》。
FUSE 設備
一個用戶態(tài)的文件系統(tǒng)包含一個內核模塊和一個用戶空間 daemon 進程。內核模塊加載時被注冊成 Linux 虛擬文件系統(tǒng)的一個 fuse 文件系統(tǒng)驅動。此外,還注冊了一個 /dev/fuse 的塊設備。該塊設備作為 fuse daemon 進程與內核通信的橋梁。而對于用戶空間的 fuse daemon 來說,訪問 /dev/fuse 設備是至關重要的。
在 Kubernetes 環(huán)境中,如果要將宿主機的某個塊設備掛載進 pod 中,可以使用 Device Plugins。而 Device Plugins 需要第三方服務自己提供,實現起來也比較簡單。
對于 FUSE 設備的 Device Plugins 來說,社區(qū)也有很多實現,不過都大同小異,只需要在 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 如何運行用戶態(tài)文件系統(tǒng),主要需要給與掛載所需權限即 CAP_SYS_ADMIN 并將宿主機的塊設備 /dev/fuse 掛載進 Pod 中,其中掛載塊設備需要做一些開發(fā)工作,實現一個 Device Plugin。然后就可以愉快地在非特權 Pod 中運行 FUSE daemon 了。