本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
非 root 容器和设备
当用户希望在 Linux 上部署使用加速器设备(通过 Kubernetes 设备插件)的容器时,Pod 的 securityContext
中与用户/组 ID 相关的安全设置会引发问题。在这篇博文中,我将讨论这个问题并描述迄今为止为解决它所做的工作。这不是一个关于如何修复 k/k 问题的长篇故事。
相反,本文旨在提高对这个问题的认识,并强调重要的设备用例。这是必要的,因为 Kubernetes 正在开发新的相关功能,例如对用户命名空间的支持。
为什么非 root 容器不能使用设备,以及为什么这很重要
在 Kubernetes 中运行容器的关键安全原则之一是最小权限原则。Pod/容器的 securityContext
指定了要设置的配置选项,例如 Linux 能力、MAC 策略以及用户/组 ID 值来实现此目的。
此外,集群管理员可以使用 PodSecurityPolicy(已弃用)或 Pod Security Admission(Alpha)等工具来强制执行部署在集群中的 Pod 所需的安全设置。这些设置可能要求容器必须 runAsNonRoot
,或者禁止它们以 root 的组 ID 运行在 runAsGroup
或 supplementalGroups
中。
在 Kubernetes 中,kubelet 构建要提供给容器的 Device
资源列表(基于设备插件的输入),并将该列表包含在发送到 CRI 容器运行时的 CreateContainer CRI 消息中。每个 Device
包含少量信息:主机/容器设备路径和所需的设备 cgroup 权限。
OCI 运行时 Linux 容器配置规范 期望除了设备 cgroup 字段外,还必须提供有关设备的更详细信息。
{
"type": "<string>",
"path": "<string>",
"major": <int64>,
"minor": <int64>,
"fileMode": <uint32>,
"uid": <uint32>,
"gid": <uint32>
},
CRI 容器运行时(containerd、CRI-O)负责从主机获取每个 Device
的此信息。默认情况下,运行时会复制主机设备的`uid`和`gid`。
uid
(uint32, 可选) - 容器命名空间中设备所有者的 ID。gid
(uint32, 可选) - 容器命名空间中设备组的 ID。
同样,运行时会根据 CRI 字段(包括 securityContext
中定义的字段:runAsUser
/runAsGroup
)准备其他强制性的 config.json
部分,这些字段通过以下方式成为 POSIX 平台用户结构的一部分:
uid
(int, 必填) 指定容器命名空间中的用户 ID。gid
(int, 必填) 指定容器命名空间中的组 ID。additionalGids
(int 数组, 可选) 指定容器命名空间中要添加到进程的其他组 ID。
然而,当尝试运行同时添加了设备并通过 runAsUser
/runAsGroup
设置了非 root uid/gid 的容器时,生成的 config.json
会引发问题:即使容器的组 ID(gid,从主机复制)对非 root 组具有许可权限,容器用户进程也无权使用该设备。这是因为容器用户不属于该主机组(例如,通过 additionalGids
)。
能够以非 root 用户身份运行使用设备的应用程序是正常且预期可行的,这样才能满足安全原则。因此,我们考虑了几种替代方案来填补 PodSec/CRI/OCI 目前支持的功能中的空白。
为解决该问题做了哪些工作?
您可能已经从问题定义中注意到,至少可以通过手动将设备 gid 添加到 supplementalGroups
来解决问题,或者在只有一个设备的情况下,将 runAsGroup
设置为设备的组 ID。然而,这存在问题,因为设备 gid 的值可能因集群中节点的发行版/版本而异。例如,对于 GPU,不同发行版和版本的以下命令会返回不同的 gid:
Fedora 33
$ ls -l /dev/dri/
total 0
drwxr-xr-x. 2 root root 80 19.10. 10:21 by-path
crw-rw----+ 1 root video 226, 0 19.10. 10:42 card0
crw-rw-rw-. 1 root render 226, 128 19.10. 10:21 renderD128
$ grep -e video -e render /etc/group
video:x:39:
render:x:997:
Ubuntu 20.04
$ ls -l /dev/dri/
total 0
drwxr-xr-x 2 root root 80 19.10. 17:36 by-path
crw-rw---- 1 root video 226, 0 19.10. 17:36 card0
crw-rw---- 1 root render 226, 128 19.10. 17:36 renderD128
$ grep -e video -e render /etc/group
video:x:44:
render:x:133:
在您的 securityContext
中选择哪个数字?此外,如果 runAsGroup
/runAsUser
值不能硬编码,因为它们在 Pod 准入期间通过外部安全策略自动分配怎么办?
与带有 fsGroup
的卷不同,设备没有 deviceGroup
/deviceUser
的官方概念,CRI 运行时(或 kubelet)无法使用。我们考虑使用设备插件设置的容器注解(例如,io.kubernetes.cri.hostDeviceSupplementalGroup/
)来获取自定义 OCI config.json
的 uid/gid 值。这将需要更改所有现有的设备插件,这并不理想。
相反,我们更倾向于一种对最终用户**无缝**的解决方案,无需设备插件供应商参与。所选择的方法是重用 config.json
中用于设备的 runAsUser
和 runAsGroup
值。
{
"type": "c",
"path": "/dev/foo",
"major": 123,
"minor": 4,
"fileMode": 438,
"uid": <runAsUser>,
"gid": <runAsGroup>
},
使用 runc
OCI 运行时(在非 rootless 模式下),设备在容器命名空间中创建(mknod(2)
),并使用 chmod(2)
将所有权更改为 runAsUser
/runAsGroup
。
说明
无根模式和设备不受支持。runAsUser
/runAsGroup
,例如,容器中的 USER
设置目前被忽略。虽然“有问题的”部署(即非 root securityContext
+ 设备)可能不存在,但为了确保没有任何部署中断,我们在 containerd 和 CRI-O 中都添加了一个选择性配置条目来启用新行为。以下配置
device_ownership_from_security_context (布尔值)
默认为 false
,必须启用才能使用该功能。
修复后查看使用设备的非 root 容器
为了演示新行为,我们以使用硬件加速器、Kubernetes CPU 管理器和 HugePages 的数据平面开发工具包 (DPDK) 应用程序为例。集群运行 containerd,配置如下:
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = true
或 CRI-O 配置如下:
[crio.runtime]
device_ownership_from_security_context = true
并使用以下 YAML 运行 DPDK 的 crypto-perf 测试工具的 Guaranteed
QoS Class Pod:
...
metadata:
name: qat-dpdk
spec:
securityContext:
runAsUser: 1000
runAsGroup: 2000
fsGroup: 3000
containers:
- name: crypto-perf
image: intel/crypto-perf:devel
...
resources:
requests:
cpu: "3"
memory: "128Mi"
qat.intel.com/generic: '4'
hugepages-2Mi: "128Mi"
limits:
cpu: "3"
memory: "128Mi"
qat.intel.com/generic: '4'
hugepages-2Mi: "128Mi"
...
要验证结果,请检查容器运行的用户和组 ID:
$ kubectl exec -it qat-dpdk -c crypto-perf -- id
它们被设置为非零值,符合预期
uid=1000 gid=2000 groups=2000,3000
接下来,检查设备节点权限 (qat.intel.com/generic
暴露 /dev/vfio/
设备) 是否可供 runAsUser
/runAsGroup
访问。
$ kubectl exec -it qat-dpdk -c crypto-perf -- ls -la /dev/vfio
total 0
drwxr-xr-x 2 root root 140 Sep 7 10:55 .
drwxr-xr-x 7 root root 380 Sep 7 10:55 ..
crw------- 1 1000 2000 241, 0 Sep 7 10:55 58
crw------- 1 1000 2000 241, 2 Sep 7 10:55 60
crw------- 1 1000 2000 241, 10 Sep 7 10:55 68
crw------- 1 1000 2000 241, 11 Sep 7 10:55 69
crw-rw-rw- 1 1000 2000 10, 196 Sep 7 10:55 vfio
最后,检查非 root 容器是否也允许创建 HugePages。
$ kubectl exec -it qat-dpdk -c crypto-perf -- ls -la /dev/hugepages/
fsGroup
为 runAsUser
提供可写 HugePages 的 emptyDir 挂载点。
total 0
drwxrwsr-x 2 root 3000 0 Sep 7 10:55 .
drwxr-xr-x 7 root root 380 Sep 7 10:55 ..
帮助我们测试并提供反馈!
这里描述的功能有望帮助提高集群安全性和设备权限的可配置性。为了允许非 root 容器使用设备,集群管理员需要通过设置 device_ownership_from_security_context = true
来选择启用该功能。要使其成为默认设置,请测试并提供您的反馈(通过 SIG-Node 会议或问题)!该标志已在 CRI-O v1.22 版本中提供,并已排队等待 containerd v1.6。
还需要更多工作才能**正确**支持它。已知它与 runc
配合使用,但它也需要与其他 OCI 运行时(如果适用)一起使用。例如,Kata Containers 支持设备直通,并允许将设备提供给虚拟机沙箱中的容器。
此外,支持用户命名空间和设备带来了额外的挑战。这个问题仍然悬而未决,需要更多的集思广益。
最后,需要了解 runAsUser
/runAsGroup
是否足够,或者 PodSpec/CRI v2 中是否需要类似于 fsGroups
的设备特定设置。
感谢
我感谢 Mike Brown(IBM,containerd)、Peter Hunt(Redhat,CRI-O)和 Alexander Kanevskiy(Intel)提供的所有反馈和愉快的交流。