3. 容器运行时基础:容器运行时架构与进程隔离机制
容器运行时是容器化技术的核心执行层,负责管理容器的创建、运行和销毁。理解其架构与隔离机制,是使用 eBPF 进行容器镜像异常定位的前提。本章将深入剖析 runc/containerd 的协作架构,以及 Namespace/Cgroup 如何实现进程隔离与资源管控。
3.1 容器运行时架构:runc 与 containerd
现代容器运行时采用分层架构,将高层编排逻辑与底层执行引擎解耦。以 Kubernetes 和 Docker 生态为例,containerd 作为行业标准的高层运行时,负责镜像管理、API 调用和容器生命周期管理;runc 作为底层运行时,直接与内核交互,通过 Namespace 和 Cgroup 创建容器进程。
关键概念: containerd 是 CRI(Container Runtime Interface)的标准实现,runc 是 OCI(Open Container Initiative)运行时规范的标准实现。两者通过 shim 进程桥接。
下图展示了 containerd 与 runc 在容器创建过程中的协作关系:
eBPF 观测点: 在 containerd-shim 与 runc 之间,eBPF 可以 hook
execve、clone 等系统调用,捕获容器进程的创建时刻,从而在进程启动瞬间注入观测逻辑,实现“零侵入”的异常定位。
3.2 容器进程隔离机制:Namespace
Namespace 是 Linux 内核提供的进程隔离机制,它让容器内的进程只能看到自己的“视图”,无法感知宿主机或其他容器的进程、网络、文件系统等资源。每个容器在创建时,runc 会为其申请一组独立的 Namespace。
| Namespace 类型 | 隔离资源 | 系统调用参数 | 容器异常定位关注点 |
|---|---|---|---|
| PID | 进程编号 | CLONE_NEWPID |
容器内 PID 1 进程行为;僵尸进程排查 |
| Network | 网络设备、IP、端口 | CLONE_NEWNET |
网络连接异常;端口冲突;DNS 解析 |
| Mount | 文件系统挂载点 | CLONE_NEWNS |
镜像层文件缺失;挂载泄露;只读文件系统 |
| UTS | 主机名与域名 | CLONE_NEWUTS |
主机名配置错误 |
| IPC | 进程间通信资源 | CLONE_NEWIPC |
共享内存异常;信号量泄漏 |
| User | 用户与用户组 ID | CLONE_NEWUSER |
权限提升;用户映射错误 |
| Cgroup | Cgroup 根目录视图 | CLONE_NEWCGROUP |
资源统计隔离;cgroup 泄漏 |
常见陷阱: 默认情况下,容器不会启用 User Namespace,这意味着容器内 root 用户与宿主机 root 用户拥有相同 UID 0。若容器进程被突破,可能直接获得宿主机 root 权限。eBPF 可以监控
setuid、capset 等系统调用,检测非预期的权限变更。
3.3 容器资源管控机制:Cgroup
Cgroup(Control Groups)是 Linux 内核的资源管控机制,用于限制、记录和隔离进程组对 CPU、内存、磁盘 I/O、网络等资源的使用。runc 在创建容器时,会为容器进程创建独立的 Cgroup 子系统,确保容器不会耗尽宿主机资源。
下图展示了 Cgroup 的层级结构与资源控制路径:
3.4 容器进程创建流程:runc 如何应用 Namespace 与 Cgroup
当 containerd 收到创建容器的请求后,runc 执行以下关键步骤,将 Namespace 和 Cgroup 应用到容器进程:
- 解析 OCI Bundle: runc 读取
config.json中的 Namespace 和 Cgroup 配置。 - 创建 Namespace: 通过
clone()系统调用,传入CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWNET | ...等标志,创建新的隔离环境。 - 配置 Cgroup: 在
/sys/fs/cgroup下创建容器对应的子目录,写入 CPU、内存、IO 等限制参数。 - 切换根文件系统: 使用
pivot_root()或chroot()将进程的根目录切换到镜像层。 - 执行容器进程: 通过
execve()运行容器入口程序(如/bin/sh或应用二进制)。
eBPF 介入时机: 在步骤 2(
clone)和步骤 5(execve)挂载 eBPF 程序,可以捕获容器进程的 PID、命令行参数、环境变量、Cgroup 路径等关键信息,为异常定位提供第一手数据。
3.5 从 eBPF 视角理解容器进程标识
在宿主机上,容器进程本质上是受限的普通进程。eBPF 程序可以通过以下方式识别进程是否属于容器:
| 标识方法 | 数据来源 | 适用场景 |
|---|---|---|
| Cgroup ID | task_struct->cgroups |
最可靠,直接关联容器 Cgroup 路径 |
| Namespace 编号 | /proc/<pid>/ns/* 的 inode 号 |
判断进程是否共享特定 Namespace |
| 容器运行时标记 | 进程环境变量(如 container_uuid) |
辅助识别,需解析 execve 参数 |
| 进程树关系 | 父进程为 containerd-shim 或 runc | 快速过滤,但存在误判风险 |
最佳实践: 在 eBPF 程序中,优先使用 Cgroup ID 作为容器标识。通过
bpf_get_current_cgroup_id() 辅助函数,可以在 kprobe/tracepoint 中直接获取当前进程的 Cgroup ID,再与已知的容器 Cgroup 路径进行匹配,实现精确的容器级观测。