本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
非 root 容器和设备
当用户想在 Linux 上部署使用加速器设备(通过 Kubernetes 设备插件)的容器时,Pod 的 securityContext
中与用户/组 ID 相关的安全设置会触发一个问题。在这篇博客文章中,我将讨论这个问题,并描述到目前为止为解决这个问题所做的工作。这并不是一个关于修复 k/k 问题 的长篇故事。
相反,这篇文章旨在提高人们对这个问题的认识,并强调重要的设备用例。这是必要的,因为 Kubernetes 正在开发新的相关功能,例如对用户命名空间的支持。
为什么非 root 容器不能使用设备以及为什么这很重要
在 Kubernetes 中运行容器的关键安全原则之一是最小权限原则。Pod/容器 securityContext
指定要设置的配置选项,例如,Linux 功能、MAC 策略以及用户/组 ID 值来实现这一点。
此外,集群管理员还获得 PodSecurityPolicy(已弃用)或 Pod 安全准入(alpha)等工具的支持,以强制执行集群中部署的 Pod 所需的安全设置。例如,这些设置可能要求容器必须是 runAsNonRoot
,或者禁止它们以 root 的组 ID 在 runAsGroup
或 supplementalGroups
中运行。
在 Kubernetes 中,kubelet 构建一个 Device
资源列表,这些资源将提供给容器(基于来自设备插件的输入),并且该列表包含在发送给 CRI 容器运行时的 CreateContainer CRI 消息中。每个 Device
包含少量信息:主机/容器设备路径和所需的设备 cgroups 权限。
Linux 容器配置的 OCI 运行时规范期望除了设备 cgroup 字段之外,还必须提供关于设备的更详细信息
{
"type": "<string>",
"path": "<string>",
"major": <int64>,
"minor": <int64>,
"fileMode": <uint32>,
"uid": <uint32>,
"gid": <uint32>
},
CRI 容器运行时(containerd、CRI-O)负责从主机获取每个 Device
的此信息。默认情况下,运行时会复制主机设备的用户和组 ID
uid
(uint32, 可选) - 容器命名空间中设备所有者的 ID。gid
(uint32, 可选) - 容器命名空间中设备组的 ID。
类似地,运行时根据 CRI 字段准备其他强制性的 config.json
部分,包括在 securityContext
中定义的那些:runAsUser
/runAsGroup
,它们通过以下方式成为 POSIX 平台用户结构的一部分
uid
(int, 必需) 指定容器命名空间中的用户 ID。gid
(int, 必需) 指定容器命名空间中的组 ID。additionalGids
(整数数组,可选) 指定要添加到进程的容器命名空间中的其他组 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
。
注意
不支持 Rootless 模式 和设备。runAsUser
/runAsGroup
,并且,例如,目前忽略容器中的 USER
设置。虽然很可能不存在“错误的”部署(即,非 root securityContext
+ 设备),但为了确保没有部署中断,在 containerd 和 CRI-O 中都添加了一个选择加入配置条目来启用新行为。以下
device_ownership_from_security_context (bool)
默认为 false
,必须启用才能使用该功能。
在修复后查看使用设备的非 root 容器
为了演示新行为,让我们使用数据平面开发套件 (DPDK) 应用程序,该应用程序使用硬件加速器、Kubernetes CPU 管理器和 HugePages 作为示例。集群运行 containerd,其中
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = true
或运行 CRI-O,其中
[crio.runtime]
device_ownership_from_security_context = true
以及运行 DPDK 加密性能测试实用程序的 Guaranteed
QoS 类 Pod,其 YAML 如下
...
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 支持设备直通,并允许它在 VM 沙箱中为容器提供设备。
此外,额外的挑战来自对用户名和设备的支持。这个问题仍然 开放,需要更多的集思广益。
最后,需要理解 runAsUser
/runAsGroup
是否足够,或者是否需要在 PodSpec/CRI v2 中使用类似于 fsGroups
的设备特定设置。
感谢
我感谢 Mike Brown(IBM,containerd)、Peter Hunt(Redhat,CRI-O)和 Alexander Kanevskiy(英特尔)提供了所有的反馈和良好的对话。