CPU steal і накладні витрати віртуалізації в Ubuntu 24.04: як помітити та що робити (Випадок №44)

Було корисно?

Ваш сервіс повільний. Load average виглядає підозріло. Користувачі скаржаться. Але всередині VM «використання» CPU навіть не таке велике. Ви масштабували деплоймент, підсунули більший інстанс — і все одно ніби тягнете через вологий цемент.

Тут і з’являється CPU steal та накладні витрати віртуалізації — тихо, як податок, за який ви не голосували. Якщо ви запускаєте Ubuntu 24.04 на KVM, VMware, Xen або «в хмарі», треба вміти довести (не вгадати), коли гіпервізор є вузьким місцем і які важелі у вас є насправді.

Що таке CPU steal насправді (і чого це не стосується)

CPU steal time — це частина часу, коли ваша гостьова ОС хотіла виконуватися на фізичному CPU, але гіпервізор сказав «не зараз». Це вимірює ядро гостьової системи і показується як %steal (інструменти типу mpstat, top, pidstat) та як поле steal у лічильниках обліку CPU в Linux.

Простими словами: ви заплатили за vCPU. Хост мав інші плани. Те, що в хості виконувалося замість вас — це steal. Ви не бачите цього як високе user або system CPU всередині VM, бо VM не виконувалась. Вона чекала за лаштунками, поки інші гості тримали мікрофон.

Steal не те саме, що throttling

Throttling — це навмисне обмеження: VM обмежують у використанні понад певну квоту (часто через cgroups, моделі CPU credit або політики провайдера). Steal — це поведінка через контенцію: ви готові виконуватись, але не плануєтесь на фізичне ядро, бо хтось інший ним користується.

Steal — це не I/O wait

%iowait — це час очікування завершення I/O, поки CPU бездіяльний. %steal — це час, коли ви могли б виконуватись, але гіпервізор не виділив вам CPU. Обидва уповільнюють додаток. Лише одне вказує на планування на хості.

Що означає «накладні витрати віртуалізації» на практиці

Навіть при нульовому steal віртуальні машини несуть накладні витрати: VM exits, емулювання деяких пристроїв, віртуалізація переривань, витрати на TLB flush, nested page tables та всі той схеми планувальника, що потрібні для множення багатьох віртуальних CPU на меншу кількість фізичних.

На сучасних KVM/VMware ці накладні витрати зазвичай малі при адекватній конфігурації. Коли вони не малі — часто через одне з наступного:

  • Запуск надто багатьох vCPU на VM відносно реальної паралельності (SMP-пеня, затримки планування).
  • Використання oversubscribed хостів (високий steal/ready).
  • Високі темпи переривань (мережа, сховище) через неоптимальні віртуальні пристрої або налаштування offload.
  • Змішування затримко-чутливих робіт із ресурсомісткими batch-навантаженнями на одному хості.

Парафраз ідеї від Werner Vogels (reliability/operations): «Усе ламається постійно; проєктуйте та оперуйте з цим припущенням.» Це стосується й тут: припускайте, що гіпервізор зайнятий, поки не доведете протилежне.

Жарт #1: Якщо вас ніколи не gaslight-или гіпервізором, значить ви цього не бачили. VM каже, що вона проста, а користувачі — що все горить.

Цікаві факти та контекст (корисно знати)

  • Steal з’явився з ери парівіртуалізації Linux: ранні гості Xen могли явно бачити, коли їх дескедульовано, що пізніше стало стандартною концепцією обліку.
  • VMware «CPU Ready» передував більшості Linux-дашбордів: багато підприємств вивчали цю проблему через vCenter-графіки ще до того, як Linux-адміни бачили %steal у своїх інструментах.
  • Cloud «vCPU» ніколи не гарантував «фізичне ядро»: oversubscription — нормальна практика; ви купуєте частку планування, а не зарезервоване місце з вашим ім’ям.
  • Високий steal зазвичай симптом хоста, а не гостя: всередині VM ви не бачите, хто саме шумить, тільки що втрачаєте час планування.
  • Steal може бути близьким до нуля, а ви все одно повільні: накладні витрати віртуалізації можуть проявитися як збільшені витрати на контекстні переключення, накладні переривань і вищу хвостову латентність без очевидних стрибків steal.
  • CPU steal часто має сплески: cron-сценарій сусіда, бекап або ETL можуть створювати періодичні піки, що псуватимуть p99, в той час як середні метрики виглядають нормально.
  • Історично синхронізація часу і віртуалізація були проблемними: старі ядра та гіпервізори мали дрейфи годинника й проблеми з TSC; сучасний Ubuntu 24.04 здебільшого приховує це, але коли час «йде не так», метрики планування швидко стають оманливими.
  • NUMA важить більше, ніж багато хто визнає: розміщення vCPU по сокетах та локальність пам’яті можуть викликати латентність, яка виглядає як «CPU», але насправді — трафік між вузлами пам’яті.
  • Вкладена віртуалізація посилює накладні витрати: запуск гіпервізора всередині VM додає більше VM exits і рівнів планування; гість бачить більшу варіабельність навіть якщо steal виглядає помірним.

Симптоми: як CPU steal маскується під усе інше

CPU steal — майстер маскування, бо гостьова ОС не «зайнята» у звичному сенсі. Ваш поток готовий до виконання, але vCPU не отримує часу на кремнії. Це створює специфічні симптоми:

Шаблони симптомів, які варто розпізнати

  • Високий load average при скромному використанні CPU: завдання, що можуть виконуватись, накопичуються, але user/system CPU всередині VM не зростає пропорційно.
  • Стрибки латентності по незв’язаних ендпоінтах: усе повільніє, не тільки одна підсистема. p99 падає вниз.
  • «Випадкові» таймаути: TLS-рукостискання, запити до БД та споживачі черг таймауt-яться при помірному трафіку.
  • Пропуск пропускної здатності після «успішного» масштабування: додавання vCPU або pod-ів збільшує runnable-потоки, що підвищує контенцію планувальника і може погіршити ситуацію.
  • Soft lockups і пропущені heartbeats: системні демони не встигають виконатись вчасно; моніторинг фіксує флап.
  • Затримки GC виглядають довшими: не тому, що GC змінився, а тому, що процес дескедульований посеред колекції.

Думайте про steal як про приховану чергу перед вашим CPU. Гість бачить чергу, але не касира.

Швидкий план діагностики (перший / другий / третій)

Це порядок тріажу, що економить час. Не імпровізуйте. Ви намагаєтесь відповісти на одне питання: Чи ми відчуваємо нестачу CPU через те, що хост не планує нас, чи тому, що ми дійсно CPU-bound всередині VM?

Перший: доведіть або виключіть steal

  1. Перевірте %steal у часі (mpstat), а не один знімок.
  2. Корелюйте з піками латентності (метрики додатка) або глибиною черги (системні метрики).
  3. Якщо steal постійно >2–5% під час інцидентів — вважайте це реальною контенцією. Якщо він підстрибує до 20–50% — це інцидент навіть якщо графіки CPU виглядають «нормально».

Другий: відділіть «CPU busy» від «CPU blocked»

  1. Перевірте чергу виконання і патерни контекстних переключень (vmstat, pidstat).
  2. Перевірте iowait і латентність диску (iostat), щоб не звинуватити steal за проблеми з диском.
  3. Перевірте тиск пам’яті (free, psi), щоб не сплутати затримки reclaim з контенцією CPU.

Третій: визначте важіль, яким ви насправді володієте

  1. Якщо ви на спільному хмарному інстансі: мігруйте тип/розмір інстансу або переходьте на dedicated hosts/ізольовані ядра.
  2. Якщо ви на своєму KVM/VMware: виправляйте oversubscription, підбір vCPU, вирівнювання NUMA та розміщення гучних сусідів.
  3. Якщо ви в Kubernetes: вирішіть, чи лагодити вузол (на рівні хоста), чи обмежувати pod (cgroups), чи переносити навантаження.

Практичні завдання: команди, інтерпретація, рішення

Ви хотіли реальних дій, а не теорії. Ось конкретні завдання, які можна виконати на гостях Ubuntu 24.04, щоб виявити steal і відрізнити його від інших вузьких місць. Кожне завдання містить (1) команду, (2) що означає вивід, і (3) рішення.

Завдання 1: Підтвердіть, що ви віртуалізовані (і як)

cr0x@server:~$ systemd-detect-virt
kvm

Значення: Ви всередині VM, отже steal — кандидат. Якщо виведе none, steal вам не заважає (дивіться CPU-bound, I/O, блокування або throttling).

Рішення: Якщо віртуалізація підтверджена, продовжуйте перевірки steal. Зверніть увагу на сімейство гіпервізора — воно впливає на те, які метрики доступні на хості.

Завдання 2: Знімок обліку CPU із включеним steal

cr0x@server:~$ top -b -n 1 | head -n 5
top - 10:42:18 up 12 days,  3:21,  2 users,  load average: 8.21, 7.90, 6.44
Tasks: 312 total,   3 running, 309 sleeping,   0 stopped,   0 zombie
%Cpu(s):  9.2 us,  3.1 sy,  0.0 ni, 79.8 id,  0.4 wa,  0.0 hi,  0.3 si,  7.2 st
MiB Mem :  32085.4 total,   1954.7 free,  11892.3 used,  18238.4 buff/cache
MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.  20193.1 avail Mem

Значення: st — це steal. Тут 7.2% — неспокійна цифра. Load average високий при високому idle: класичний підпис контенції.

Рішення: Якщо st небезпечно відмінний від нуля, перестаньте звинувачувати «додаток», поки не виміряєте більше. Перейдіть до серійного зняття даних у часі.

Завдання 3: Серійне зняття steal по ядрах

cr0x@server:~$ mpstat -P ALL 1 10
Linux 6.8.0-41-generic (server)  12/30/2025  _x86_64_  (8 CPU)

10:42:30 AM  CPU   %usr  %nice   %sys %iowait  %irq  %soft  %steal  %idle
10:42:31 AM  all   12.5   0.0    4.0    0.3    0.0   0.5     9.8    72.9
10:42:31 AM    0   10.0   0.0    3.0    0.0    0.0   0.0    18.0    69.0
10:42:31 AM    1    9.0   0.0    2.0    0.0    0.0   0.0     1.0    88.0
...

Значення: Steal нерівномірний по vCPU. Це часто вказує на контенцію планувальника і ефекти розміщення vCPU. Якщо %steal стабільно >2–5% під час повільних періодів — це реальне явище.

Рішення: Якщо steal має сплески, фіксуйте їх під час інцидентів і корелюйте з латентністю. Якщо постійний — думайте про міграцію або зміну розміру; постійний steal — це проблема ємності.

Завдання 4: Порівняйте load average з чергою виконання та заблокованими задачами

cr0x@server:~$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 6  0      0 201312  98244 932112    0    0     1     8  520 1220 10  3 76  1 10
 8  0      0 200984  98244 932120    0    0     0     0  610 1405 12  4 70  0 14

Значення: r (runnable) високий; b (blocked) низький. CPU має помітний st. Це вказує на контенцію планування, а не на блокування через I/O.

Рішення: Якщо b високий — досліджуйте сховище та мережу. Якщо r високий разом зі steal — фокусуйтеся на контенції хоста або підборі vCPU.

Завдання 5: Перевірте, чи вас CPU-тротлять через cgroups (контейнери/systemd)

cr0x@server:~$ systemctl show -p CPUQuota -p CPUQuotaPerSecUSec docker
CPUQuota=
CPUQuotaPerSecUSec=infinity

Значення: Немає системного CPU-квотування на рівні сервісу. У Kubernetes або контейнерному рантаймі throttling може все ще відбуватись на рівні pod/container cgroup.

Рішення: Якщо квоти існують — усуньте throttling першочергово; throttling може виглядати як «повільний CPU» навіть при нульовому steal.

Завдання 6: Шукайте докази тротлінгу контейнера (cgroup v2)

cr0x@server:~$ cat /sys/fs/cgroup/cpu.stat
usage_usec 389432187
user_usec 312903110
system_usec 76529077
nr_periods 21903
nr_throttled 0
throttled_usec 0

Значення: nr_throttled і throttled_usec показують тротлінг cgroup. Тут нулі — отже гість не сам себе тротлить.

Рішення: Якщо тротлінг не нульовий і зростає — підніміть ліміти/запити, зменшіть паралелізм або перемістіть до виділених CPU-пулів; не звинувачуйте steal раніше часу.

Завдання 7: Перевірте pressure stall information (PSI) на сигнали насиченості CPU

cr0x@server:~$ cat /proc/pressure/cpu
some avg10=2.14 avg60=1.05 avg300=0.34 total=9823412
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Значення: PSI CPU some вказує час, коли хоча б одне завдання чекало CPU. Якщо це зростає разом з %steal, у вас контенція. Якщо PSI високий при низькому steal — ви, ймовірно, CPU-bound всередині VM.

Рішення: Високий PSI + високий steal: мігруйте/змінюйте тип інстансу або зменшуйте overcommit хоста. Високий PSI + низький steal: оптимізуйте використання CPU додатком або додайте реальний обчислювальний ресурс.

Завдання 8: Відокремте I/O wait від steal за допомогою iostat

cr0x@server:~$ iostat -xz 1 5
Linux 6.8.0-41-generic (server)  12/30/2025  _x86_64_  (8 CPU)

avg-cpu:  %user %nice %system %iowait  %steal  %idle
          11.9  0.00    3.8    0.4     9.7    74.2

Device            r/s     w/s   rKB/s   wKB/s  await  svctm  %util
nvme0n1          2.1     3.4    45.2    88.3   1.20   0.35   0.3

Значення: Disk await низький, %util мізерний; сховище не є вузьким місцем. Тим часом %steal ~10%.

Рішення: Не тюнінуйте диски. Ескалюйте до планувальної контенції хоста або підбору/розміщення VM.

Завдання 9: Перевірте навантаження softirq мережі (накладні віртуальної NIC можуть виглядати як проблеми CPU)

cr0x@server:~$ cat /proc/softirqs | head
                    CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
HI:                 12          8          4          7          6          5          4          6
TIMER:        1234567    1122334    1099887    1044556    1001222     998877     912345     887766
NET_TX:         23456      19876      21011      18765      16543      17002      16001      15888
NET_RX:        345678     312345     321002     300111     280987     275444     260999     255888

Значення: Великі лічильники NET_RX/NET_TX можуть вказувати на високі швидкості пакетів, що підвищує накладні витрати CPU і джітер планування — особливо при невеликих пакетах або поганих offload-настройках.

Рішення: Якщо мережа гаряча, перевірте тип NIC (virtio vs емульований), offloads і розподіл переривань. Але не плутайте це зі steal; вони можуть співіснувати.

Завдання 10: Перевірте топологію vCPU, яку бачить гість

cr0x@server:~$ lscpu | egrep 'CPU\\(s\\)|Thread|Core|Socket|NUMA'
CPU(s):                               8
Thread(s) per core:                   1
Core(s) per socket:                   8
Socket(s):                            1
NUMA node(s):                         1

Значення: Цей гість бачить просту топологію 1-сокет. Якщо в VM видно кілька сокетів/NUMA і навантаження не NUMA-орієнтоване, ви отримаєте неприємні хвостові затримки без великих показників steal.

Рішення: Для затримко-чутливих сервісів надавайте перевагу меншій кількості сокетів і розумним числам vCPU; уникайте великих SMP-VM, якщо вам не потрібна справжня паралельність.

Завдання 11: Перевірте джерело часу/таймкіпінг (воно може підсилювати біль планування)

cr0x@server:~$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock

Значення: kvm-clock очікуваний при KVM. Дивні clocksources або часті стрибки часу роблять аналіз латентності оманливим і можуть ламати логіку таймаутів.

Рішення: Якщо спостерігаються стрибки часу (логи показують «time went backwards»), дослідіть налаштування часу гіпервізора й NTP/chrony. Не ганяйтеся за фантомними багами продуктивності.

Завдання 12: Зніміть докази затримок планування за допомогою perf sched (коротко, контрольовано)

cr0x@server:~$ sudo perf sched record -a -- sleep 10
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 6.214 MB perf.data (12698 samples) ]
cr0x@server:~$ sudo perf sched latency --sort max | head
Task                  |   Runtime ms  | Switches | Avg delay ms | Max delay ms
postgres:checkpointer |      120.11   |    1321  |       0.18   |      22.34
nginx:worker          |       98.45   |    1887  |       0.12   |      18.02

Значення: Великі max scheduling delays вказують, що гість не отримує CPU вчасно. Це може бути через steal (контенція гіпервізора) або через насичення CPU в гостевій ОС.

Рішення: Якщо %steal високий одночасно — це контенція хоста. Якщо %steal низький — ймовірно, ви перевантажені всередині VM (або має місце блокування потоків).

Завдання 13: Перевірте повідомлення ядра на soft lockups і зупинки планування

cr0x@server:~$ journalctl -k --since "1 hour ago" | egrep -i "soft lockup|rcu_sched|stall|watchdog" | tail -n 5
Dec 30 10:11:02 server kernel: watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [java:28199]
Dec 30 10:11:02 server kernel: rcu: INFO: rcu_sched detected stalls on CPUs/tasks: { 3-... } (detected by 2, t=5250 jiffies)

Значення: Stall-и можуть виникати при екстремальному steal, бо гість просто не отримує планування часто. Також вони можуть бути через реальні lockup-и CPU, але в VM контенція — частий тригер.

Рішення: Якщо це корелює зі сплесками steal — ескалюйте питання ємності платформи/хоста. Якщо корелює з завантаженням CPU і нульовим steal — досліджуйте runaway-потоки або баги ядра.

Завдання 14: Підтвердіть підказки щодо гіпервізора (DMI) для ескалації

cr0x@server:~$ sudo dmidecode -s system-product-name
Standard PC (Q35 + ICH9, 2009)

Значення: Це допомагає ідентифікувати стек віртуалізації (тут QEMU machine type). Корисно при створенні тікета або зіставленні відомих проблем.

Рішення: Зберігайте це разом із графіками steal і часовими мітками інциденту. Коли ескалюєте — приносіть докази, а не відчуття.

Завдання 15: Якщо підозрюєте overcommit хоста — перевірте steal за допомогою лічильників на кшталт node_exporter (з боку гостя)

cr0x@server:~$ awk '/^cpu /{print "steal_jiffies=" $9}' /proc/stat
steal_jiffies=981223

Значення: 9-е поле CPU-рядка в /proc/stat — це steal time в jiffies для агрегованого CPU. Так побудовані експортери для обчислення ставки steal. Корисно перевірити, що ваше моніторинг бачить те ж саме.

Рішення: Якщо ваші дашборди показують нуль steal, але /proc/stat змінюється — моніторинг налаштований неправильно. Виправте моніторинг перш ніж «лагодити» продакшн.

Завдання 16: Перевірте, чи доступні віртуалізаційні можливості (paravirt hints)

cr0x@server:~$ dmesg | egrep -i "kvm-clock|pvclock|Hypervisor detected|Booting paravirtualized kernel" | tail -n 5
[    0.000000] Hypervisor detected: KVM
[    0.000000] kvm-clock: Using msrs 4b564d01 and 4b564d00

Значення: Paravirtualized clock і виявлення гіпервізора — нормальні і зазвичай корисні. Якщо ви не бачите очікуваних вірт-фіч, можливо використовуються емульовані пристрої або відсутні оптимізації, що збільшує накладні витрати.

Рішення: Якщо відсутні virt-функції — перегляньте конфігурацію VM (virtio-драйвери, passthrough CPU model, правильний machine type). Виправлення цього може знизити накладні витрати навіть при низькому steal.

Що ви контролюєте проти того, що контролює провайдер

Steal time політично незручний: коренем часто є «хтось інший на хості». У публічній хмарі цей хтось не прийде на ваш інцидентний дзвінок. Ваше завдання — визначити, які важелі існують у вашому середовищі, і тягнути ті, що приносять результат.

Якщо ви працюєте на публічній хмарі в спільній оренді

  • Ви можете: змінити сімейство інстансу, змінити розмір up/down, переміститися між регіонами/зонами доступності, використовувати dedicated hosts/instances, застосувати CPU pinning там, де доступно, налаштувати autoscaling і паралелізм, зменшити кількість vCPU, щоби відповідати реальній паралельності.
  • Ви не можете: безпосередньо виправити overcommit хоста, вижити шумових сусідів, контролювати версії ядра на хості або змінювати політику планування гіпервізора.

Практична порада: якщо сплески steal регулярні і корелюють з робочими годинами, вважайте це класом невідповідності ємності. Перейдіть на більш передбачуваний рівень (dedicated, reserved performance, isolated core пропозиції). Платіть трохи більше — це дешевше, ніж пояснювати p99, який переслідує вашу команду.

Якщо ви керуєте власним KVM/Proxmox/OpenStack

  • Ви можете: встановлювати співвідношення vCPU:pCPU, пінити vCPU, ізолювати ядра хоста, налаштовувати планувальник хоста, примушувати розміщення робочих навантажень, моніторити черги виконання хоста і не змішувати latency-навантаження з batch.
  • Ви не можете: обійти закони фізики; якщо ви oversubscribe і всі будуть зайняті, steal — це коректний результат.

Якщо ви працюєте на VMware

VMware називає споріднену метрику «CPU Ready». %steal гостя часто корелює, але не ідеально. Операційний сенс однаковий: VM готова, але не запланована. Для остаточного вердикту використовуйте метрики з боку хоста, але не ігноруйте гостевий steal — це часто перший сигнал.

Якщо ви запускаєте Kubernetes на VM

Ви маєте два рівні планувальної контенції:

  • Linux cgroups (pod limits) можуть тротлити CPU — це не steal, але відчувається схоже.
  • Планування гіпервізора може «вкрасти» CPU у вузла — ку-лєт не вирішить це.

Операційно: якщо на вузлі зростає steal, пересадка pod-ів у межах того ж node pool часто ні до чого не призведе. Потрібна заміна вузла, інший тип інстансу або виділена ємність.

Три корпоративні міні-історії (анонімізовані, правдоподібні, болючі)

Міні-історія 1: Інцидент через хибне припущення

Вони запускали API платежів на Ubuntu VM. Інцидент почався як «база даних повільна». Команди додатку бачили таймаути, SRE бачили зростання load average, і всі дивилися на графіки бази як на щось персональне.

Хибне припущення: «CPU в порядку, бо використання лише 25%». Це число було всередині гостя. Хости були oversubscribed, і один аналітичний tenant у тому ж кластері гіпервізора запустив CPU-важку роботу. Вищенаведені VM показували %steal близько 15–30% протягом сорока хвилин. Не достатньо, щоб зафіксувати CPU на 100%, але достатньо, щоб підірвати хвостову латентність. Запити таймаутили, retries накопичувалися, і черги піднімали тиск.

Дебаг затягувався, бо дашборди команди не містили steal. Їхній node exporter був налаштований, але Grafana панель відображала лише user/system/idle. Тож наратив інциденту став «додаток повільний з невідомих причин», що є ввічливим способом сказати «ми не вимірюємо те, що важливо».

Коли вони додали rate(node_cpu_seconds_total{mode="steal"}) у стандартний node-панель, підпис контенції став очевидним. Фактичне рішення було нудним: перемістити платіжний шар на менш-oversubscribed кластер і обмежити розміщення batch-tenant під час робочих годин. Постмортемний action item не був «оптимізувати SQL», а «перестати припускати, що CPU-використання всередині VM — повна історія».

Міні-історія 2: Оптимізація, що відкотилася

Платформна команда вирішила «допомогти» Java-сервісу, подвоївши йому vCPU: з 8 до 16. Їх логіка була проста: більше ядер — більше пропускної здатності. Сервіс дійсно став трохи швидшим у синтетичних тестах, і цього вистачило, щоб оголосити перемогу і розгорнути зміни в продакшн.

Потім p99 латентність стала гіршою. Не завжди. Лише під навантаженням. Ось як працюють ці грімлінги: вони чекають, поки ви вже впевнені.

Кластер хостів був помірно oversubscribed. 16-vCPU VM важче планувати, ніж 8-vCPU VM, коли гіпервізор намагається коспланувати vCPU чесно. Під контенцією VM проводила більше часу в очікуванні слотів CPU. Гостьовий %steal піднявся. Додаток також збільшив розміри thread pool, бо «побачив» більше CPU, що підвищило runnable-потоки і контенцію блокувань. Вони покращили здатність системи боротися сама з собою.

Відкат — повернення до 8 vCPU і обмеження конкурентності — стабілізував латентність. Остаточне поліпшення було більш нюансованим: перехід на CPU-оптимізовану сімейство інстансів і використання меншої кількості швидших vCPU з кращою продуктивністю на ядро. Урок: масштабування числа vCPU може збільшити латентність планування й накладні витрати. Більше не завжди означає швидше; іноді це просто більше.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Інфраструктурна команда мала звичку: кожен production-нод мав дашборд зі steal, PSI і простий алерт «підозра на контенцію хоста», коли steal перевищував малий поріг кілька хвилин. Ніхто не був вражений. Це не було гламурно. Але це було, як паски безпеки.

Одного вівторка клієнт повідомив про періодичну повільність. Графіки додатка виглядали переважно нормально, але p95 латентності ключового ендпоінту стрибав кожні 20–30 хвилин. Перший погляд на ноду показав сплески %steal, що майже ідеально збігалися з шаблоном. Без здогадок. Без двогодинних дебатів про garbage collection.

Вони віднесли інцидент як «контенція платформи», перемістили навантаження до виділеного пулу нод, і проблема зникла. Пізніше знайшли, що періодичні сплески викликані задачами обслуговування на інших гостях того ж кластера хостів. Команда не потребувала доказати, хто сусід; їм потрібно було просто захистити сервіс.

Практика, що врятувала їх — не хитрий тюнк в ядрі. Це були правильні метрики, вже на графіках, і шлях ескалації, що не залежав від настрою когось. Нудне перемагає знову.

Жарт #2: Найкращий фікс продуктивності іноді — це переселитися. Це як переїхати в іншу квартиру, бо сусід практикує гру на барабанах — технічно не ваша помилка, але все одно ваша проблема.

Поширені помилки: симптом → корінь → виправлення

Цей розділ свідомо прямий. Ось помилки, що тримають інциденти довше, ніж потрібно.

1) Високий load average, при цьому CPU «idle» високий

Симптом: Load average зростає, user CPU лишається скромним, латентність додатка підвищується.

Корінь: CPU steal time (контенція хоста) або сильні затримки планування.

Виправлення: Підтвердіть %steal через mpstat. Якщо стійко — мігруйте на менш завантажену ємність, зменшіть vCPU або перейдіть на dedicated hosts. Якщо ви власник гіпервізора — зменшіть overcommit і ізолюйте гучні робочі навантаження.

2) «Ми масштабували, і стало гірше»

Симптом: Збільшення vCPU або реплік підвищує хвостову латентність.

Корінь: Великі SMP-VM важче планувати; збільшення конкурентності підвищує runnable-потоки і контенцію блокувань; хост oversubscribed.

Виправлення: Масштабуйте горизонтально з більшим числом менших VM (якщо є ємність) або переходьте на краще сімейство інстансів. Обмежуйте конкурентність. Для latency-сервісів віддавайте перевагу меншій кількості vCPU з вищою продуктивністю на ядро.

3) Сплутування cgroup-тротлінгу зі steal

Симптом: Сервіс повільний, CPU використання стабілізується, steal близько нуля.

Корінь: CPU quota тротлінг (nr_throttled зростає) або занадто низькі ліміти в Kubernetes.

Виправлення: Перевірте /sys/fs/cgroup/cpu.stat. Налаштуйте ліміти/запити, використовуйте QoS Guaranteed для критичних навантажень або перемістіть до виділених CPU-ноди.

4) Звинувачення диску через «iowait є»

Симптом: Невеликий %wa з’явився, додаток повільний, люди кричать «диск».

Корінь: CPU steal або затримка планування, в той час як сховище в порядку.

Виправлення: Використайте iostat -xz для перевірки await і %util. Якщо диск не навантажений, а steal високий — перестаньте тюнити сховище.

5) Не знімати метрики під час інциденту

Симптом: «Ми перевірили пізніше — все виглядало нормально.»

Корінь: Steal буває переривчастим; ви його пропустили.

Виправлення: Додайте легкий скрипт для capture під час інциденту (mpstat/vmstat/iostat snapshots), увімкніть постійний моніторинг і алерт на sustain-ний steal вище порогу.

6) Сприйняття steal як «нормального хмарного шуму»

Симптом: Регулярні стрибки латентності приймаються як доля.

Корінь: Невідповідність класу інстансу; спільна оренда занадто контенційна для SLA.

Виправлення: Використовуйте виділену/ізольовану ємність для критичних затримко-чутливих сервісів, або перейдіть на рівень, де передбачуване планування — частина продукту.

7) Надмірний тюнінг ядра і ігнорування розміщення

Симптом: Тижні налаштувань sysctl, без сталого покращення.

Корінь: Контенція хоста; тюнінг не посперечається з планувальником, якщо вас не планують.

Виправлення: Вирішіть ємність і розміщення першочергово. Тюньте після і лише з вимірюваним ефектом.

Чеклісти / покроковий план

Чекліст A: Коли VM повільна і ви підозрюєте накладні витрати віртуалізації

  1. Підтвердіть віртуалізацію: systemd-detect-virt.
  2. Зробіть знімок CPU зі steal: top (шукайте st).
  3. Вибірково знімайте steal у часі: mpstat -P ALL 1 60 під час інциденту.
  4. Перевірте чергу виконання і steal разом: vmstat 1 60.
  5. Виключіть диск: iostat -xz 1 10.
  6. Виключіть тротлінг cgroup: cat /sys/fs/cgroup/cpu.stat.
  7. Перевірте PSI: cat /proc/pressure/cpu і пам’ять PSI за потреби.
  8. Якщо steal підтверджено — вирішіть: міграція/зміна розміру/виділення vs тюнінг гостя.

Чекліст B: Якщо ви контролюєте гіпервізор (KVM/VMware) і steal високий

  1. Знайдіть overcommit CPU і тиск черги на хості (інструменти на боці хоста).
  2. Зменшіть співвідношення vCPU:pCPU для latency-кластерів.
  3. Не змішуйте batch CPU-hog з latency-сервісами на тих же хостах/пулаx.
  4. Піньте або ізолюйте CPU для критичних навантажень, якщо ваша операційна модель дозволяє.
  5. Перевірте вирівнювання NUMA: уникайте розкидання по сокетах без потреби.
  6. Використовуйте virtio-пристрої і сучасні CPU-моделі; уникайте емулювання.
  7. Повторно тестуйте з mpstat і p95/p99 додатка після кожної зміни.

Чекліст C: Для Kubernetes-нoдів на VM

  1. Перевірте steal на вузлі (mpstat) і тротлінг контейнерів (cpu.stat).
  2. Якщо steal високий: замініть ноду(и) або перемістіть до іншого node pool/класу інстансу.
  3. Якщо тротлінг високий: налаштуйте ліміти, використайте policies CPU manager або змініть QoS.
  4. Не «виправляйте» це збільшенням реплік без розуміння контенції; ви можете посилити проблему.

Операційні пороги (суб’єктивно, корисно)

  • Steal < 1% більшість часу: підходить для багатьох навантажень.
  • Steal 1–5% стійко: перевірте для latency-сервісів; ймовірна контенція.
  • Steal 5–10% стійко: очікуйте видимий вплив для користувачів; трактуйте як платформну проблему.
  • Steal > 10% під час інцидентів: ескалюйте і мігруйте; тюнінг всередині VM зазвичай театральний.

FAQ

1) Який відсоток CPU steal вважати «поганим»?

Для batch-навантажень кілька відсотків можуть бути прийнятні. Для latency-сервісів стійко >2–5% — це реальна проблема. Сплески >10%, що збігаються з p99-стрибками — фактично зізнання в проблемі.

2) Чи може steal бути нульовим, а я все одно відчувати накладні витрати віртуалізації?

Так. Накладні витрати можуть походити від переривань, VM exits, NUMA-ефектів і емулювання пристроїв. Це проявляється як вищі витрати CPU на запит і гірша хвостова латентність без великого підпису steal.

3) Як Linux steal співвідноситься з VMware CPU Ready?

Вони концептуально подібні: «готовий до виконання, але не запланований». CPU Ready вимірюється на хості і часто є найбільш авторитетним у VMware-середовищах. Гостьовий steal корисний для первинної діагностики.

4) Чому load average росте, коли CPU всередині VM виглядає idle?

Load враховує runnable-завдання. Якщо ваш vCPU не запланований (steal), завдання залишаються runnable довше, що підвищує load average, хоча guest не споживає user/system CPU.

5) Чи завжди CPU steal — це noisy neighbor?

Часто — так. Але це можуть бути також роботи з обслуговування хоста, живі міграції, зміни частот CPU через теплові обмеження або рішення про oversubscription, прийняті вашою віртуалізаційною командою.

6) Чи просто перейти на більші інстанси, щоб виправити steal?

Іноді допомагає, іноді — гірше. Більші VM важче планувати під контенцією. Краще переходити на менш завантажений клас (dedicated/isolated cores, compute-optimized) замість просто збільшення числа vCPU.

7) Як правильно алертити на steal?

Алертуйте на стійку ставку steal, а не на одиничні сплески: наприклад, steal >2% протягом 5–10 хвилин на нодах latency-сервісів. Корелюйте з p95/p99 латентністю, щоб уникнути помилкових тривог для batch-рівнів.

8) Чи можна виправити steal всередині гостя тюнінгом ядра?

Не дуже. Ви можете зменшити попит на CPU (менше потоків, менше busy-polling), що зменшить потребу у CPU, але не змусить гіпервізор запланувати вас частіше. Steal — це проблема ємності/розміщення.

9) Що з CPU frequency scaling і симптомами схожими на steal?

Зміна частоти впливає на те, скільки роботи ви робите за одиницю часу, але не показується як steal. Якщо steal низький, а пропускна здатність зменшилась, перевірте продуктивність на ядро, turbo-поведінку і різницю моделей CPU між хостами.

10) Чи вирішує все пінінг vCPU?

Пінінг може зменшити джітер для конкретних навантажень, але обмежує гнучкість і може знизити загальне використання. Це інструмент для критичних сервісів зі стабільними потребами CPU, а не універсальне вирішення для поганої планування ємності.

Висновок: наступні кроки, що дійсно дають ефект

Коли Ubuntu 24.04 працює в VM, «використання» CPU — лише половина історії. Steal time — відсутня глава: час, коли ваше навантаження хотіло виконуватись, але не дозволяли. Якщо ви цього не вимірюєте, ви неправильно діагностуєте інциденти, марнуєте час на тюнінг невірних підсистем і випадково погіршуєте продуктивність через добре намірене масштабування.

Зробіть наступне, у цьому порядку:

  1. Додайте steal до стандартних дашбордів (і перевірте, що це відповідає /proc/stat).
  2. Налаштуйте алерт на стійкий steal для вузлів latency-критичних сервісів.
  3. Під час наступного інциденту зніміть mpstat, vmstat, iostat і cgroup cpu.stat за відповідне вікно.
  4. Якщо steal підтверджено, перестаньте тюнити всередині VM і змініть розміщення/ємність: міграція, зниження overcommit або купівля виділеного планування.
  5. Правильно підійміть розмір vCPU: менше, швидші ядра часто кращі за більше, повільні, контенційні vCPU для хвостової латентності.

Найкраще: як тільки ви можете довести steal, у вас з’явиться правильний аргумент з правильною командою. І ви припините допитувати базу даних щодо злочинів, скоєних гіпервізором.

← Попередня
PC HDR: чому це дивовижно… і чому іноді жахливо
Наступна →
ZFS: Використання NVMe як SLOG — коли це ідеально і коли надмірно

Залишити коментар