B3. cgroups і OOM killer
Кожне обмеження «2 CPU, 512 МіБ» у Docker, Kubernetes чи systemd
зводиться до кількох файлів у /sys/fs/cgroup. У цій роботі ви створите
таку групу руками, без жодного контейнерного інструменту, і доведете
процес у ній до межі пам’яті. Так ви побачите те, що
модуль 12 описує словами: malloc майже ніколи
не повертає NULL, а нестача пам’яті закінчується несподіваним
завершенням процесу.
Після цієї роботи ви зможете:
- обмежити процесор, пам’ять і ввід-вивід окремому процесу або службі й перевірити, що обмеження діє;
- відрізнити квоту процесора (
cpu.max) від пріоритету (nice) і пояснити, коли потрібне яке; - прочитати
memory.eventsі журнал ядра після спрацювання OOM killer і сказати, кого й чому було вбито; - пояснити різницю між
memory.maxіmemory.highі вибрати правильне для служби, якій не можна впасти.
Це та сама робота, яку роблять під час розбору інциденту «контейнер перезапустився з OOMKilled»: знайти групу, подивитися лічильники і зрозуміти, обмеження це чи витік.
Завдання
Section titled “Завдання”На віртуальній машині або в контейнері з увімкненою вкладеністю cgroups (див. огляд) створити власну контрольну групу в ієрархії cgroups v2 і показати на ній три види обмежень.
Що зробити:
- Група з увімкненими контролерами
cpu,memoryтаio, у якій працює запущений вами процес. - Обмеження процесора квотою: процес у циклі отримує задану частку,
і це видно в
top. - Обмеження пам’яті через
memory.maxдля програми, що виділяє й записує пам’ять, доки її не вб’є OOM killer у вашій групі. - Те саме з
memory.high: процес лишається живим, але гальмує. - Обмеження вводу-виводу через
io.maxіз заміромddдо й після.
Тестову програму для пам’яті пишіть самі, на C або Python: цикл, який виділяє по 10 МіБ і записує в кожен блок хоча б по байту на сторінку.
Що записати у звіт:
- команди створення групи і вміст
cgroup.controllersтаcgroup.subtree_controlна кожному рівні; - частку процесора з
topпри заданомуcpu.maxі порівняння зnice 19на порожній машині; memory.eventsпісля OOM і відповідний рядок ізjournalctl -k;- ваше пояснення, чим
memory.highвідрізняється відmemory.maxі для якого типу служби ви обрали б кожне.
Готово, коли ви можете підтвердити командою кожне твердження з таблиці «Що треба вміти підтвердити», а звіт містить чотири пункти вище.
Перед початком
Section titled “Перед початком”- Прочитайте в модулі 12 розділи «Підкачка на вимогу» і «Як це насправді в Linux» (частину про overcommit і OOM killer), а в модулі 17 розділ «З чого зроблено контейнер».
- Потрібна система з cgroups v2 і правами root. Vagrant-машина
з архіву курсу підходить, у ній уже є
stress-ngіcgroup-tools. - У контейнері робота піде лише з увімкненою вкладеністю cgroups
і достатніми правами. Якщо
cgroup.subtree_controlне пишеться, переходьте на віртуальну машину.
-
Розвідка.
Terminal window mount | grep cgroup2cat /sys/fs/cgroup/cgroup.controllerssystemd-cgls | head -20Переконайтеся, що це саме v2 (одна ієрархія), і подивіться, як
systemdуже розклав усі служби по групах. -
Власна група.
Створіть каталог у
/sys/fs/cgroup, увімкніть потрібні контролери в батьківськомуcgroup.subtree_control, помістіть у групу процес, записавши його PID уcgroup.procs.Зверніть увагу на правило внутрішніх вузлів: у v2 процеси можуть лежати лише в листках дерева. Спроба покласти процес у групу, яка має дочірні, дасть помилку, і це очікувана поведінка.
-
Обмеження процесора.
cpu.maxу форматі «квота період». Запустіть зациклений процес і подивіться вtop, що він отримує рівно задану частку.Порівняйте з
nice:niceзадає вагу відносно конкурентів, аcpu.maxзадає абсолютну квоту (модуль 7). Процес ізnice 19на порожній машині займе 100%, процес ізcpu.max 50000 100000— рівно 50%, навіть якщо більше нікого немає. -
Обмеження пам’яті й OOM.
memory.maxна 64 МіБ, потім програма, що виділяє й записує пам’ять по 10 МіБ у циклі. Запис обов’язковий: без нього сторінки фізично не існують (модуль 12).Спостерігайте
memory.current,memory.eventsі журнал ядра. -
Тиск замість убивства.
Тепер те саме, але з
memory.highзамістьmemory.max. Процес лишається живим: його гальмують, змушуючи звільняти пам’ять. Подивіться наmemory.pressureі поясніть різницю у звіті. -
Ввід-вивід.
io.maxна конкретний пристрій, потімddі замір швидкості до й після.
Що треба вміти підтвердити
Section titled “Що треба вміти підтвердити”| Твердження | Чим доводиться |
|---|---|
| Система на cgroups v2 | mount | grep cgroup2 |
| Ваша група існує й містить процес | cat <група>/cgroup.procs |
| Процесор обмежено квотою, а не пріоритетом | cat <група>/cpu.max + top |
| Пам’ять обмежено | cat <група>/memory.max |
| OOM спрацював саме у вашій групі | cat <група>/memory.events (поле oom_kill) |
| Ядро це зафіксувало | journalctl -k --grep 'Out of memory' |
Ви розумієте різницю high і max |
письмове пояснення у звіті |
Часті помилки
Section titled “Часті помилки”Контролери не ввімкнені. Файлів cpu.max і memory.max просто немає,
поки контролер не дозволено в cgroup.subtree_control батьківської групи.
Процес не потрапляє в групу. У cgroup.procs пишеться один PID за раз,
а потоки переносяться через cgroup.threads.
systemd забирає групу назад. Створювати групи руками поруч із деревом
systemd можна, але надійніше робити це через systemd-run --scope.
OOM убив не того. Якщо обмеження стоїть на батьківській групі, жертву обиратимуть серед усіх нащадків. Перевірте, що ви обмежили той рівень, який мали на увазі.
Далі, якщо цікаво
Section titled “Далі, якщо цікаво”Обмежити кількість процесів через pids.max і подивитися, як
форк-бомба :(){ :|:& };: перестає бути небезпечною.