用户命名空间

特性状态: Kubernetes v1.30 [beta]

此页面解释了如何在 Kubernetes Pod 中使用用户命名空间。用户命名空间将容器内运行的用户与主机上的用户隔离开来。

在容器中作为 root 用户运行的进程可以在主机上作为不同的(非 root)用户运行;换句话说,该进程对于用户命名空间内的操作拥有完全权限,但对于命名空间外的操作则是非特权的。

你可以使用此功能来减少受损容器对主机或同一节点中其他 Pod 造成的损害。有几处安全漏洞被评为高危严重,在用户命名空间激活时无法利用。预计用户命名空间也将缓解未来的某些漏洞。

准备工作

这是一个仅限 Linux 的功能,需要 Linux 在所使用的文件系统上支持 idmap 挂载。这意味着:

  • 在节点上,你用于 /var/lib/kubelet/pods/ 的文件系统,或你为此配置的自定义目录,需要 idmap 挂载支持。
  • Pod 卷中使用的所有文件系统都必须支持 idmap 挂载。

实际上,这意味着你需要至少 Linux 6.3,因为 tmpfs 在该版本中开始支持 idmap 挂载。这通常是必需的,因为许多 Kubernetes 功能都使用 tmpfs(默认挂载的服务账户令牌使用 tmpfs,Secrets 使用 tmpfs 等)。

Linux 6.3 中一些支持 idmap 挂载的流行文件系统有:btrfs、ext4、xfs、fat、tmpfs、overlayfs。

此外,容器运行时及其底层 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持:

  • crun 版本 1.9 或更高(建议版本 1.13+)。
  • runc 版本 1.2 或更高

要在 Kubernetes 中使用用户命名空间,你还需要一个 CRI 容器运行时来使用 Kubernetes Pod 的此功能:

  • containerd:2.0(及更高版本)支持容器的用户命名空间。
  • CRI-O:1.25(及更高版本)支持容器的用户命名空间。

你可以在 GitHub 上的一个议题中查看 cri-dockerd 中用户命名空间支持的状态。

介绍

用户命名空间是 Linux 的一项功能,它允许将容器中的用户映射到主机中的不同用户。此外,授予 Pod 在用户命名空间中的能力仅在该命名空间中有效,在命名空间之外无效。

Pod 可以通过将 pod.spec.hostUsers 字段设置为 false 来选择使用用户命名空间。

kubelet 将选择 Pod 映射到的主机 UID/GID,并且将以确保同一节点上的两个 Pod 不使用相同映射的方式进行选择。

pod.spec 中的 runAsUserrunAsGroupfsGroup 等字段始终指容器内部的用户。这些用户将用于卷挂载(在 pod.spec.volumes 中指定),因此主机 UID/GID 对 Pod 可以挂载的卷的写入/读取没有任何影响。换句话说,Pod 挂载的卷中创建/读取的 inode 将与 Pod 不使用用户命名空间时相同。

通过这种方式,Pod 可以轻松启用和禁用用户命名空间(而不影响其卷的文件所有权),并且还可以通过仅设置容器内的适当用户(RunAsUserRunAsGroupfsGroup 等)来与不带用户命名空间的 Pod 共享卷。这适用于 Pod 可以挂载的任何卷,包括 hostPath(如果 Pod 允许挂载 hostPath 卷)。

默认情况下,启用此功能时,有效的 UID/GID 范围为 0-65535。这适用于文件和进程(runAsUserrunAsGroup 等)。

使用此范围之外的 UID/GID 的文件将被视为属于溢出 ID,通常为 65534(在 /proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid 中配置)。但是,即使以 65534 用户/组身份运行,也无法修改这些文件。

如果使用配置旋钮扩展了 0-65535 范围,则上述限制适用于扩展范围。

大多数需要以 root 身份运行但未访问其他主机命名空间或资源的应用程序,如果激活用户命名空间,则无需任何更改即可继续正常运行。

理解 Pod 的用户命名空间

默认配置的多个容器运行时(如 Docker Engine、containerd、CRI-O)使用 Linux 命名空间进行隔离。其他技术也存在,并且可以与这些运行时一起使用(例如 Kata Containers 使用虚拟机而不是 Linux 命名空间)。本页适用于使用 Linux 命名空间进行隔离的容器运行时。

创建 Pod 时,默认会使用多个新的命名空间进行隔离:网络命名空间用于隔离容器的网络,PID 命名空间用于隔离进程视图等。如果使用用户命名空间,它将隔离容器中的用户与节点中的用户。

这意味着容器可以作为 root 运行并映射到主机上的非 root 用户。在容器内部,进程会认为它以 root 身份运行(因此 aptyum 等工具可以正常工作),而实际上该进程在主机上没有特权。你可以通过在主机上执行 ps aux 来检查容器进程正在哪个用户下运行来验证这一点。ps 显示的用户与你在容器内部执行 id 命令时看到的用户不同。

这种抽象限制了可能发生的情况,例如,如果容器设法逃逸到主机。鉴于容器在主机上作为非特权用户运行,它能对主机做的事情是有限的。

此外,由于每个 Pod 上的用户将映射到主机上不同的、不重叠的用户,它们对其他 Pod 也能做的事情也是有限的。

授予 Pod 的能力也仅限于 Pod 用户命名空间,在命名空间之外大多无效,有些甚至完全失效。以下是两个示例:

  • CAP_SYS_MODULE 如果授予使用用户命名空间的 Pod,则没有任何效果,Pod 无法加载内核模块。
  • CAP_SYS_ADMIN 仅限于 Pod 的用户命名空间,在命名空间之外无效。

如果不使用用户命名空间,以 root 身份运行的容器,在容器逃逸的情况下,在节点上具有 root 权限。如果授予容器某些能力,这些能力在主机上也有效。当我们使用用户命名空间时,这些情况都不成立。

如果你想了解更多关于使用用户命名空间时更改的详细信息,请参阅 man 7 user_namespaces

设置节点以支持用户命名空间

默认情况下,kubelet 为 Pod 分配高于 0-65535 范围的 UID/GID,因为假定主机的文件夹和进程使用该范围内的 UID/GID,这对于大多数 Linux 发行版来说是标准做法。这种方法可以防止主机的 UID/GID 与 Pod 的 UID/GID 发生重叠。

避免重叠对于缓解诸如 CVE-2021-25741 之类的漏洞影响很重要,这些漏洞可能导致 Pod 读取主机上的任意文件。如果 Pod 和主机的 UID/GID 不重叠,Pod 能够做的事情将受到限制:Pod 的 UID/GID 不会与主机的文件的所有者/组匹配。

kubelet 可以为 Pod 使用自定义的用户 ID 和组 ID 范围。要配置自定义范围,节点需要:

  • 系统中的一个用户 kubelet (此处不能使用其他用户名)
  • 安装了二进制文件 getsubidsshadow-utils 的一部分)并在 kubelet 二进制文件的 PATH 中。
  • kubelet 用户配置的附属 UID/GID(参见man 5 subuidman 5 subgid)。

此设置仅收集 UID/GID 范围配置,不更改执行 kubelet 的用户。

你必须遵循为 kubelet 用户分配的附属 ID 范围的一些约束条件:

  • 附属用户 ID,即 Pod 的 UID 范围的起始 ID,必须是 65536 的倍数,并且必须大于或等于 65536。换句话说,你不能将范围 0-65535 中的任何 ID 用于 Pod;kubelet 强制执行此限制,以防止意外创建不安全的配置。

  • 附属 ID 计数必须是 65536 的倍数。

  • 附属 ID 计数必须至少为 65536 x <maxPods>,其中 <maxPods> 是节点上可以运行的最大 Pod 数量。

  • 你必须为用户 ID 和组 ID 分配相同的范围。其他用户是否具有与组 ID 范围不一致的用户 ID 范围无关紧要。

  • 所有分配的范围都不应与任何其他分配重叠。

  • 附属配置必须只有一行。换句话说,你不能有多个范围。

例如,你可以定义 /etc/subuid/etc/subgid,使它们都包含 kubelet 用户的以下条目:

# The format is
#   name:firstID:count of IDs
# where
# - firstID is 65536 (the minimum value possible)
# - count of IDs is 110 * 65536
#   (110 is the default limit for number of pods on the node)

kubelet:65536:7208960

每个 Pod 的 ID 计数

从 Kubernetes v1.33 开始,可以在KubeletConfiguration中设置每个 Pod 的 ID 计数。

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
userNamespaces:
  idsPerPod: 1048576

idsPerPod (uint32) 的值必须是 65536 的倍数。默认值为 65536。此值仅适用于 kubelet 启动后使用此 KubeletConfiguration 创建的容器。正在运行的容器不受此配置的影响。

在 Kubernetes v1.33 之前,每个 Pod 的 ID 计数被硬编码为 65536。

与 Pod 安全准入检查集成

特性状态: Kubernetes v1.29 [alpha]

对于启用用户命名空间的 Linux Pod,Kubernetes 以受控方式放宽Pod 安全标准的应用。此行为可以通过功能门 UserNamespacesPodSecurityStandards 控制,允许最终用户提前选择启用。管理员必须确保,如果使用此功能门,集群内的所有节点都已启用用户命名空间。

如果启用了相关的功能门并创建了一个使用用户命名空间的 Pod,以下字段将不会受到约束,即使在强制执行“基线”或“受限”Pod 安全标准的上下文中也是如此。此行为不构成安全问题,因为具有用户命名空间的 Pod 内的 root 实际上指的是容器内的用户,该用户从不映射到主机上的特权用户。以下是这些情况下 Pod 检查的字段列表:

  • spec.securityContext.runAsNonRoot
  • spec.containers[*].securityContext.runAsNonRoot
  • spec.initContainers[*].securityContext.runAsNonRoot
  • spec.ephemeralContainers[*].securityContext.runAsNonRoot
  • spec.securityContext.runAsUser
  • spec.containers[*].securityContext.runAsUser
  • spec.initContainers[*].securityContext.runAsUser
  • spec.ephemeralContainers[*].securityContext.runAsUser

限制

当 Pod 使用用户命名空间时,不允许使用其他主机命名空间。特别是,如果将 hostUsers: false 设置为 false,则不允许设置以下任何项:

  • hostNetwork: true
  • hostIPC: true
  • hostPID: true

任何容器都不能使用 volumeDevices (原始块卷,如 /dev/sda)。这包括 Pod Spec 中的所有容器数组:

  • containers
  • initContainers
  • ephemeralContainers

指标和可观测性

kubelet 导出了两个特定于用户命名空间的 prometheus 指标:

  • started_user_namespaced_pods_total:一个计数器,用于跟踪尝试创建的用户命名空间 Pod 的数量。
  • started_user_namespaced_pods_errors_total:一个计数器,用于跟踪创建用户命名空间 Pod 时发生的错误数量。

下一步

本页面上的项目引用了提供 Kubernetes 所需功能的第三方产品或项目。Kubernetes 项目作者不负责这些第三方产品或项目。有关更多详细信息,请参见 CNCF 网站指南

在提议添加额外第三方链接的更改之前,你应该阅读内容指南

最后修改于 2025 年 7 月 1 日下午 3:15 PST: docs: 在 1.34 中添加 userns 更改 (f3a6e8e88d)