AI 算力基础设施深度系列(一):从容器到 Kubernetes——算力底座的诞生
本文是《AI 算力基础设施深度系列》第 1 篇,共 6 篇。
系列目录:① 容器与 K8S 基础 → ② K8S 底层原理 → ③ GPU 与异构算力 → ④ AI 平台架构 → ⑤ 高性能网络与存储 → ⑥ 生产运维与成本优化
导语
2024 年底,OpenAI 公开了一个让基础设施工程师集体沉默的数字:他们在 Kubernetes 上编排了 25,000 块 GPU,支撑 GPT 系列模型的训练和推理。 同一时间,Google GKE 宣布单集群支持 65,000 个节点,阿里云 ACK 已经在内部支撑了数以万计的 AI 训练任务。
一件事已经很清楚了:Kubernetes 不再只是"跑微服务的编排器"——它正在成为 AI 时代的算力操作系统。
但在理解 Kubernetes 如何管理 GPU、如何编排分布式训练之前,我们需要先回答一个更基础的问题:Kubernetes 是什么?它为什么能成为 AI 算力的底座?
这个问题看似简单,但如果你不理解容器技术的本质、不理解 Kubernetes 的核心设计哲学,后面所有关于 GPU 调度、拓扑感知、Gang 调度的讨论都会变成空中楼阁。
本文是整个系列的起点。我们将从最基础的问题出发:计算资源是怎么被隔离和管理的? 从物理机到虚拟机,从虚拟机到容器,从单机容器到 Kubernetes 集群——每一步演进都在解决什么问题?最终,我们会带着这些理解,看看为什么 AI 算力场景对 Kubernetes 提出了全新的挑战。
一、计算资源的隔离演进:从物理机到容器
1.1 物理机时代:一台机器一个应用
最初的服务器部署模式很直接:一台物理机跑一个应用。
┌─────────────────────────────────┐
│ 应用 A │
├─────────────────────────────────┤
│ 操作系统 (Linux) │
├─────────────────────────────────┤
│ 硬件 (CPU / 内存 / 磁盘) │
└─────────────────────────────────┘
问题显而易见:
- 资源浪费:一个 Web 应用可能只用了 10% 的 CPU,剩余 90% 闲置
- 扩展困难:想部署新应用?买台新服务器
- 环境冲突:两个应用依赖同一个库的不同版本,放在一台机器上必出问题
1.2 虚拟机时代:一台机器多个"逻辑机器"
虚拟化技术(VMware、KVM、Xen)解决了物理机的资源浪费问题。通过 Hypervisor 在一台物理机上虚拟出多台"逻辑机器",每台虚拟机拥有独立的操作系统、独立的内核、独立的资源空间。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App A │ │ App B │ │ App C │
├──────────┤ ├──────────┤ ├──────────┤
│ Guest OS │ │ Guest OS │ │ Guest OS │
│ (Ubuntu) │ │ (CentOS) │ │ (Debian) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
└──────┬──────┘──────┬──────┘
┌──────▼─────────────▼──────┐
│ Hypervisor │
│ (KVM / VMware ESXi) │
├───────────────────────────┤
│ Host OS (Linux) │
├───────────────────────────┤
│ 硬件 (CPU / 内存 / 磁盘) │
└───────────────────────────┘
优点:强隔离——每个虚拟机有独立内核,一个虚拟机崩溃不影响其他虚拟机。
致命缺点:
- 重:每个虚拟机都需要运行完整的 Guest OS,占用 GB 级内存
- 慢:启动一个虚拟机需要分钟级时间
- 开销大:Hypervisor 本身消耗 5-15% 的 CPU,内存虚拟化也有损耗
当你需要在一台 8 卡 GPU 服务器上同时跑 20 个推理任务时,每个任务都启动一个完整的虚拟机?光是 Guest OS 就要吃掉几十 GB 内存。
1.3 容器时代:同一个内核,不同的视图
容器的核心洞察是:大多数应用并不需要独立的操作系统内核,它们需要的只是一个"看起来"独立的运行环境。
容器不虚拟硬件,也不运行 Guest OS。它直接运行在宿主机内核之上,通过 Linux 内核的三大机制实现"隔离的假象":
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App A │ │ App B │ │ App C │
│ (容器) │ │ (容器) │ │ (容器) │
├──────────┤ ├──────────┤ ├──────────┤
│ Libs/Bins│ │ Libs/Bins│ │ Libs/Bins│
└────┬─────┘ └────┬─────┘ └────┬─────┘
└──────┬──────┘──────┬──────┘
┌──────▼─────────────▼──────┐
│ Container Runtime │
│ (containerd / CRI-O) │
├───────────────────────────┤
│ Host OS Kernel (Linux) │ ← 共享同一个内核!
├───────────────────────────┤
│ 硬件 (CPU / 内存 / 磁盘) │
└───────────────────────────┘
对比总结:
| 维度 | 物理机 | 虚拟机 | 容器 |
|---|---|---|---|
| 隔离级别 | 物理隔离 | 硬件虚拟化 | OS 级(进程隔离) |
| 启动速度 | 分钟级 | 30秒-数分钟 | 毫秒-秒级 |
| 资源开销 | 无额外开销 | GB 级(Guest OS) | MB 级(仅应用+依赖) |
| 密度 | 1 应用/机器 | 数十 VM/机器 | 数百容器/机器 |
| 内核 | 独占 | 独立 Guest 内核 | 共享宿主机内核 |
| 性能 | 原生 | 有损(5-15%) | 接近原生(<2%) |
容器就是"用内核的视图隔离能力,以接近零开销的方式,把一个进程封装成一个独立的运行单元。"
这对 AI 场景意味着什么?一块 H100 GPU 价值 3 万美元,你不会愿意为每个推理任务分配一整台虚拟机的开销。容器让你可以在同一台 GPU 服务器上高密度部署几十个推理服务,几乎零开销地共享底层算力。
二、容器核心技术三件套:Namespace、Cgroup、Union FS
容器不是一个魔法黑盒。它本质上就是一个 受约束的 Linux 进程,通过三个内核机制实现隔离:
- Namespace:让进程"看到"一个独立的世界(视图隔离)
- Cgroup:限制进程能"使用"多少资源(资源限制)
- Union FS:让进程有自己的"文件系统"(镜像分层)
2.1 Linux Namespace:给进程戴上"VR 眼镜"
Namespace 是 Linux 内核提供的资源隔离机制。每个 Namespace 为进程组创建一个"独立的视图"——进程在 Namespace 内看到的世界与宿主机不同。
Linux 支持 8 种 Namespace:
| Namespace | 隔离内容 | 系统调用标志 | 引入版本 |
|---|---|---|---|
| Mount (mnt) | 文件系统挂载点 | CLONE_NEWNS |
Linux 2.4.19 |
| UTS | 主机名和域名 | CLONE_NEWUTS |
Linux 2.6.19 |
| IPC | 进程间通信资源 | CLONE_NEWIPC |
Linux 2.6.19 |
| PID | 进程 ID 空间 | CLONE_NEWPID |
Linux 2.6.24 |
| Network (net) | 网络设备、IP、端口、路由 | CLONE_NEWNET |
Linux 2.6.29 |
| User | 用户和用户组 ID | CLONE_NEWUSER |
Linux 3.8 |
| Cgroup | Cgroup 根目录视图 | CLONE_NEWCGROUP |
Linux 4.6 |
| Time | 系统时钟 | CLONE_NEWTIME |
Linux 5.6 |
一个直观的例子:PID Namespace
在宿主机上,所有进程共享同一个 PID 空间,init 进程是 PID 1。当你创建一个新的 PID Namespace 时,这个 Namespace 内的第一个进程会被编号为 PID 1——它认为自己是 init,完全看不到 Namespace 外的进程。
宿主机视角:
PID 1 (systemd)
PID 1234 (containerd)
PID 5678 (容器内的 nginx) ← 宿主机看到的真实 PID
容器内视角 (PID Namespace):
PID 1 (nginx) ← 容器内看到的 PID,它认为自己是 init
PID 2 (nginx worker)
(看不到任何宿主机进程)
Network Namespace 对 AI 场景尤其重要:
每个 Network Namespace 有独立的网络栈——IP 地址、路由表、iptables 规则、网络设备。这意味着:
- 每个容器可以有自己的 IP 地址(不用端口映射)
- 容器间的网络通信需要通过虚拟网络设备(veth pair)桥接
- 后续的 RDMA 网络直通、GPUDirect 等高性能网络方案,本质上就是在绕过或优化 Network Namespace 带来的网络栈开销
创建 Namespace 的底层 API:
// 方式一:通过 clone() 系统调用创建新进程时指定 Namespace
int pid = clone(child_func, child_stack + STACK_SIZE,
CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS | SIGCHLD,
NULL);
// 方式二:通过 unshare() 让当前进程进入新的 Namespace
unshare(CLONE_NEWPID | CLONE_NEWNET);
// 方式三:通过 setns() 加入一个已存在的 Namespace
int fd = open("/proc/1234/ns/net", O_RDONLY);
setns(fd, CLONE_NEWNET);
生产经验:容器的 Namespace 隔离是"软隔离"——所有容器共享同一个内核。如果一个容器利用了内核漏洞,理论上可以逃逸到宿主机。这就是为什么在安全敏感的多租户 AI 平台上,有些场景需要 Kata Containers(在微虚拟机内运行容器)来提供更强的隔离。
2.2 Cgroup(Control Groups):给进程装上"资源计量表"
Namespace 解决了"看到什么"的问题,但没解决"用多少"的问题。一个容器如果不加限制,它可以吃光宿主机的所有 CPU 和内存。
Cgroup 就是 Linux 内核的资源管理框架——它把进程组织成层级结构,对每个组设定资源使用上限。
Cgroup 层级示例:
/sys/fs/cgroup/
├── cpu/
│ ├── docker/
│ │ ├── container-a/ → CPU 限制: 2 cores
│ │ ├── container-b/ → CPU 限制: 4 cores
│ │ └── container-c/ → CPU 限制: 1 core
│ └── system.slice/ → 系统服务
├── memory/
│ ├── docker/
│ │ ├── container-a/ → 内存限制: 4GB
│ │ ├── container-b/ → 内存限制: 16GB
│ │ └── container-c/ → 内存限制: 2GB
│ └── system.slice/
└── devices/
└── docker/
├── container-a/ → 允许访问: /dev/nvidia0
└── container-b/ → 允许访问: /dev/nvidia1
Cgroup 可以管理的资源类型:
| 控制器 | 管理的资源 | AI 场景关联 |
|---|---|---|
| cpu | CPU 时间片分配 | 数据预处理、推理前后处理 |
| cpuset | 绑定 CPU 核心 | NUMA 亲和性优化 |
| memory | 内存用量限制 | OOM 保护 |
| devices | 设备访问控制 | <

:从容器到 Kubernetes——算力底座的诞生&spm=1001.2101.3001.5002&articleId=159552321&d=1&t=3&u=2c1017a7ec7d4d10b6ec18b8770ef01f)
1427

被折叠的 条评论
为什么被折叠?



