Kubernetes v1.36:用户命名空间终于正式可用

经过数年的开发,Kubernetes 中的用户命名空间(User Namespaces)支持已随着 v1.36 发布进入正式发布(GA)阶段。 这是一个仅适用于 Linux 的特性。

对于从事底层容器运行时(Container Runtimes)和 rootless 技术的我们来说, 这是一个期待已久的里程碑。 我们终于走到了可以将 rootless 安全隔离用于 Kubernetes 工作负载的阶段。

这一特性还开启了一种关键模式:让工作负载在拥有特权的同时,依然被限制在用户命名空间内。 当设置 hostUsers: false 时,CAP_NET_ADMIN 这类权能(capabilities)会变成 被命名空间化(namespaced) 的权能, 这意味着它们只会授予容器本地资源的管理能力,而不会影响主机。 这实际上开启了此前只有运行完全特权容器(fully privileged container)才能实现的新用例。

UID 0 的问题

在容器内以 root 身份运行的进程,从内核视角看,在主机上同样是 root。 如果攻击者成功逃逸出容器,无论是利用内核漏洞,还是借助配置错误的挂载(misconfigured mount), 他们都会在主机上获得 root 权限。

虽然运行容器时已经有许多安全防护措施,但这些措施并不会改变进程的底层身份, 它依然保留着 root 的某些“部分能力”。

核心机制:ID-mapped mounts

通往 GA 的道路并不只是 Kubernetes API 的演进,更关键的是让内核真正为我们所用。 在早期阶段,最大的阻碍之一是卷的所有权问题。 如果你把容器映射到较高的 UID 范围,Kubelet 就必须递归地对挂载卷中的每个文件执行 chown, 这样容器才能对这些文件进行读写。 对于大型卷来说,这一操作的成本高得惊人,足以摧毁启动性能。

实现这一目标的关键,是 ID-mapped mounts(在 Linux 5.12 中引入,并在后续版本中持续完善)。 借助这一机制,内核可以在挂载时重映射文件所有权,而不必改写磁盘上的实际所有权信息。

当一个卷被挂载到启用了用户命名空间的 Pod 中时,内核会透明地转换 UID(user ids)和 GID(group ids)。 对容器来说,这些文件看起来像是由 UID 0 拥有。 而在磁盘上,文件所有权完全不会变化,因此不需要执行 chown。 这是一个 O(1) 操作,既即时又高效。

在 Kubernetes v1.36 中使用它

使用用户命名空间非常直接:你只需要在 Pod spec 中设置 hostUsers: false。 无需修改容器镜像,也不需要复杂配置。 它仍然使用 Alpha 阶段引入的同一套接口。 在 Pod(或 PodTemplate)的 spec 中,你可以显式选择不使用主机用户命名空间:

apiVersion: v1
kind: Pod
metadata:
  name: isolated-workload
spec:
  hostUsers: false
  containers:
  - name: app
    image: fedora:42
    securityContext:
      runAsUser: 0

如果你想进一步了解用户命名空间在实践中的工作方式,以及它如何缓解被评为 HIGH 的 CVE, 请参阅此前的博客文章: User Namespaces alphaUser Namespaces stateful pods in alphaUser Namespaces beta,以及 User Namespaces enabled by default

参与其中

如果你对用户命名空间感兴趣,或希望参与贡献,这里有一些有用的链接:

致谢

这一特性历经多年才走到今天:第一份 KEP 在 10 年前由其他贡献者提出, 而我们在过去 6 年里一直积极推动它向前发展。 我们感谢所有在 SIG Node、容器运行时以及 Linux 内核领域参与贡献的人。 同时,也特别感谢那些在多个 Alpha 和 Beta 周期中帮助打磨设计的评审者与早期采用者。