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网络接口、路由、端口
ipcIPC 对象
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 等成本记到对应组里;超过规则后,调度、回收、节流或终止就会发生。

资源典型限制方式超限后表现
CPUquota / period / shares周期性 throttling,延迟抖动
memorymemory limitOOM 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. 联系实际:容器里程序慢怎么想

容器内程序慢,要同时看:

  1. 应用本身是否 CPU-bound、IO-bound、memory-bound。
  2. 容器 CPU quota 是否限制。
  3. 是否发生 CPU throttling。
  4. 内存是否接近 cgroup 限制。
  5. 是否被 OOM kill。
  6. 文件写入是否走慢的容器层。
  7. 网络是否经过 NAT、代理、限速或 DNS 问题。
  8. 线程数是否按宿主机而不是限制配置。

容器不是让 OS 问题消失。它只是把 OS 资源隔离问题变得更显式。

边界条件:容器不是虚拟机,隔离视图会泄漏内核事实

容器给进程一组隔离视图,但它通常仍共享宿主机内核。这意味着:

容器内看到的文件、进程、网络可以被隔离;
系统调用最终仍由同一个内核处理。

所以容器不是一台完整独立机器。边界处会出现这些现象:

现象解释
容器内有自己的 PID 1PID namespace 改变了进程编号视图
容器内 root 不一定等于宿主机无限权限用户映射、能力集、挂载和安全策略仍会限制
容器内能看到宿主机内核版本内核不是每个容器一份
宿主机资源足够,容器仍 OOMcgroup 的账本先到限制
容器内网络正常,外部仍访问不到还要经过端口映射、bridge/NAT、策略和防火墙

因此排障时要同时问两套问题:

这个进程在容器视图里看到了什么?
宿主机内核和 cgroup 对它实际做了什么限制?

只看容器内会漏掉真实资源限制;只看宿主机会漏掉容器内的隔离视图。

手推:容器内存为什么会被“看不见的部分”吃掉

假设容器内存限制是:

memory limit = 512 MB

应用内部只统计了主要数据结构:

主数据结构 = 300 MB

看起来还有 212 MB,但实际还可能有:

代码块PLAINTEXT · 4 行收起展开
线程栈:        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. 学完本章你能解决什么问题

学完这一章,你应该能解决或开始分析这些问题:

  1. 虚拟机和容器的根本区别是什么?
  2. 容器为什么共享宿主机内核?
  3. namespace 隔离的是哪些系统视图?
  4. cgroup 如何限制 CPU、内存、IO?
  5. 为什么宿主机有内存,容器仍可能 OOM?
  6. CPU quota 为什么会造成延迟抖动?
  7. 容器文件系统为什么要区分镜像层、可写层和卷?
  8. 容器网络为什么要看命名空间、端口映射和 NAT?

虚拟化和容器的核心是隔离视图与限制资源。它们不是另一套魔法,而是 OS 资源管理能力的组合应用。

延伸阅读