第一百九十五章:容器内核
大乘期涉及内核源码:
一
林小源走出 lockdep 的森林,眼前豁然开朗——一片广袤的平原上,漂浮着无数透明的气泡。每一个气泡里都装着一个完整的世界:有自己的天空、大地、河流,甚至有自己的进程在其中运行。
"这些是……容器?"林小源问。
"没错。"一个清脆的女声从最近的一个气泡里传来。气泡的壁面微微波动,露出一张年轻女子的面孔。她穿着一件由六种颜色丝线编织的长裙,每一种颜色都代表着一种 namespace。
"我叫 namespace。"女子说,"PID 颜色的丝线隔离进程 ID,NET 颜色的丝线隔离网络,MNT 颜色的丝线隔离挂载点,USER 颜色的丝线隔离用户,UTS 颜色的丝线隔离主机名,IPC 颜色的丝线隔离进程间通信。六种丝线,六层隔离。"
林小源伸手触碰气泡的壁面,指尖传来一阵微弱的阻力——那是隔离的边界。
"容器不是虚拟机。"namespace 强调道,"虚拟机有自己的内核,容器共享宿主机的内核。我只是让你看不到别人的东西——每个气泡里的进程都以为自己是唯一的。"
/*
* 容器的内核基础:
*
* 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");#include <stdio.h>
#include <string.h>
/*
* 容器的内核基础:
*
* 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);
}
int main() {
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");
return 0;
}二
林小源注意到气泡的底部连着一根细细的管子,管子里流动着淡金色的液体。管子的另一端连着平原中央的一座石塔。
"那是 cgroup。"namespace 说,语气里带着一丝敬畏,"资源的管家。"
林小源走向石塔。塔身上刻满了数字——CPU 权重 1024、内存限制 512MB、CPU 配额 100000us、进程数限制 100。每一个数字都对应着一个容器的资源上限。
"如果一个容器试图消耗超过限额的资源会怎样?"林小源问。
塔内传来一个浑厚的声音:"我会限制它。CPU 超了就节流,内存超了就回收,I/O 超了就排队,进程数超了就拒绝创建。没有哪个容器能独占系统资源。"
林小源看到塔身上还刻着 "v2" 的标记。"cgroups v2?"
"统一层级。"那声音说,"v1 里每种资源是独立的树,管理混乱。v2 把所有资源放在同一棵树上,cpu.max、memory.max、io.max、pids.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 虚拟机:
- 容器 — 共享内核,轻量
- 虚拟机 — 独立内核,重量
容器安全:
- 非特权容器
- 只读文件系统
- 最小镜像
容器是隔离——内核的轻量级虚拟化。
容器内核之试
容器能拥有独立视图而仍共享宿主机内核,最基础的隔离机制英文名是什么?