Skip to content

第一百九十五章:容器内核

大乘期

涉及内核源码:

林小源走出 lockdep 的森林,眼前豁然开朗——一片广袤的平原上,漂浮着无数透明的气泡。每一个气泡里都装着一个完整的世界:有自己的天空、大地、河流,甚至有自己的进程在其中运行。

"这些是……容器?"林小源问。

"没错。"一个清脆的女声从最近的一个气泡里传来。气泡的壁面微微波动,露出一张年轻女子的面孔。她穿着一件由六种颜色丝线编织的长裙,每一种颜色都代表着一种 namespace。

"我叫 namespace。"女子说,"PID 颜色的丝线隔离进程 ID,NET 颜色的丝线隔离网络,MNT 颜色的丝线隔离挂载点,USER 颜色的丝线隔离用户,UTS 颜色的丝线隔离主机名,IPC 颜色的丝线隔离进程间通信。六种丝线,六层隔离。"

林小源伸手触碰气泡的壁面,指尖传来一阵微弱的阻力——那是隔离的边界。

"容器不是虚拟机。"namespace 强调道,"虚拟机有自己的内核,容器共享宿主机的内核。我只是让你看不到别人的东西——每个气泡里的进程都以为自己是唯一的。"

c
/*
 * 容器的内核基础:
 *
 * 1. Namespaces — 隔离
 *    PID namespace — 进程 ID 隔离
 *    NET namespace — 网络隔离
 *    MNT namespace — 挂载点隔离
 *    USER namespace — 用户隔离
 *    UTS namespace — 主机名隔离
 *    IPC namespace — 进程间通信隔离
 *
 * 2. cgroups — 资源限制
 *    CPU — CPU 使用限制
 *    内存 — 内存使用限制
 *    I/O — I/O 带宽限制
 *    PID — 进程数限制
 *
 * 3. seccomp — 系统调用过滤
 *    限制可用的系统调用
 *
 * 4. capabilities — 权限控制
 *    细粒度权限
 *
 * cgroups v2:
 *   统一层级
 *   更好的资源管理
 *
 * 容器运行时:
 *   runc — 参考实现
 *   containerd — 容器管理
 *   Docker — 用户界面
 */

/* 模拟 cgroup 资源限制 */
struct cgroup_limits {
    int cpu_shares;      /* CPU 权重 */
    long memory_limit;   /* 内存限制 */
    long cpu_quota;      /* CPU 配额 */
    int pids_limit;      /* 进程数限制 */
};

void print_limits(struct cgroup_limits *limits) {
    printf("  CPU 权重: %d\n", limits->cpu_shares);
    printf("  内存限制: %ld MB\n", limits->memory_limit / 1024 / 1024);
    printf("  CPU 配额: %ld us\n", limits->cpu_quota);
    printf("  进程数限制: %d\n", limits->pids_limit);
}

printf("=== 容器内核 — 隔离与限制 ===\n\n");

printf("容器 = namespaces + cgroups + seccomp\n\n");

printf("--- Namespaces (隔离) ---\n");
printf("PID namespace:\n");
printf("  进程 ID 隔离\n");
printf("  容器内 PID 1 = 容器 init\n\n");
printf("NET namespace:\n");
printf("  网络隔离\n");
printf("  独立的网络接口\n\n");
printf("MNT namespace:\n");
printf("  挂载点隔离\n");
printf("  独立的文件系统\n\n");
printf("USER namespace:\n");
printf("  用户隔离\n");
printf("  容器内 root ≠ 宿主机 root\n\n");

printf("--- cgroups (资源限制) ---\n");
struct cgroup_limits limits = {
    .cpu_shares = 1024,
    .memory_limit = 512 * 1024 * 1024,
    .cpu_quota = 100000,
    .pids_limit = 100,
};
print_limits(&limits);

printf("\n--- cgroups v2 ---\n");
printf("统一层级:\n");
printf("  所有资源在同一层级\n\n");
printf("资源控制:\n");
printf("  cpu.max — CPU 配额\n");
printf("  memory.max — 内存限制\n");
printf("  io.max — I/O 限制\n");
printf("  pids.max — 进程数限制\n\n");

printf("--- seccomp (系统调用过滤) ---\n");
printf("限制容器可用的系统调用:\n");
printf("  允许: read, write, open\n");
printf("  禁止: reboot, mount\n\n");

printf("--- 容器 vs 虚拟机 ---\n");
printf("容器:\n");
printf("  共享内核\n");
printf("  启动快\n");
printf("  资源开销小\n\n");
printf("虚拟机:\n");
printf("  独立内核\n");
printf("  启动慢\n");
printf("  资源开销大\n\n");

printf("--- 容器安全 ---\n");
printf("1. 非特权容器:\n");
printf("   USER namespace\n");
printf("   容器内 root ≠ 宿主机 root\n\n");
printf("2. 只读文件系统:\n");
printf("   防止修改\n\n");
printf("3. 最小镜像:\n");
printf("   减少攻击面\n\n");
printf("4. 安全扫描:\n");
printf("   检查镜像漏洞\n");

林小源注意到气泡的底部连着一根细细的管子,管子里流动着淡金色的液体。管子的另一端连着平原中央的一座石塔。

"那是 cgroup。"namespace 说,语气里带着一丝敬畏,"资源的管家。"

林小源走向石塔。塔身上刻满了数字——CPU 权重 1024、内存限制 512MB、CPU 配额 100000us、进程数限制 100。每一个数字都对应着一个容器的资源上限。

"如果一个容器试图消耗超过限额的资源会怎样?"林小源问。

塔内传来一个浑厚的声音:"我会限制它。CPU 超了就节流,内存超了就回收,I/O 超了就排队,进程数超了就拒绝创建。没有哪个容器能独占系统资源。"

林小源看到塔身上还刻着 "v2" 的标记。"cgroups v2?"

"统一层级。"那声音说,"v1 里每种资源是独立的树,管理混乱。v2 把所有资源放在同一棵树上,cpu.maxmemory.maxio.maxpids.max,清晰明了。"

林小源回到气泡旁,发现 namespace 正在加固气泡的壁面。

"安全问题。"namespace 说,"容器共享内核,如果容器内的进程是 root,逃逸出来就是宿主机的 root。所以我们要用非特权容器。"

她指着 USER 颜色的丝线:"USER namespace 让容器内的 root 映射到宿主机的普通用户。UID 0 在容器内是 root,出了容器就是 UID 1000。即使容器逃逸,攻击者也只是普通用户。"

"还有只读文件系统、最小镜像、安全扫描……"她一项一项数着,"容器轻量,但不代表可以忽视安全。共享内核意味着攻击面更小,但一旦突破就是全系统。"

林小源看着那些漂浮的气泡,每一个都看似脆弱,实则层层防护。容器之道,在于隔离与限制的平衡。


道藏笔记

内核启示

容器是内核的隔离机制。

容器 = namespaces + cgroups + seccomp:

  • namespaces — 隔离视图
  • cgroups — 资源限制
  • seccomp — 系统调用过滤

cgroups v2:

  • 统一层级
  • cpu.max, memory.max, io.max

容器 vs 虚拟机:

  • 容器 — 共享内核,轻量
  • 虚拟机 — 独立内核,重量

容器安全:

  • 非特权容器
  • 只读文件系统
  • 最小镜像

容器是隔离——内核的轻量级虚拟化。


破关试炼

容器内核之试

容器能拥有独立视图而仍共享宿主机内核,最基础的隔离机制英文名是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道