这一章在做什么

回答一个问题:在虚拟机和容器里,为什么指标「看着不对」,该去哪里找真话。书里比较三种虚拟化:硬件虚拟机、容器(操作系统虚拟化)、轻量虚拟机,看各自的开销、资源控制,以及在宿主上和在客户机里各能看见什么。最该带走的是:限额比物理资源先用完,所以先查限额;容器里的 top、free、uptime 多半报的是整台宿主;CPU 节流、steal、内存上限各有自己的计数,要自己去读。

容器里敲的命令,读的是谁的数 容器里的进程 top、free uptime、vmstat /proc/stat、meminfo、loadavg 整台宿主的数,含邻居的活 自己的 cgroup 目录 cpu.max、cpu.stat、memory.* 默认 要自己读 宿主空着八成,容器却可能每 100 ms 被掐掉 75 ms: 上面那条路看不见,下面那条路的 cpu.stat 写得清清楚楚。
本站画的示意。以常见的容器配置为准(有自己的 PID 和挂载命名空间);有的平台会把 /proc 换成按容器算的版本,以你机器上实际读到的为准。

什么时候翻这一章

这一章不讲新的资源,讲的是第 6–10 章的方法搬进虚拟化环境以后哪里要改。USE 方法要把每一种限额当成一种资源来查(§2.5.9);CPU 那一章的 steal(§6.6.2)、限额(§6.9.8)在这里展开。

先用中文过一遍

三种虚拟化,差在有几个内核

硬件虚拟化给每个客户机一台「假电脑」,客户机跑自己的内核;容器只有一个内核,靠内核把进程分成几组、互相看不见;轻量虚拟机(MicroVM,书里以 Firecracker 为例)还是一客户机一内核,但虚拟机管理程序(hypervisor)做得很小,启动快、占内存少。

有几个内核,决定了两件事。一是开销:虚拟机里用户态代码直接在 CPU 上跑,开销主要出在特权操作、内存地址的二次翻译和 IO 上,有硬件支持后已经很小;容器在 CPU 和内存上没有额外开销,代价是大家挤同一个内核里的锁、缓存和队列(§11.2.2、§11.3.2)。二是谁看得清:虚拟机的用户有自己的内核,perf、BPF 都能用,但看不到宿主;容器的运营方在宿主上什么都看得见,容器里的用户却多半用不了内核跟踪工具(§11.6)。书里点出的尴尬是:从虚拟机搬到容器,更容易撞上内核里的争用,同时失去查它的手段。

steal:虚拟 CPU 想跑却没轮到

虚拟机里的 CPU 是虚拟 CPU(vCPU),由宿主调度到物理 CPU 上。宿主把这颗物理 CPU 给了别的客户机,或者拿去处理 IO、执行实例类型的限额,你的 vCPU 就得等。客户机内核把这段时间记成 steal(被偷走的时间),vmstat 的 st、mpstat 的 %steal 就是它(§11.2.4)。书里说这是少见的「知道自己被虚拟化了」的指标。

读它要注意两点。第一,它只告诉你被拿走了多少,不告诉你被谁拿走:客户机看不到邻居,原因得问云厂商或者换实例。第二,它在客户机里算,以 KVM 为例,记的是 vCPU 有活要干、却没被宿主调度的时间;vCPU 自己闲着时,宿主拿走它不算 steal。所以 steal 高说明「你想要的 CPU 没给足」,和利用率、排队一起看才完整。

同一段时间,宿主和客户机各记了什么 物理 CPU 3 你的 vCPU 1 邻居的 vCPU 你的 宿主 IO 你的 客户机里 us / sy st us / sy st us / sy 客户机只知道「这段时间我想跑,没跑上」,不知道给了谁。 宿主上(KVM)每个 vCPU 是 qemu 进程里的一个线程,用 pidstat、top 就能看到。
本站画的示意,假设这颗 vCPU 一直有活要干。vCPU 空闲时被拿走,不记成 steal。

容器:命名空间管看见什么,cgroup 管用多少

Linux 内核里没有「容器」这个东西。Docker 之类的软件用两样内核功能拼出容器:命名空间(namespace)决定一组进程能看见什么(自己的进程号、挂载点、网卡、主机名),控制组(cgroup)决定它们能用多少(CPU、内存、IO、进程数)。所以容器里的 1 号进程在宿主上是另一个 PID,宿主上用 grep NSpid /proc/PID/status 能看到两边的对应(§11.3.1、§11.3.4)。

这两样东西分开做,留下了这一章最大的坑:很多统计并没有按命名空间隔离。/proc 下的 stat、meminfo、loadavg 在常见配置下仍是整台宿主的数,于是容器里的 top 头部、uptime、free、vmstat、mpstat、iostat 报的都是宿主;ps、pidstat 只列容器自己的进程,因为 PID 命名空间隔离了进程表。本站实测:在一个限了半个 CPU、256 MB 内存的 cgroup 里,nproc 仍然说 16,free 仍然说有 11 GB 可用。用这些数给线程池、缓存定大小,就会定错。

真话在 cgroup 的目录里。cgroup v2 下,cat /proc/self/cgroup 给出自己所在的路径,前面加上 /sys/fs/cgroup 就是目录:cpu.max 是 CPU 限额,cpu.stat 是用量和节流,memory.max、memory.current 是内存的上限和当前用量。

CPU 限额:配额、周期和节流

cgroup 管 CPU 有三种手段(§11.3.3)。cpuset 把整颗 CPU 划给容器,稳定但闲着也不让别人用。份额(v1 叫 shares,v2 叫 weight)只在大家都抢的时候按比例分,没人抢时可以「借」闲置的 CPU,这叫突发(bursting)。带宽限额是硬上限:每个周期(默认 100 ms)给一份配额,用完就停到下个周期。cgroup v2 里写成 cpu.max 的两个数,200000 100000 就是每 100 ms 给 200 ms,相当于两颗 CPU。

配额是所有线程合用的。一个有 4 个线程的服务,限额一颗 CPU:4 个线程在 4 个核上同时跑,25 ms 就把 100 ms 的配额花完,然后全体停 75 ms。平均下来它确实只用了一颗 CPU,可每个请求都可能撞上一次 75 ms 的停顿。这时宿主上大半的核空着,mpstat 一切正常;只有 cpu.stat 里的 nr_throttled(被掐的周期数)和 throttled_usec(被掐的时间)在涨。下面「动手」一节量出的就是这个数。

cpu.max = 100000 100000(每 100 ms 给 100 ms),4 个线程一起跑 线程 1线程 2线程 3线程 4 被掐:等下个周期 0100 ms200 ms300 ms 跑 25 ms × 4 个线程 = 100 ms,配额用完;每个周期 nr_throttled 加 1。 平均只用 1 颗 CPU,宿主上另外 3 个核这 75 ms 是空的;请求延迟里多了最长 75 ms。
本站画的示意,数字取自本页「动手」在 WSL2 上的实测(最长停顿约 76–80 ms)。线程越多、配额相对越小,停得越久:2 个线程时实测约 50 ms。

内存限额:上限之内,page cache 也算

虚拟机的内存是配置时定死的,客户机内核自己在这个范围里换页、回收。容器则由 memory cgroup 管:容器读写文件产生的 page cache 记在容器名下,不用另外设置(§11.3.3)。cgroup v2 里 memory.max 是硬上限,用量回收不下来就在这个 cgroup 里触发 OOM;memory.high 是软一些的线,超过后进程被放慢、被迫回收。

读数时要分清两种内存:memory.stat 里的 anon 是堆和栈,回收它只能换出;file 是 page cache,大多可以直接丢。memory.current 贴着 memory.max 不一定危险,可能只是缓存把空间用满了;危险的信号是 memory.events 里 max、oom_kill 在涨,或者 anon 一路长。

英文中文一句话
hypervisor / VMM虚拟机管理程序创建和调度虚拟机的软件,书里按它跑在硬件上还是宿主内核里分两种配置 §11.2
host / guest / tenant宿主 / 客户机 / 租户物理机是宿主,上面的虚拟机或容器是客户机,客户机的使用者是租户 §11.1
vCPU虚拟 CPU客户机看到的 CPU,由宿主调度到物理 CPU 上 §11.2.3
steal time被偷走的时间vCPU 想跑却没被宿主调度的时间,vmstat 的 st §11.2.4
guest exit客户机退出vCPU 遇到要宿主处理的事件时停下、交给宿主;退出越多越久,开销越大 §11.2.2
SR-IOV / PCI pass-through单根 IO 虚拟化 / PCI 直通让客户机直接访问硬件,IO 快了,宿主却可能看不见这些 IO §11.2.2
namespace命名空间过滤进程能看见的东西:PID、挂载、网络、主机名等 §11.3.1 术语页
cgroup (control group)控制组限制和统计一组进程用多少资源;v2 是一棵统一的树 §11.3.1 术语页
CPU shares / weightCPU 份额 / 权重大家都抢时按比例分,只保证下限,没人抢时可以多用 §11.3.3
CPU bandwidth (quota / period)CPU 带宽限额(配额 / 周期)每个周期给一份 CPU 时间,用完就停到下个周期,硬上限 §11.3.3 术语页
throttling节流被限额掐住;CPU 的看 cpu.stat 的 nr_throttled 和被掐的时间 §11.3.4
bursting突发借用别人闲着的资源,跑得比自己的份额快;邻居一忙就没了 §11.3.3
noisy neighbor吵闹的邻居同一台宿主上大量占资源、拖慢别人的租户 §11.1.5
MicroVM微型虚拟机轻量虚拟化:一客户机一内核,但管理程序极小、启动快 §11.4

方法:按什么顺序查

书里的原则只有一句:限额比物理上限先到,所以先查限额(§11.3.4)。宿主运营方和容器用户都该这么做;USE 方法把每一种限额当作一种资源,查它的利用率、饱和度和错误(§2.5.9)。落到一次排查上,本站整理成下面几步,前三步在容器或客户机里就能做,不要 root。

  1. 我在哪种环境里?

    systemd-detect-virt 说出是哪种虚拟机或容器;lscpu 的 Hypervisor vendor 一行;cat /proc/self/cgroup 给出所在的 cgroup,只有一行 0::/… 就是 cgroup v2。环境不同,下面能信哪些数也不同。§11.2.4§11.3.4

  2. 有哪些限额?

    在自己的 cgroup 目录里读 cpu.max、cpu.weight、cpuset.cpus.effective、memory.max、memory.high、pids.max。max 表示没限。把这些数记下来,后面所有利用率都按它算,而不是按 nproc 和 free。§11.3.3

  3. CPU 是被限额掐住,还是被邻居挤了?

    隔几秒读两次 cpu.stat:nr_throttled 和 throttled_usec 在涨,就是带宽限额在节流。用了 cpuset,就看那几颗 CPU 是不是满了。都不是,而进程的 nonvoluntary_ctxt_switches(/proc/PID/status)在涨、宿主上没有空闲的 CPU,那是份额之争:别的租户也在抢。虚拟机里看 vmstat 1 的 st。§11.3.4§11.2.4

  4. 内存离上限多远?

    memory.current 对 memory.max;memory.stat 里 anon 和 file 各多少;memory.events 的 max、oom_kill 有没有在涨;memory.pressure 给出因为内存而停顿的时间比例。§11.3.3§11.3.4

  5. IO 和网络:先看延迟

    虚拟磁盘、虚拟网卡的「忙」不说明什么,因为你不知道背后是什么设备;看延迟。虚拟机里有自己的内核,可以用 BPF 工具逐个看 IO 的延迟,找出既不是因为 IO 大、也不是因为排在写回后面的离群值,那多半是物理上的争用。网络的限速常常只有宿主或云厂商的接口看得到。§11.2.4

  6. 到宿主上接着查

    能上宿主时:systemd-cgtop 按 cgroup 排出谁在用 CPU 和内存;grep NSpid /proc/PID/status 对上容器里外的 PID;nsenter -t PID -m -p top 进它的命名空间跑工具。KVM 的客户机在宿主上是 qemu 进程,pidstat -t -p PID 1 看每个 vCPU 线程,perf kvm stat live 看退出原因(要 root)。§11.2.4§11.3.4

工具速查

想知道敲什么看哪里节
在哪种虚拟化里systemd-detect-virt
lscpu
输出 kvm、microsoft、docker 之类(本站的 WSL2 上输出 wsl);lscpu 的 Hypervisor vendor§11.2.4
所在 cgroup、是不是 v2cat /proc/self/cgroup
stat -fc %T /sys/fs/cgroup
一行 0::/路径、文件系统类型 cgroup2fs 就是 v2;v1 是一堆按控制器分开的行§11.3.1
CPU 限额cat cpu.max cpu.weight配额 周期,两数相除是几颗 CPU;max 100000 是不限§11.3.3
有没有被节流cat cpu.stat,隔几秒再读一次nr_throttled / nr_periods 的增量比例;throttled_usec 在涨就是限额在掐§11.3.4
内存离上限多远cat memory.current memory.max
cat memory.events
上限是不是 max;memory.stat 的 anon 和 file;oom_kill 不为零就是被杀过§11.3.3
按 cgroup 的停顿cat cpu.pressure memory.pressure和 /proc/pressure/* 一样的格式,但只算这个 cgroup 里的任务§11.3.4
虚拟机被偷了多少 CPUvmstat 1
mpstat -P ALL 1
st / %steal 持续不为零;看是不是所有 vCPU 都有§11.2.4
宿主上谁在用systemd-cgtop按 cgroup 排的 %CPU、内存、任务数;容器放在哪一层随运行时和 cgroup 驱动而变,本站的 WSL2 上是 /docker 和 /kubepods§11.3.4
容器里的 PID 在宿主上是谁grep NSpid /proc/PID/status
ls -l /proc/PID/ns/
NSpid 第一个数是宿主的 PID,后面是各层命名空间里的;命名空间 ID 相同才是同一个§11.3.4
进容器的命名空间跑工具nsenter -t PID -m -p top
nsenter -t PID -n ss -tin
-p 只看到容器的进程,但 top 头部仍是宿主的数;-n 用容器的网络栈§11.3.4
有哪些命名空间lsns每个命名空间的类型、进程数、第一个进程§11.3.1
容器平台的汇总docker stats
kubectl top pod
docker stats 给每个容器的 CPU 百分比(可超过 100%)和内存用量对上限;kubectl top pod 给的是 CPU 核数(250m 是四分之一核)和内存用量,要另外对照 limit§11.3.4
KVM 客户机为什么退出perf kvm stat live退出次数少且多是 HLT:客户机闲着;IO 类退出多:它在做虚拟磁盘、网卡的 IO§11.2.4
Xen 各客户机的用量xentop每个域的 CPU、内存、虚拟网卡和块设备;VBD_OO 是块设备请求排队§11.2.4

表里的 cgroup 文件都在自己的 cgroup 目录下(/sys/fs/cgroup 加上 /proc/self/cgroup 第三段),读它们不要 root;书里的例子是 cgroup v1 的文件名,对照见「成书之后」。systemd-cgtop、systemd-detect-virt、systemd-run 随 systemd 自带;nsenter、lsns 在 util-linux 里;vmstat 在 procps,mpstat、pidstat 在 sysstat。宿主上的 nsenter、perf kvm、读别的用户的 /proc 要 root。工具名链到本站的工具卡:vmstat、mpstat、systemd-cgtop、nsenter、lsns、perf、xentop。

动手:在自己的机器上试

给自己套一个 CPU 限额,量出节流的停顿

下面这个程序起几个线程空转,每个线程不停地读时钟,记下两次读之间最长隔了多久。不限额时这个间隔只有一两毫秒(被别的进程抢一下)。先猜:限额一颗 CPU、跑 4 个线程,最长会停多久?

// stall.c:N 个线程各自空转,记下两次读时钟之间最长停了多久
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static double now_ms(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec * 1e3 + t.tv_nsec / 1e6;
}

static void *spin(void *arg) {
    double start = now_ms(), last = start, worst = 0;
    long stalls = 0;                       // 停了 5 ms 以上的次数
    while (last - start < 3000) {          // 跑 3 秒
        double t = now_ms();
        if (t - last > worst) worst = t - last;
        if (t - last > 5) stalls++;
        last = t;
    }
    printf("thread %ld: longest stall %.1f ms, stalls>5ms: %ld\n",
           (long)arg, worst, stalls);
    return NULL;
}

int main(int argc, char **argv) {
    int n = argc > 1 ? atoi(argv[1]) : 4;
    pthread_t th[32];
    if (n < 1 || n > 32) n = 4;
    for (long i = 0; i < n; i++) pthread_create(&th[i], NULL, spin, (void *)i);
    for (int i = 0; i < n; i++) pthread_join(th[i], NULL);
    return 0;
}
$ gcc -O2 -pthread stall.c -o stall
# 不限额,作对照
$ ./stall 4
# 放进一个限额 1 颗 CPU 的临时 cgroup 里跑,跑完读它的 cpu.stat
$ systemd-run --user --scope --quiet --expand-environment=no -p CPUQuota=100% sh -c './stall 4; cat /sys/fs/cgroup$(cut -d: -f3 /proc/self/cgroup)/cpu.stat'

本站在 16 核的 WSL2 上看到的(各截了一部分):

不限额:  thread 0: longest stall 1.9 ms, stalls>5ms: 0
限额后:  thread 0: longest stall 80.0 ms, stalls>5ms: 31

usage_usec 3111431
nr_periods 31
nr_throttled 31
throttled_usec 9037843
  1. 每个线程 3 秒里停了 31 次,每次七八十毫秒:每个 100 ms 的周期里,4 个线程各跑 25 ms 左右,合起来就花完了配额,然后一起等。
  2. nr_throttled 等于 nr_periods:每个周期都被掐了。usage_usec 约 3.1 秒,3 秒多的墙钟时间正好用掉一颗 CPU,限额起作用了。
  3. throttled_usec 是 9 秒,比墙钟时间还长:从数上看,它是每个 CPU 上被掐的时间加在一起(4 个核 × 31 次 × 约 75 ms)。所以它只适合看涨没涨、涨得快不快,不要拿它和墙钟时间直接比。
  4. 试试 ./stall 1:一个线程用不完一颗 CPU 的配额,nr_throttled 基本是 0;./stall 2 时最长停顿变成 50 ms 左右。线程数按 nproc 定、限额却只有一两颗 CPU 的服务,就是 4 个线程这一种。
  5. 节流的同时在另一个终端跑 mpstat 1:本站看到全机 85% 空闲、%steal 是 0。只看全机指标,这个服务没有任何问题。

再看一眼限额里的程序以为自己有多少资源:

$ systemd-run --user --scope --quiet --expand-environment=no -p CPUQuota=50% -p MemoryMax=256M sh -c 'nproc; free -m; C=/sys/fs/cgroup$(cut -d: -f3 /proc/self/cgroup); cat $C/cpu.max $C/memory.max'
# 对照:nproc 认 CPU 亲和性(cpuset 就是靠它);本站的 coreutils 9.7 不认配额
$ taskset -c 0,1 nproc
nproc:      16
free:       total 14874   available 11713   (MB)
cpu.max:    50000 100000
memory.max: 268435456
taskset 后:  2

限了半颗 CPU、256 MB,nproc 和 free 完全没察觉。nproc 会随 CPU 亲和性变(cpuset 也是靠亲和性生效的),配额它不管;这是本站 coreutils 9.7 的行为,2025 年 9 月发布的 coreutils 9.8 起 nproc 会参考 cgroup v2 的配额(--all 仍然不管),先在自己机器上试一下。照 nproc 起线程池、照 free 定缓存大小的程序,放进容器就会定错,要改成读 cgroup 的文件,或者让部署时把数传进来。

WSL2
本站实测过上面所有命令,没有用 root。能这样做,是因为本机的 systemd 把 cpu、memory、pids 三个控制器委派给了用户(看 user@UID.service 那个 cgroup 目录里的 cgroup.controllers)。本机的 WSL2(Debian 13)只挂了 cgroup v2。
服务器
没有委派时 systemd-run --user 设不了限额,用 root 的 systemd-run --scope -p CPUQuota=100%,或者 docker run --cpus=1。生产上要看已有容器的节流,直接读它的 cgroup 目录里的 cpu.stat,路径用 /proc/容器进程的PID/cgroup 找,各种运行时放的位置不一样。
ARM 板
要内核开了 cgroup 和 CPU 带宽控制(CONFIG_CGROUPS、CONFIG_CFS_BANDWIDTH,看你的内核配置)。没有 systemd 时用 root 手工建:mkdir /sys/fs/cgroup/t,往 cgroup.subtree_control 写 +cpu,往 t/cpu.max 写 100000 100000,再把 PID 写进 t/cgroup.procs。程序本身只用 pthread,交叉编译即可。

成书之后

cgroup v2 成了主流发行版的默认,v1 在退场

书里
说 Linux 有 v1 和 v2 两版 cgroup,Kubernetes 等很多项目还在用 v1,v2 在路上;预计容器技术几年内迁到 v2,Fedora 31 已经切换(§11.3.1)。§11.3.3、§11.3.4 的例子都是 v1 的文件。
今天
runc 和 Kubernetes 的文档列出的默认用 cgroup v2 的发行版有:Fedora 31 起、Debian 11 起、Ubuntu 21.10 起、RHEL 9 及其衍生版起。Kubernetes 1.35 把 cgroup v1 支持标为弃用,kubelet 默认不再在 v1 节点上启动。systemd 258 去掉了 v1 支持,开机只挂 v2。判断自己的机器:有 /sys/fs/cgroup/cgroup.controllers 就是 v2。读者的 WSL2(Debian 13)也是 v2。
核对于 2026-10。出处:runc 文档《cgroup v2》;Kubernetes 文档《About cgroup v2》;Kubernetes KEP-5573《Remove cgroup v1 support》;Debian systemd 软件包 NEWS 中的 systemd 258 条目;LWN 关于 systemd 258 的报道。

书里的 v1 文件名,在 v2 里换了名字

书里
CPU 看 cpu,cpuacct 目录下的 cpuacct.usage 和 cpu.stat 的 throttled_time(纳秒),内存看 memory.limit_in_bytes,磁盘看 blkio.throttle.*(§11.3.3、§11.3.4)。
今天
v2 每个 cgroup 只有一个目录,各控制器的文件放在一起。CPU 用量在 cpu.stat 的 usage_usec,节流时间是 throttled_usec(单位是微秒),限额是 cpu.max,份额是 cpu.weight;内存是 memory.max、memory.high、memory.events;磁盘限速是 io.max,统计在 io.stat。v2 还给每个 cgroup 配了自己的 cpu.pressure、memory.pressure、io.pressure(PSI)。以上 CPU、内存和 PSI 的文件本站在 WSL2(内核 6.6)上逐个读过。
核对于 2026-10。出处:内核文档 Documentation/admin-guide/cgroup-v2.rst;本站在 WSL2 上实测。

带宽限额可以攒着突发:cpu.max.burst

书里
带宽限额是每个周期一份配额,用不完不能留到下个周期;书里管理突发的办法是份额加上限,或者提醒用户别依赖突发(§11.3.3)。
今天
内核给 CFS 带宽控制加了 burst:cgroup v2 的 cpu.max.burst 允许把前面周期没用完的配额攒一点,留给后面的短时高峰;cpu.stat 里随之多了 nr_bursts、burst_usec。默认是 0,不开。对延迟敏感、负载一阵一阵的服务,它能减少上面那种整周期的停顿,代价是这段时间对邻居的干扰更大。
核对于 2026-10。出处:内核文档《CFS Bandwidth Control》(Documentation/scheduler/sched-bwc.rst);本站在 WSL2 内核 6.6 上看到了这个文件和 cpu.stat 里的两个计数。

分节地图

这一章先铺背景(§11.1),再把三种虚拟化各按「实现、开销、资源控制、可观测性」四节走一遍(§11.2–11.4),最后是别的形态和一张对比。先读的是容器那一半:资源控制和可观测性两节最长,也是「指标看不准」的答案所在。虚拟机的 steal、宿主上的工具在 §11.2.4,云主机用户用到时再查。

  1. The rise of cloud computing solves…约 400 词开篇学习目标和四个部分,以及三种虚拟化在云上普及的先后。
  2. Background约 300 词背景宿主、客户机、租户、多租户这几个词怎么用。
  3. Instance Types约 200 词实例类型云厂商的实例家族和规格,名字会过时,看厂商文档即可。
  4. Scalable Architecture约 300 词可扩展的架构纵向和横向扩展的架构常识,和单机排查关系不大。
  5. Capacity Planning约 800 词容量规划自动伸缩会掩盖性能退化;突发是什么。
  6. Storage约 200 词存储云上的网络存储为什么比本地盘难预测。
  7. Multitenancy约 200 词多租户吵闹的邻居,和两种对策:资源控制与可观测性。
  8. Orchestration (Kubernetes)约 400 词编排(Kubernetes)Pod、Node、Service 的关系;调度和网络是两大性能问题。
  9. Hardware Virtualization约 700 词硬件虚拟化两种管理程序配置;二进制翻译、半虚拟化、硬件辅助的演进。
  10. Implementation约 400 词实现VMware、Xen、Hyper-V、KVM、Nitro 的来历,排查用不上。
  11. Overhead约 1800 词开销CPU、内存映射、内存、IO、多租户争用各自的开销从哪来。
  12. Resource Controls约 900 词资源控制vCPU、缓存划分、气球驱动;客户机内的优先级宿主看不见。
  13. Observability约 1900 词可观测性宿主上看 vCPU 线程和退出原因,客户机里看 steal 和设备延迟。
  14. OS Virtualization约 500 词操作系统虚拟化容器比虚拟机好在哪、差在哪;前两条缺点要合起来读。
  15. Implementation约 400 词实现命名空间和 cgroup 各管什么;内核里没有容器这个概念。
  16. Overhead约 700 词开销CPU 和内存没有额外开销;IO 多几层;主要问题是多租户争用。
  17. Resource Controls约 1300 词资源控制优先级和上限两类;CPU 的 cpuset、份额、带宽怎么配合。
  18. Observability约 3400 词可观测性容器里的工具报谁的数,宿主上怎么按容器查,节流怎么判断。
  19. Lightweight Virtualization约 300 词轻量虚拟化MicroVM 想兼得虚拟机的隔离和容器的轻快。
  20. Implementation约 200 词实现几个项目的来历和启动时间,名单会变。
  21. Overhead约 100 词开销和 KVM 相近,内存占用小得多。
  22. Resource Controls约 100 词资源控制管理程序是宿主上的进程,用宿主的 cgroup 管。
  23. Observability约 300 词可观测性宿主上整台虚拟机只是一个进程;客户机里的统计只算自己。
  24. Other Types约 300 词其他形态函数即服务、SaaS、Unikernel:没有可登录的系统时怎么分析。
  25. Comparisons约 300 词比较一张对比表;可观测性对运营方和用户各偏向哪种技术。
  26. Exercises约 200 词习题习题;本页的动手和诊断题可以代替。

词与短语

只收先读和用到再查两档的节。斜体那一行是原句里连续的几个词,在电子书里搜它就能找到那一句。

§11.1.3 Capacity Planning

bursting
… using a strategy called bursting …
突发:临时借用闲置资源
不是「爆裂」。云上指容器用别人闲着的 CPU,跑得比自己分到的多。

§11.1.5 Multitenancy

noisy neighbors
… may be caused by noisy neighbors …
吵闹的邻居
同一台宿主上狂占资源的别的租户。行话,不是真有声音。
Worse,
Worse, a neighbor could be evaluating …
更糟的是

§11.1.6 Orchestration (Kubernetes)

disposable
… allowing them to be treated as disposable …
可以随时丢弃的
Pod 随时被删、被重建,不要把它当成长期存在的机器。

§11.2 Hardware Virtualization

Type 1-ish
Type 2 has been made Type 1-ish …
有点像第一类了
-ish 是口语后缀,「差不多是、有几分像」。第二类管理程序借内核模块直接碰硬件,界线模糊了。

§11.2.2 Overhead

virtually the same
… may experience virtually the same performance …
几乎一样
在讲虚拟化的一章里,virtually 是「几乎、实际上」,不是「虚拟地」。
culminating with
… culminating with hardware support for eliminating …
最终发展到
follow suit
… performance to follow suit …
跟着这样做
出自纸牌游戏:跟出同花色的牌。作者预计各大云厂商都会学 Nitro。

§11.2.3 Resource Controls

coarsely
… assigned coarsely limits CPU resource usage …
粗略地
给几个 vCPU 只能按整颗来限,粒度粗。
opaque to
… usage is typically opaque to the hypervisor …
对……不透明,看不进去
管理程序只看到 vCPU 在忙,看不到客户机里是哪个线程、什么优先级。
on the lookout
… I would be on the lookout …
留神提防
作者的保留态度:用了气球驱动,就要提防它带来的性能问题。

§11.2.4 Observability

heavy users
… so that heavy users can be …
用量大户
不是「重的用户」:资源用得最多的那几个客户机。
peace of mind
… give cloud customers peace of mind …
安心
后面括号里补了一句真实动机:也能少收点工单。
redacted
… check redacted physical resource usage …
删去敏感信息后的
本义是公文里涂黑的部分。给客户看物理资源的用量,但抹掉邻居是谁。

§11.3.1 Implementation

in the works
(v2 is in the works)
正在做,还没完成
成书时 Kubernetes 对 v2 的支持还在开发中。今天的情况见「成书之后」。

§11.3.2 Overhead

yield
… until they either yield or are preempted …
(主动)让出 CPU
不是「产出」。线程自己让出,或者被抢占,才离开 CPU。
by an order of magnitude
… by an order of magnitude or more …
(增加)一个数量级
十倍左右。

§11.3.3 Resource Controls

best-effort
… provide a best-effort division of CPU …
尽力而为的
网络和系统里的行话,意思是「不作保证」,不是「最大努力保证做到」。
working as intended
… the system is working as intended …
系统就是这么设计的
不是 bug。用户以为出了故障,其实只是邻居搬进来了,借不到闲置 CPU。
the new norm
… ten-times slower is the new norm …
新常态
慢十倍以后就一直是这样,不会恢复。
bait and switch
… can feel like bait and switch.
挂羊头卖狗肉
原指用便宜货招揽、再换成别的商品的推销手法。试用时快,买了以后慢,客户觉得被骗了。
a memory form of bursting
… a memory form of bursting …
内存版的突发
别的容器没用的内存被拿来做 page cache,你就多用了。

§11.3.4 Observability

not dissimilar to
… this is not dissimilar to traditional …
和……差不多
双重否定,等于 similar,语气更保留。
blind to
… and are blind to cgroup …
看不见,意识不到
工具不知道有 cgroup 限额这回事,照样按整台机器报。
headroom
… that systems have headroom when …
余量
工具按物理资源算出还有余量,实际早撞上软件限额了。
still more complex
… made still more complex and time-consuming …
更加复杂
still 放在比较级前面是「更加」,不是「仍然」。
help and hinder
… would both help and hinder analysis …
既帮忙又添乱
long before
… encountered long before the physical limits …
远在……之前
long 在这里修饰 before,表示早得多,不是「长」。
playing catch up
… container-awareness playing catch up …
在后面追赶
内核变得快,厂商文档和工具跟不上。

§11.6 Comparisons

in a position to
… not in a position to change …
有条件、有权去(做)
不是「在某个位置上」。即使你决定不了公司用哪种技术,比较一下也有助于理解。
overlook
… this overlooks the importance of being …
忽视
不是「俯瞰」。只比微基准测试,就忽视了可观测性。
Given
Given their benefits, especially for …
考虑到,鉴于
句首的 given 是介词,不是「给了」。

诊断题:换成你来查

场景和输出都是本站编的。先自己判断,再点选项看解释。

这个容器里的服务为什么 p99 抖?§11.3.4

场景:你的 C++ 网关跑在 Kubernetes 里,CPU limit 是 2。程序按 std::thread 的 hardware_concurrency() 起了 32 个工作线程。p99 偶尔到 90 多 ms,平均延迟很好。节点上 mpstat 显示 70% 空闲。你在容器里隔 10 秒读了两次:

cpu.max:   200000 100000
cpu.stat:  nr_periods    1200  →  1300
           nr_throttled   410  →   497

10 秒 100 个周期里 87 个被掐,节流非常频繁。hardware_concurrency() 返回的是节点的核数,不知道限额,32 个线程同时跑,配额很快用完,剩下的时间所有线程一起等,平均值看不出来,p99 看得出来。节点空闲说明的是物理资源,限额先到了。改法:按限额定线程数(2–3 个工作线程),或者提高 limit;加线程只会更快地花完配额。steal 是虚拟机的概念,容器里节流看的是 cpu.stat。

云主机里这组 vmstat 说明什么?§11.2.4

场景:一台 4 vCPU 的云主机跑行情推送服务,下午开始变慢,代码和流量都没变。

 r   us  sy  id  wa  st
 6   48   9   3   0  40
 5   45  10   2   0  43

st 是被偷走的时间:这台虚拟机的 vCPU 有活要干,宿主却把物理 CPU 给了别人,或者在执行实例类型的限额。id 只剩 2–3%,四成被偷,实际能用的 CPU 只有六成左右,r 高是结果不是原因。客户机里看不到是谁在占,能做的是确认实例类型有没有 CPU 积分之类的限制、找云厂商,或者换一台实例。等磁盘是 wa。

这个容器快被 OOM 杀了吗?§11.3.3

场景:一个做日志归档的容器,内存上限 1 GiB。监控报警说内存用到了 98%。你进去看:

memory.max:      1073741824
memory.current:  1052000000
memory.stat:     anon 210000000
                 file 820000000
memory.events:   max 0   oom_kill 0

容器读写文件的 page cache 记在它的 cgroup 名下,98% 里有八成是 file,内核需要时可以回收。真正回收不了的是 anon,只有 200 MB。memory.events 的 max 和 oom_kill 都是 0,说明还没撞过上限。free 报的是宿主,和容器的上限无关;清缓存只是让数字好看,归档还会把缓存读回来。报警该按 anon 或 PSI 设,不按 memory.current。

容器里的负载平均为什么这么高?§11.3.4

场景:你在容器里排查一个编译服务,容器限额 4 颗 CPU,服务现在没有任务。

uptime:    load average: 31.2, 29.8, 27.5
cpu.stat:  usage_usec  +40000 in 10s
           nr_throttled  +0

在常见的容器配置里,/proc/loadavg 没有按容器隔离,uptime 和 top 头部显示的是宿主的负载,包括所有邻居。容器自己的 CPU 用量要看 cgroup:10 秒 40 ms,几乎为零,也没有节流。这时的高负载最多说明宿主很忙,可能影响你,但不是你造成的。负载平均给出三个窗口,不只是 15 分钟。

测试环境快、生产环境慢,该怎么解释?§11.3.3

场景:一个图片转码服务,测试时在一台新节点上单独跑,每张图 40 ms。上线后和十几个服务挤在同一类节点上,每张图 200 ms。它只配了 CPU request(份额),没配 limit。生产上:

cpu.max:       max 100000
nr_throttled:  0
node %idle:    0.5

没设 limit,所以不会节流,nr_throttled 为 0 不能排除 CPU。份额在没人抢时允许多用,在空节点上测出的是突发后的速度;生产节点几乎没有空闲,每个服务只能拿到按份额算的那一份。书里管这叫突发对容量规划的误导。办法是按份额测(在有负载的节点上测,或者临时设一个等于 request 的 limit),按真实需要申请 request。

值得停下来的句子

Since these limits, where present, are encountered long before the physical limits, they are more likely to be in effect and should be checked first.— §11.3.4。本站译:这些限额只要存在,就会远比物理上限先撞上,所以更可能正在起作用,应该先查。

前面十章教的都是物理资源:CPU 满没满、磁盘忙不忙。到了容器和云主机里,这些检查依然要做,但顺序要倒过来。一个限了 2 颗 CPU 的容器跑在 64 核的宿主上,物理 CPU 用到 3% 时它就到顶了;看 mpstat 永远看不到问题。先读 cpu.max、memory.max,再按它们去算利用率,本页「方法」的第二步就是这么来的。

This can lighten the support burden on container operators: people tend to blame what they cannot observe.— §11.3.4。本站译:这能减轻容器运营方的支持负担:人总爱把问题怪到自己看不见的东西上。

这句话说的是让容器看到宿主统计的好处,但反过来也是给排查者的提醒。看不见宿主,就容易把一切慢都推给「吵闹的邻居」;看得见宿主,又容易把宿主的数当成自己的。两种错都来自同一件事:没弄清手上这个数是谁的。每读一个指标,先问它的范围是整台机器、这个 cgroup,还是这个虚拟机。

带走的几条

一句话:先弄清手上的数是谁的,再先查限额、后查物理资源。

先查限额

cpu.max、memory.max、cpuset 比物理资源先到顶。利用率要按限额算,不按 nproc 和 free。

§11.3.4
节流看 cpu.stat

隔几秒读两次,nr_throttled、throttled_usec 在涨就是被掐。宿主空闲、mpstat 正常时也会发生。

§11.3.4
线程数别超过限额

配额所有线程合用。4 个线程配 1 颗 CPU,实测每 100 ms 停约 75 ms,p99 被拉高。

§11.3.3
容器里的老工具报宿主

top 头部、uptime、free、vmstat、mpstat、iostat 多半是整台机器;ps、pidstat 只列自己的进程。

§11.3.4
虚拟机看 st

vmstat 的 st 持续不为零,说明 vCPU 想跑没轮到。客户机里看不到原因,查实例类型或找云厂商。

§11.2.4
内存先分 anon 和 file

memory.current 贴着上限可能只是缓存;anon 在涨、memory.events 的 oom_kill 不为零才危险。

§11.3.3
份额只保证下限

空机器上测出的速度含突发,邻居一多就没了。按份额测,按真实需要申请。

§11.3.3