containerd 以非 root 用户运行完全指南:RootlessKit 用户命名空间配置与 ctr 客户端实战
2026/9/13 7:12:29 网站建设 项目流程

containerd 以非 root 用户运行完全指南:RootlessKit 用户命名空间配置与 ctr 客户端实战

【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd

导读:本文以 containerd 官方文档 docs/rootless.md 为核心,系统讲解如何借助 Linux user namespace(配合 RootlessKit 的 mount/network namespace)让 containerd 守护进程以非 root 用户身份运行,覆盖"一条命令的 Easy Way"与"逐参数拆解的 Hard Way"两条路径,并结合本仓库源码深入剖析 root、state、gRPC socket 地址、snapshotter 与 cgroup 等关键配置的底层原理。读完本文,你将能够在无特权用户环境下独立部署 rootless containerd,并用ctr在守护进程命名空间内完成镜像拉取与容器运行。

背景:为什么需要 Rootless containerd

在默认安装中,containerd 的守护进程需要 root 权限,因为它的默认路径都位于系统级目录:

  • 默认 unix socket:/run/containerd/containerd.sock
  • 默认状态目录(transient data):/run/containerd
  • 默认快照器:overlayfs(见 defaults/defaults_linux.go)

这些目录均非普通用户可写。containerd 提供的"非 root 运行"方案,核心思路是利用 Linux 的user_namespaces(7)特性:在用户命名空间中,普通用户可以被映射为命名空间内的"root"(UID 0),从而获得对该命名空间内资源的特权,但不会对宿主机拥有真实特权。

RootlessKit 是常用的辅助工具,它负责同时设置 user namespace、mount namespace,并可选用 network namespace。此外可以参考 rootlesscontaine.rs 了解更广泛的 rootless 容器生态背景。

在进入具体操作前,需要说明一个前提:rootless 模式下的守护进程与客户端都必须运行在同一套命名空间内,这是后续所有命令组织方式的根本原因。

"Easy way":借助 nerdctl 一键安装

对于绝大多数用户,官方文档推荐的最简单路径是使用containerd-rootless-setuptool.sh脚本,该脚本包含在 containerd/nerdctl 项目中(属于第三方配套工具,不在本仓库内)。

安装并验证 rootless containerd 只需要两步:

$ containerd-rootless-setuptool.sh install $ nerdctl run -d --restart=always --name nginx -p 8080:80 nginx:alpine
  • 第一步install会完成 RootlessKit 环境初始化、生成 rootless 专用的 containerd 配置(包括 user 目录下的 root/state/socket 路径)、并启动 rootless containerd。
  • 第二步直接通过nerdctl(rootless 版的 Docker CLI 兼容工具)运行容器,端口映射-p 8080:80由 RootlessKit 的网络栈(slirp4netns 等)提供。

该脚本本质上是对下文 "Hard way" 的自动化封装,因此理解 Hard way 中的每个参数,能帮助你排查 Easy way 遇到的问题。

"Hard way":手工搭建 Rootless 环境

如果你希望完全掌控每个细节(例如自定义网络驱动、额外的 copy-up 目录、特定的 config.toml),可以按下面的手工方式部署。

守护进程(Daemon)启动

$ rootlesskit --net=slirp4netns --copy-up=/etc --copy-up=/run \ --state-dir=/run/user/1001/rootlesskit-containerd \ sh -c "rm -f /run/containerd; exec containerd -c config.toml"

逐项拆解这条命令的参数含义:

  • --net=slirp4netns --copy-up=/etc:仅当你希望 unshare 网络命名空间时才需要。slirp4netns是 RootlessKit 支持的多种网络驱动之一,具体驱动的能力与限制可参考 RootlessKit 的 network 文档。
  • --copy-up=/DIR:在 mount namespace 内,于/DIR之上挂载一个可写的 tmpfs,并以符号链接的方式"复制"父命名空间中/DIR下的原有文件。这样普通用户就能在 mount namespace 内的/DIR下增删文件,而不会影响宿主机。典型部署至少需要--copy-up=/etc--copy-up=/run如果你的 containerd 插件配置涉及其他需要写权限的系统目录,需要追加更多--copy-up选项
  • rm -f /run/containerd:删除"被复制上来"的、指向父命名空间/run/containerd的符号链接(如果存在)。该链接指向的宿主目录对非 root 用户不可访问,删除它只是移除链接本身,宿主机上的真实/run/containerd目录不受任何影响
  • --state-dir:RootlessKit 的状态目录。如果未设置,会使用/tmp下的随机目录。RootlessKit 会把子进程 PID 写入该目录下的child_pid文件中——这个文件在后续客户端nsenter时要用到。
  • containerd -c config.toml:以指定配置文件启动 containerd 守护进程。rootless 场景下必须提供自定义配置,因为默认的 root/state/socket 路径普通用户无权使用。

自定义 config.toml:路径重定向是关键

文档给出的最小可运行配置如下:

version = 2 root = "/home/penguin/.local/share/containerd" state = "/run/user/1001/containerd" [grpc] address = "/run/user/1001/containerd/containerd.sock"

这里三个字段与守护进程的启动参数一一对应(参见 cmd/containerd/command/main.go 中--root--state--address三个 flag 的定义):

  • root:containerd 持久化数据(内容存储、元数据数据库等)的存放目录,必须位于用户可写的路径,典型选择是$HOME/.local/share/containerd
  • state:运行时瞬时数据(socket、挂载点等)目录,对应 rootless 场景应放到 user runtime 目录/run/user/<UID>/containerd
  • [grpc].address:gRPC 服务的 unix socket 地址。rootless 部署时应置于state目录之下(即/run/user/<UID>/containerd/containerd.sock),确保客户端与守护进程使用一致的地址。

从源码结构看,containerd 的配置解析逻辑允许rootstate通过命令行 flag 覆盖配置文件(见 cmd/containerd/command/main.go),配置文件只是最推荐的集中管理方式。另外需要留意 cmd/containerd/server/server.go 中的处理:对于未将守护进程放入 UserNS 的非标准 rootless 部署,containerd 会忽略文件权限问题,避免因权限检查而启动失败。

客户端(ctr)如何接入

关键约束:ctr这样的客户端程序也必须运行在守护进程所在的命名空间内,否则无法访问其 unix socket 与相关挂载。

$ nsenter -U --preserve-credentials -m -n -t $(cat /run/user/1001/rootlesskit-containerd/child_pid) $ export CONTAINERD_ADDRESS=/run/user/1001/containerd/containerd.sock $ export CONTAINERD_SNAPSHOTTER=native $ ctr images pull docker.io/library/ubuntu:latest $ ctr run -t --rm --fifo-dir /tmp/foo-fifo --cgroup "" docker.io/library/ubuntu:latest foo

逐步说明:

  1. nsenter -U --preserve-credentials -m -n -t <pid>:进入 RootlessKit 子进程的 user(-U)、mount(-m)、network(-n)命名空间。--preserve-credentials保证 nsenter 不会尝试在目标命名空间内重新映射凭据。PID 来自--state-dir下 RootlessKit 写入的child_pid文件。
  2. export CONTAINERD_ADDRESS=...:告诉ctr去连接哪个 gRPC socket,值必须与 config.toml 中[grpc].address一致。
  3. export CONTAINERD_SNAPSHOTTER=native:显式选择native快照器。之所以不依赖默认的overlayfs,是因为 overlayfs 在 user namespace 内有内核版本限制(详见下文"快照器与内核版本")。
  4. ctr images pull:拉取镜像,验证守护进程连通性。
  5. ctr run -t --rm --fifo-dir /tmp/foo-fifo --cgroup "" ...:以 TTY 交互(-t)、退出即删(--rm)方式运行容器。其中:
    • --fifo-dir /tmp/foo-fifo指定客户端 I/O FIFO 的存放目录(对应ctrfifo-dirflag,见 cmd/ctr/commands/run/run.go),rootless 下需选用户可写路径;
    • --cgroup ""显式传空字符串以禁用 cgroup——这是 rootless 环境的常见需求,ctr对该 flag 的语义说明是"要禁用 cgroup,请显式设为空字符串"。

快照器与内核版本限制

这是 rootless 部署中最容易踩坑的点,原文档明确给出了两条约束:

  1. overlayfs快照器在 user namespace 内,于内核 5.11 之前不工作,唯一的例外是 Ubuntu 与 Debian 发行版的内核(它们对 overlayfs 在 user namespace 中的应用做了额外支持)。
  2. 如果内核版本>= 4.18,可以改用fuse-overlayfssnapshotter 作为替代,它以 FUSE 方式实现 overlay 语义,不依赖内核 overlayfs 的 user namespace 支持。

实践建议:

  • 先确认内核版本(uname -r),再决定 snapshotter 选型;
  • 低版本内核且无法更换时,使用native快照器是最稳妥的选择(即上文CONTAINERD_SNAPSHOTTER=native的原因);
  • 也可在本仓库 docs/snapshotters/README.md 查看各类快照器的整体说明。

cgroup 的启用前提

rootless 模式下要启用 cgroup,需要同时满足cgroup v2systemd两个前提,此时可以用ctr的 systemd cgroup 参数指定 cgroup 路径,例如:

$ ctr run --cgroup "user.slice:foo:bar" --runc-systemd-cgroup ...

其中user.slice:foo:bar是 systemd 风格的 cgroup 路径表示,--runc-systemd-cgroup让 runc 通过 systemd 来管理 cgroup(而不是直接写 cgroupfs)。相关细节可参考 runc 的 cgroup-v2 文档。

如果环境不满足上述前提,则保持--cgroup ""(禁用 cgroup)即可正常运行 rootless 容器——这也是本文上文示例采用的做法。

与源码实现相关的补充事实

  • 默认路径的迁移点:containerd 的默认 socket、状态目录与快照器均定义在 defaults/defaults_linux.go,rootless 部署本质上就是把这三个默认值"搬迁"到用户可写目录,并用 config.toml 固化。
  • 启动参数覆盖机制--root--state--address均可作为命令行 flag 覆盖配置(见 cmd/containerd/command/main.go),理解这一点有助于排查"配置写了但没生效"的问题。
  • OCI 层面对 rootless 的适配:在 OCI spec 生成过程中,containerd 对 rootless 容器使用设备访问权限有专门处理,相关注释见 pkg/oci/utils_unix.go,这保证了 rootless 容器对部分设备节点仍可正常访问。

总结与排障要点

关注点结论
部署方式Easy way 用 nerdctl 脚本;Hard way 用 RootlessKit 手工搭建
路径三要素root、state、gRPC address 必须全部位于用户可写目录
命名空间一致性守护进程与客户端(ctr)必须在同一 user/mount/network 命名空间
快照器内核 < 5.11 时 overlayfs 不可用(Ubuntu/Debian 除外),可用 native 或 fuse-overlayfs(>= 4.18)
cgroup需要 cgroup v2 + systemd;否则显式传--cgroup ""禁用
常见失败点child_pid文件路径与--state-dir不一致、socket 地址不一致、/run/containerd符号链接未清理

排障时建议按顺序核对:RootlessKit 是否成功启动(--state-dir下是否有child_pid)→ 配置三路径是否可写 →CONTAINERD_ADDRESS与配置是否一致 → snapshotter 是否符合内核版本 → 是否涉及 cgroup 且环境满足 v2+systemd。掌握了这些约束,rootless containerd 的部署与问题定位就会变得清晰可控。

【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询