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