Runc Namespace 隔离流程分析
Lapin Gris Lv3

在开始之前,我们先介绍一下 runc 的定位和职责,

先介绍,组成容器的三大技术,

  • namespace, 内核提供的 namespace 隔离,用于让进程互相感知不到对方。
  • cgroup,用于资源限制,防止进程过度使用资源,影响其他进程甚至主机的正常运行
  • rootfs,容器的根文件系统,每个容器使用自己的一套 rootfs,无需共享主机 rootfs,实现 rootfs 隔离。

组成容器的三大技术是由内核提供的底层能力。在上次 kubernetes 编排容器时,和内核打交道的组件并不是我们熟知的 kubelet。而是节点上的容器运行时,

  • containerd
  • runc

二者职责有分工,其中 containerd 是守护进程,负责从容器镜像提取出容器 rootfs ,并从 CRI 接口中拿到容器配置,用于生成描述容器的 OCI SPEC。 runc 会根据传来的 rootfs 和配置创建出容器进程。

OCI SPEC 对 namespace 的描述

OCI SPEC 是 一个 json 文件,它完整的描述了一个容器的所有配置。在 spec 中,在 .linux.namespaces 层级中描述容器需要对哪些 namespace 进行隔离。

例,当前容器对 pid、network、ipc、uts、mount、cgroup 这 6 个 namespace 进行隔离。

# cat config.json | jq .linux.namespaces
[
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
},
{
"type": "cgroup"
}
]

Linux Namespace 隔离

linux 系统通过 fork 来创建子进程,子进程会继承会进程的配置,其中就包括 namespace。在容器场景,我们父进程就是 runc cli,共享主机的 namespace。内核提供了 unshare 系统调用来让子进程进入一个全新的 namespace,不再共享父进程的 namespace,这就是容器实现 namespace 隔离的系统调用。

介绍下C 库使用示例, 其中 flags 参数传递需要 unshare 的 namespace。

#define _GNU_SOURCE
#include <sched.h>

int unshare(int flags);

// 需要隔离 pid/net/mount namespace, flags 通过或运算指定如下参数
CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS

隔离时序图

image.png

ENV 作用

在 runc 中,会通过环境变量在父子进程间传递配置,对于ns 隔离这部分重要的环境变量有三个,

其中 INITPIPE 用于 runc 与 PARENT 之间传递配置和回传信息,属于进程间通信。

# _LIBCONTAINER_INITPIPE 用于和 runc 通信, 读取 bootstrap data 和 发送 grandchild's PID
# _LIBCONTAINER_LOGPIPE 用于导出子进程写的日志的管道, 用于调试
# _LIBCONTAINER_LOGLEVEL 日志等级,用于调试

为什么不在一个进程中完成所有 namesapce 隔离?

因为 linux namespace 的限制,导致我们在进行 namespace 隔离的时候,必须fork 两次,即parent → child → grandchild .

第一次 fork,是因为如果直接 unshare ns, 会导致原有 ns 里面的操作权限都被 drop 掉。(如,隔离 user ns 还需要执行 gid/uid mapping, 这时候就没有权限操作了)。因此我们先 fork 一次,父进程保留权限,在子进程 unshare 后虽然丢失权限,但是可以通过父子进程通信通知父进程(此处是通过管道),让父进程替我们操作。

第二次 fork,是因为 pid namespace, pid 命名空间仅适用于子进程。我们 child 进程 unshare pid ns对自己是不生效的,child 自己的 pid ns 不会改变,所以需要 fork 一次,让子进程继承配置。这就是第二次 fork 原因。

为什么 user ns 隔离和其他 ns 分开?

因为潜在内核 bug,将所有 ns 一起 unshare 隔离,可能导致命名空间对象的归属权错误,因此采用了保守方案,分开 unshare。