virtualization - container
10. 虚拟化、容器与资源隔离
0. 本章先解决什么问题
现代系统里,一个物理机器上可能运行:
- 多个虚拟机。
- 多个容器。
- 多个服务。
- 多个用户任务。
它们都想“像自己独占机器一样运行”,但实际共享 CPU、内存、磁盘、网络。
本章要解决:
- 虚拟化和容器分别是什么抽象?
- VM 和容器有什么区别?
- namespace 如何隔离视图?
- cgroup 如何限制资源?
- 容器里的 CPU、内存、文件、网络问题为什么仍然是 OS 问题?
这张图怎么读
这张图强调容器的本质:容器进程仍运行在宿主机内核上,只是通过 namespace 看到隔离后的系统视图,通过 cgroup 被限制 CPU、内存、IO 等资源,通过镜像层、可写层和挂载卷组织文件系统。
1. 虚拟化的直觉
虚拟化让一个物理机器模拟出多个虚拟机器。
物理硬件
-> 虚拟化层
-> VM A / VM B / VM C
每个虚拟机通常有:
- 自己的虚拟 CPU。
- 自己的虚拟内存。
- 自己的虚拟磁盘。
- 自己的操作系统内核。
虚拟机隔离强,但开销更大。
2. 容器的直觉
容器不是完整虚拟机。
容器通常共享宿主机内核,但有隔离的运行视图:
宿主机内核
-> 容器 A 进程
-> 容器 B 进程
容器主要依赖 OS 提供:
- namespace 隔离视图。
- cgroup 限制资源。
- 文件系统层。
- 网络命名空间。
- 进程隔离。
容器启动快、资源开销小,但隔离边界和 VM 不同。
3. VM 和容器对比
| 对比 | 虚拟机 | 容器 |
|---|---|---|
| 内核 | 每个 VM 通常有自己的内核 | 共享宿主机内核 |
| 隔离 | 更强 | 较轻量 |
| 启动 | 较慢 | 较快 |
| 资源开销 | 较大 | 较小 |
| 文件系统 | 虚拟磁盘 | 分层文件系统/挂载 |
| 适合 | 强隔离、多 OS | 快速部署、服务隔离 |
理解这个区别能避免把容器误认为完整机器。
4. namespace:隔离视图
namespace 让进程看到不同的系统视图。
常见隔离:
| namespace | 隔离什么 |
|---|---|
| pid | 进程编号视图 |
| mount | 挂载点和文件系统视图 |
| net | 网络接口、路由、端口 |
| ipc | IPC 对象 |
| uts | 主机名等 |
| user | 用户和权限映射 |
容器内看到的 PID 1,可能只是宿主机上的一个普通进程。
容器视图和宿主机真实视图不同
机制深挖:namespace 隔离的是“名字”,不是资源本身
namespace 的核心是让同一个内核对象体系呈现出不同视图。它隔离的是“你能看见什么名字、什么编号、什么挂载点、什么网络接口”,并不自动让资源变成独占。
| namespace | 容器内看到 | 宿主机真实情况 |
|---|---|---|
| PID | 自己的进程树,可能把某进程看作 PID 1 | 这些进程仍是宿主机进程 |
| mount | 自己的根文件系统和挂载点 | 文件层和挂载最终落在宿主机存储 |
| net | 自己的网卡、路由、端口 | 通过虚拟网卡、bridge、NAT 接到宿主机网络 |
| user | 容器内用户/组映射 | 需要映射到宿主机权限模型 |
| ipc | 自己可见的 IPC 对象 | 仍由同一个宿主机内核管理 |
这能解释一个常见错觉:
容器内看到自己是 root
不等于它在宿主机上拥有无限权限
也能解释另一个错觉:
容器内看不到别的进程
不等于这些进程不存在于同一台机器
5. cgroup:限制和统计资源
cgroup 用来限制和统计资源。
常见资源:
- CPU。
- 内存。
- IO。
- 进程数量。
- 设备访问。
作用:
限制某组进程最多能用多少资源
统计它们实际用了多少资源
容器内 OOM,可能不是宿主机真的没内存,而是容器内存限制到了。
cgroup 的第一原则:限制的是一组进程的资源账本
cgroup 更像一个资源账本和限额器。进程运行时,内核把 CPU 时间、内存占用、IO 等成本记到对应组里;超过规则后,调度、回收、节流或终止就会发生。
| 资源 | 典型限制方式 | 超限后表现 |
|---|---|---|
| CPU | quota / period / shares | 周期性 throttling,延迟抖动 |
| memory | memory limit | OOM kill、分配失败、频繁回收 |
| IO | 权重、带宽或 IOPS 限制 | 写入/读取排队,延迟升高 |
| pids | 进程数量上限 | 无法创建新进程或线程 |
这也是容器排障比普通进程多一层的原因:你既要看进程本身,也要看它所在 cgroup 的账本。
6. 容器内存问题
容器内存受 cgroup 限制。
可能出现:
宿主机还有内存
容器进程仍然 OOM
因为它超过了容器限制。
还要看:
- 堆内存。
- 线程栈。
- native/外部内存。
- mmap。
- 页缓存是否计入限制。
- 进程数量。
容器内排查内存,不能只看应用内部统计。
7. 容器 CPU 问题
容器 CPU 限制可能带来:
- 可用核数少于宿主机。
- CPU quota 用完后被节流。
- 延迟周期性抖动。
- 线程数按宿主机核数配置过大。
CPU 限制不是“机器慢”,而是调度器按资源控制规则让这组进程少运行。
排查时要看:
- CPU quota。
- throttling。
- runnable 队列。
- 上下文切换。
- 线程数量。
反例:看到很多 CPU 核,不代表容器真的能一直用
容器里有时能看到宿主机的 CPU 数量,但 cgroup 只给了很小的 CPU quota。于是程序可能按“很多核”创建大量线程,实际却只能周期性获得有限 CPU 时间。
可以把 CPU quota 理解成一个时间账本:
每个周期给这组进程一段可运行时间
用完后,即使还有线程想跑,也要等下个周期
这会产生一种很迷惑的表现:
| 观察 | 可能解释 |
|---|---|
| 线程很多,但吞吐上不去 | 可运行线程超过 quota 能承载的并行度 |
| 平均 CPU 使用率看似不满 | 统计口径没有显示 throttling 时间 |
| 延迟呈周期性尖刺 | quota 用完后整组进程被节流 |
| 单机复现正常,容器内变慢 | 宿主机资源和 cgroup 限额不同 |
因此容器里的“CPU 问题”不能只问机器有几核,还要问:
这组进程每个调度周期实际能拿到多少 CPU 时间?
被 throttled 了多少次、多久?
线程数是否按照限额而不是宿主机规模配置?
8. 容器文件系统
容器文件系统常有分层。
只读镜像层
可写容器层
挂载卷
写入容器层和写入宿主机卷的语义可能不同。
注意:
- 容器删除后可写层可能丢失。
- 大量写容器层可能性能差。
- 持久数据应明确挂载位置。
- 文件权限和用户映射要确认。
9. 容器网络
容器可以有自己的网络命名空间。
可能涉及:
- 虚拟网卡。
- bridge。
- NAT。
- 端口映射。
- DNS 配置。
- 网络策略。
容器内能访问,不代表宿主机或外部能访问。外部访问也可能受端口映射和防火墙影响。
10. 联系实际:容器里程序慢怎么想
容器内程序慢,要同时看:
- 应用本身是否 CPU-bound、IO-bound、memory-bound。
- 容器 CPU quota 是否限制。
- 是否发生 CPU throttling。
- 内存是否接近 cgroup 限制。
- 是否被 OOM kill。
- 文件写入是否走慢的容器层。
- 网络是否经过 NAT、代理、限速或 DNS 问题。
- 线程数是否按宿主机而不是限制配置。
容器不是让 OS 问题消失。它只是把 OS 资源隔离问题变得更显式。
边界条件:容器不是虚拟机,隔离视图会泄漏内核事实
容器给进程一组隔离视图,但它通常仍共享宿主机内核。这意味着:
容器内看到的文件、进程、网络可以被隔离;
系统调用最终仍由同一个内核处理。
所以容器不是一台完整独立机器。边界处会出现这些现象:
| 现象 | 解释 |
|---|---|
| 容器内有自己的 PID 1 | PID namespace 改变了进程编号视图 |
| 容器内 root 不一定等于宿主机无限权限 | 用户映射、能力集、挂载和安全策略仍会限制 |
| 容器内能看到宿主机内核版本 | 内核不是每个容器一份 |
| 宿主机资源足够,容器仍 OOM | cgroup 的账本先到限制 |
| 容器内网络正常,外部仍访问不到 | 还要经过端口映射、bridge/NAT、策略和防火墙 |
因此排障时要同时问两套问题:
这个进程在容器视图里看到了什么?
宿主机内核和 cgroup 对它实际做了什么限制?
只看容器内会漏掉真实资源限制;只看宿主机会漏掉容器内的隔离视图。
手推:容器内存为什么会被“看不见的部分”吃掉
假设容器内存限制是:
memory limit = 512 MB
应用内部只统计了主要数据结构:
主数据结构 = 300 MB
看起来还有 212 MB,但实际还可能有:
代码块收起展开
线程栈: 80 MB
运行时/本地内存: 60 MB
内存映射: 40 MB
页缓存/缓冲: 50 MB合计:
300 + 80 + 60 + 40 + 50 = 530 MB
超过 512 MB 后,容器可能被 OOM kill。于是你会看到一种反直觉现象:
应用自己说“我只用了 300 MB”,
容器却因为超过限制被杀。
这说明容器内存排障不能只看程序内部的对象统计。要把 cgroup 口径下的总账本拆开:
进程私有内存
线程栈
本地/运行时内存
mmap
页缓存
共享库和映射
内存问题的核心不是“还有没有宿主机内存”,而是“这组进程在自己的限制账本里是否超支”。
排障卡:容器内慢/OOM/网络不通的三层视角
容器问题不要只在容器内看,也不要只在宿主机看。要同时看三层:
| 层次 | 要看什么 | 常见误判 |
|---|---|---|
| 容器内视图 | PID、文件、DNS、路由、可见 CPU/内存 | 以为它就是完整机器 |
| cgroup 限制 | CPU quota、throttling、memory limit、OOM kill | 宿主机有资源就不会 OOM |
| 宿主机真实状态 | 真实进程、真实网络、真实挂载、内核日志 | 容器内正常就代表外部可达 |
典型判断:
宿主机内存还很多,但容器 OOM
-> 看 cgroup memory limit 和 OOM kill 记录
容器内服务监听了端口,但外部访问不到
-> 看网络 namespace、端口映射、bridge/NAT、防火墙
容器 CPU 使用率不高但延迟周期性抖动
-> 看 CPU quota 是否触发 throttling
容器排障的关键是把“隔离视图”和“真实资源”对齐。
11. 学完本章你能解决什么问题
学完这一章,你应该能解决或开始分析这些问题:
- 虚拟机和容器的根本区别是什么?
- 容器为什么共享宿主机内核?
- namespace 隔离的是哪些系统视图?
- cgroup 如何限制 CPU、内存、IO?
- 为什么宿主机有内存,容器仍可能 OOM?
- CPU quota 为什么会造成延迟抖动?
- 容器文件系统为什么要区分镜像层、可写层和卷?
- 容器网络为什么要看命名空间、端口映射和 NAT?
虚拟化和容器的核心是隔离视图与限制资源。它们不是另一套魔法,而是 OS 资源管理能力的组合应用。