小白问:K8S中的容器runtime,是不是类似于ghost装机里的那个静态文件,而runtime相当于Windows或者Linux系统,跑起来了?

一、类比对与错

类比

准确性

修正点

镜像 ≈ Ghost 静态文件

非常准确

镜像就是一组静态的 layer(rootfs 分层)+ JSON 元数据,只读,类似 Ghost 的 .gho 或 .iso

Runtime ≈ Windows/Linux 系统

方向对,但精度不够

Runtime 不是完整的操作系统,它没有自己的内核。它更像是一个**"借用宿主机内核,为单个应用进程快速搭建隔离环境"的启动器**。如果非要比,它像 Windows 的 smss.exe + csrss.exe(会话管理器)+ 注册表挂载器,而不是完整的 Windows

一句话纠偏:容器 Runtime 是**"内核隔离技术的编排器",不是操作系统本身。容器里的进程用的就是宿主机的内核**,只是被 Namespace 和 Cgroup 骗了,以为自己在一个独立机器里。

二、容器 Runtime 的技术分层

K8S 里的 Runtime 不是单一组件,而是三层架构

K8S 视角
    │
    ▼
kubelet ──► CRI (Container Runtime Interface)
              │
              ├──► containerd / CRI-O  (High-Level Runtime,CRI 实现)
              │         │
              │         ├──► shim v2 (per-Pod 进程,管理容器生命周期)
              │         │
              │         └──► runc / crun / youki  (Low-Level OCI Runtime)
              │                   │
              │                   ▼
              │              Linux Kernel
              │         (Namespace + Cgroup + Seccomp + Capabilities)
              │
              └──► (已废弃) dockershim

2.1 镜像层(静态):OCI Image Spec

镜像确实像是(但不是) Ghost 静态文件:

  • Rootfs:分层存储(Layer),每层是一个 tar.gz,OverlayFS 合并后形成完整文件系统
  • Config:JSON 描述,包含启动命令(Entrypoint/Cmd)、环境变量、端口声明、作者信息
  • Manifest:索引层,支持多架构(amd64/arm64/loongarch)

# 镜像本质就是解压到磁盘上的静态文件树
$ skopeo inspect docker://harbor.bank.internal/app:v1.2.3
# 输出的是 JSON 元数据,没有进程,没有内核,就是"死的"

2.2 High-Level Runtime:containerd(K8S 实际对接的"管家")

containerd 是 K8S 通过 CRI 直接对话的组件,它负责:

职责

技术动作

镜像管理

Pull/Push/Unpack 镜像到 /var/lib/containerd;管理快照(snapshot,即 OverlayFS 的 upperdir/lowerdir)

Pod 生命周期

创建/停止/删除 Pod Sandbox(即 pause 容器)

容器生命周期

调用 Low-Level Runtime 启动业务容器

CRI 接口实现

实现 gRPC 接口,供 kubelet 调用

关键点:containerd 本身不直接执行 clone()/unshare() 系统调用,它更像一个**"项目经理"**,把具体的"脏活"交给 runc。

2.3 Low-Level Runtime:runc(真正的"隔离环境启动器")

runc 是 OCI Runtime Spec 的参考实现,它做最底层、最内核的事情:

runc create mycontainer
    │
    ├──► 1. 根据 OCI Bundle(config.json + rootfs/)解析配置
    │
    ├──► 2. 调用 Linux 系统调用:
    │         unshare(CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWNET|...)  → 创建 Namespace
    │         cgroup v2 写入 /sys/fs/cgroup/...                   → 资源限制
    │         seccomp 加载 BPF 过滤器                             → 系统调用拦截
    │         capabilities 计算 Bounding Set                       → 权限裁剪
    │         selinux/apparmor 标签应用                           → 强制访问控制
    │
    ├──► 3. pivot_root() 或 chroot() 切换根文件系统到镜像的 rootfs
    │
    └──► 4. execve() 执行用户指定的 Entrypoint(如 /app/server)

类比的"跑起来"的瞬间——runc 不是启动一个操作系统,而是"骗"一个普通进程,让它以为自己在一个新机器里:

  • PID Namespace:进程 PID 变成 1(容器内看),宿主机上实际是个普通 PID(如 12345)
  • Mount Namespace:看到的 / 是镜像的 rootfs,宿主机的 / 被隐藏
  • Network Namespace:拥有自己的 eth0(veth pair 的一端)、路由表、iptables
  • UTS Namespace:拥有自己的 hostname
  • IPC Namespace:拥有自己的共享内存段
  • User Namespace:root 用户可能是宿主机上的 UID 100000(映射)

三、Namespace + Cgroup:Runtime 的"魔法"核心

3.1 Namespace(视图隔离)

Namespace 是 Linux 内核 2.6.24+ 引入的机制,它让一组进程看到资源的子集

Namespace

隔离内容

容器内看到的假象

PID

进程 ID

容器内 PID 1 是应用进程,宿主机上它是普通进程

Network

网络设备、端口、路由

eth0 是虚拟的,端口 80 绑定不影响宿主机 80

Mount

挂载点

容器内 / 是镜像 rootfs,宿主机文件系统被遮住

IPC

信号量、消息队列、共享内存

容器内 ipcs 看不到宿主机 IPC

UTS

Hostname/NIS 域名

hostname 返回容器名

User

UID/GID

容器内 root 可能是宿主机 UID 100000

Cgroup

控制组根目录

容器内只能看到自己的 cgroup 树

Time (Linux 5.6+)

系统时间

容器内可独立调整时间(较少用)

3.2 Cgroup(资源限制)

Namespace 解决"看到什么",Cgroup 解决"能用多少":

  • Cgroup v1cpumemoryblkiopidsdevices 等子系统分离,K8S 的 cpu.sharesmemory.limit_in_bytes 即写在此
  • Cgroup v2:统一层级,eBPF 友好,新集群建议启用

# 容器内存限制的实际落点
/sys/fs/cgroup/memory/kubepods.slice/.../docker-xxx.scope/memory.limit_in_bytes

3.3 OverlayFS:镜像"静态"转"动态可写"的关键

镜像层是只读的(lowerdir),Runtime 启动容器时,在 upperdir 创建可写层:

OverlayFS 挂载结构:
lowerdir=/var/lib/containerd/.../layer1:/var/lib/containerd/.../layer2  (镜像层,只读)
upperdir=/var/lib/containerd/.../tmpmounts/xxx  (容器可写层,写时复制)
workdir=/var/lib/containerd/.../work
merged=/run/containerd/.../rootfs  (容器内看到的最终文件系统)

  • 容器内删除文件:在 upperdir 创建 whiteout 文件(标记删除),不碰 lowerdir
  • 容器内修改文件:copy-up 到 upperdir 再修改,lowerdir 保持纯净
  • 容器内新增文件:直接写在 upperdir

这是 Ghost 类比最精妙的地方:Ghost 还原后,用户可以在上面装软件、改配置;容器镜像启动后,OverlayFS 给用户一层"可写覆盖层",底层镜像不动。

四、K8S 中 Runtime 的演进与现状

4.1 从 Docker 到 containerd

时期

架构

问题

K8S 1.5 以前

kubelet → Docker → containerd → runc

dockershim 维护负担重,Docker 不是为 K8S 设计的

K8S 1.24+

kubelet → CRI → containerd → runc

dockershim 正式移除,containerd 成为事实标准

未来

kubelet → CRI → containerd → youki/crun

纯 Rust/C 写的 runtime,启动速度更快,内存占用更低

4.2 CRI 接口:kubelet 与 Runtime 的契约

kubelet 不直接调用 runc,它通过 gRPC 调用 CRI:

// CRI 核心接口(简化)
service RuntimeService {
    rpc RunPodSandbox(PodSandboxConfig) returns (PodSandbox);
    rpc CreateContainer(ContainerConfig) returns (Container);
    rpc StartContainer(ContainerID) returns (Empty);
    rpc StopContainer(ContainerID, Timeout) returns (Empty);
    rpc RemoveContainer(ContainerID) returns (Empty);
    rpc ListContainers(Filter) returns (ContainerList);
}

为什么要有 CRI? 让 kubelet 与具体 Runtime 解耦。今天用 containerd,明天可以换 CRI-O,只要实现同一套 gRPC 接口。

五、Ghost的类比,如何说得更精确

如果要向团队或领导解释,可以用这个三层递进类比

第一层(给用户/业务方)
"镜像就像 Ghost 系统备份,是静态的;Runtime 是把它还原并启动成一台'虚拟小电脑'的软件。"
—— 这里对方可能会误以为容器里有完整操作系统,需要补一句:"但它没有自己的内核,用的是宿主机的内核,只是被隔离了。"

第二层(给初级工程师)
"镜像是只读的分层文件系统;Runtime 是 runc 这个二进制,它调用 Linux 内核的 Namespace 和 Cgroup,给进程造了一个'沙盒房间',让进程以为自己在独立机器里。"

第三层(给架构师)
"镜像符合 OCI Image Spec,是 content-addressable 的静态 rootfs;Runtime 分两层——containerd 作为 CRI 实现管理镜像生命周期和 Pod 沙盒,runc 作为 OCI Runtime Spec 实现,通过 unshare/pivot_root/execve 系统调用序列,在内核态构造隔离执行环境。整个过程中,容器进程与宿主机共享内核调度器、页缓存、系统调用表,只是视图和资源边界被重构。"

六、类比无高下,精度有边界

Ghost 镜像 ≈ 容器镜像,Windows/Linux ≈ Runtime 这个直觉:

  • 对的地方:确实抓住了"静态模板"与"动态执行环境"的核心关系
  • 需要修正的地方:Runtime 不是完整的操作系统(没有内核),而是**"利用宿主机内核,为单个进程树构造隔离视图的启动器"**

如果非要用一句话替换最初的类比:

容器镜像 ≈ Ghost 镜像(静态 rootfs),容器 Runtime ≈ "用宿主机内核 + Namespace/Cgroup 快速还原并启动一个隔离进程"的引擎。

这个引擎比操作系统轻量得多——启动一个容器不是"开机",而是**"执行一个系统调用序列,让普通进程换一套视图继续跑"**,耗时以毫秒计。

更多推荐