Перевыделение памяти в Linux и admin_reserve_kbytes

archive view archive save

ram-tux-logo Перевыделение (overcommitting) оперативной памяти в Linux, особенно на рабочих серверах, является величайшим Злом, и это Зло в Linux разрешено по-умолчанию - vm.overcommit_memory=0

По-умолчанию (vm.overcommit_memory=0) Linux выделяет памяти больше чем есть в наличии, при этом используется эвристический анализ определения выделяемой процессу памяти. Т.е. выделяя оперативки больше чем есть, Linux ещё дополнительно расходует ценные системные ресурсы на эвристику, которая не во всех случаях может давать положительный результат. Эвристический анализ - это, как пальцем в небо.

Более того, наличие возможности перевыделить памяти поощряет говнокодеров говнокодить кривые приложения в стиле "хуяк-хуяк и в продакшин" без реализации кода для надлежащей утилизации неиспользуемой приложением памяти.

huyak-huyak-program-logo

Кому сложно понять что такое перевыделение памяти, то для сравнения можно привести пример с продажей билетов, скажем в кино. К примеру, в кинотеатре всего 100 мест, а мы продали 1000 в надежде, что не все купившие билеты дойдут до кинотеатра и востребуют свои места. Но, "сталося не так як гадалося":

  • 100 первых пришедших в кино заняли все доступные места;
  • 100 по дороге е.анулось, ещё 100 обо.ралось, и до кинотеатра не дошло (ура товарищи);
  • 700 оставшихся таки дошли до кинотеатра, но понятно, что мест им не осталось, в гневе они в хлам развалили тот кинотеатр, и в конечном итоге кина не досталось никому.

Дабы подобного беспредела не происходило на Linux сервере мы обязательно должны ограничить рамки перевыделения памяти, что дополнительно избавит от необходимости тратить ресурсы на эвристику, например:

vm.overcommit_memory=2
vm.overcommit_ratio=300

Обычно, относительно "вменяемые" приложения при запуске запрашивают не более чем в 3 раза больше памяти, чем им реально требуется, а значит vm.overcommit_ratio=300 должно быть вполне достаточно в большинстве случаев. При таком раскладе будет доступно оперативки: РАМ * (overcommit_ratio/100) + SWAP. Скажем, у нас РАМ 2048 МБ и СВОП 5120, тогда получим: 2048 * 3 + 5120 = 11264 МБ около того.

vm.overcommit_memory=2 полезно даже в тех случаях, когда в системе работают более-менее "вменяемые" и проверенные приложения, - ведь не факт, что они всегда будут работать стабильно. Ведь даже самые проверенные и надёжные приложения (как и люди) время от времени, по различным причинам, могут сойти с ума начав "форкать чилдов" и запрашивать всё новые и новые порции памяти (перенаселение, голодовка, и песдец).

vm.overcommit_memory=2 совместно с vm.overcommit_ratio=300 определяет границы реальных возможностей системы виртуальной памяти, а OOM killer (OOM: out-of-memory) в свою очередь прибивая пизданутые процессы и/или их чилдов помогает остальным процессам пребывать в равновесии с системными ресурсами.

https://ru.wikipedia.org/wiki/Скрижали_Джорджии

  1. Пусть земное население никогда не превышает 500.000.000, пребывая в постоянном равновесии с природой
  2. Разумно регулируйте рождаемость, повышая ценность жизненной подготовки и многообразия человечества
  3. Найдите новый живой язык, способный объединить человечество.
  4. Проявляйте терпимость в вопросах чувств, веры, традиций и им подобных
  5. Пусть справедливые законы и беспристрастный суд встанут на защиту народов и наций
  6. Пусть каждая нация сама решает свои внутренние дела, вынося на мировой суд общенародные проблемы.
  7. Избегайте мелочных судебных тяжб и бесполезных чиновников.
  8. Поддерживайте равновесие между личными правами и общественными обязанностями.
  9. Превыше всего цените правду, красоту, любовь, стремясь к гармонии с бесконечностью.
  10. Не будьте раковой опухолью для земли, природе тоже оставьте место!

При vm.overcommit_memory=1 система считает, что в наличии неограниченное количество памяти и выделяет её без ограничений до тех пор пока она вся реально не закончится.

Когда vm.overcommit_memory=2 (overcommit 'never') нам нужно изменить значение по-умолчанию admin_reserve_kbytes, для определения которого нужно понимать смысл аббревиатур VSZ и RSS.

VSZ and RSS Linux memory

Что такое VSZ и RSS? Ответ нам даст man ps: VSZ - virtual memory size; RSS - resident set size, the non-swapped physical memory that a task has used (in kiloBytes).

$ man ps|grep -A 3 VSZ
...
STANDARD FORMAT SPECIFIERS
...
vsz VSZ virtual memory size of the process in KiB
 (1024-byte units). Device mappings are currently
 excluded; this is subject to change. (alias
 vsize).
 
$ man ps|grep -A 3 RSS
The SIZE and RSS fields dont count some parts of a process including
the page tables, kernel stack, struct thread_info, and struct
task_struct. This is usually at least 20 KiB of memory that is always
resident. SIZE is the virtual size of the process (code+data+stack).
--
rss RSS resident set size, the non-swapped physical
 memory that a task has used (in kiloBytes).
 (alias rssize, rsz).

Короче:

  • VSZ - виртуально выделенная память, но не означает, что вся она используется. Кто в теме, тот в курсе, что Linux на запрос приложения перевыделяет (overcommitting) памяти больше, чем есть в наличии - прямо как американцы печатают баксов больше, чем фактически обеспечено золотовалютным резервом;
  • RSS - фактически занимаемый в данный момент размер оперативки без учёта страниц перемещённых в swap.

man admin_reserve_kbytes

https://www.kernel.org/doc/Documentation/sysctl/vm.txt

admin_reserve_kbytes

The amount of free memory in the system that should be reserved for users with the capability cap_sys_admin.

admin_reserve_kbytes defaults to min(3% of free pages, 8MB)

That should provide enough for the admin to log in and kill a process, if necessary, under the default overcommit 'guess' mode.

Systems running under overcommit 'never' should increase this to account for the full Virtual Memory Size of programs used to recover. Otherwise, root may not be able to log in to recover the system.

How do you calculate a minimum useful reserve?

sshd or login + bash (or some other shell) + top (or ps, kill, etc.)

For overcommit 'guess', we can sum resident set sizes (RSS). On x86_64 this is about 8MB.

For overcommit 'never', we can take the max of their virtual sizes (VSZ) and add the sum of their RSS. On x86_64 this is about 128MB.

Changing this takes effect whenever an application requests memory.

Что такое cap_sys_admin и с чем его едят

cap_sys_admin - это флаг определяющий набор административных возможностей (capabilities) приложения/программы/пользователя. Подробнее в man capabilities

Отобразить список процессов и назначенный им список capabilities-флагов можно с помощью pscap из пакета libcap-ng-utils:

# apt-get install libcap-ng-utils
 
or
 
# yum install libcap-ng-utils
 
# pscap|less
ppid  pid   name        command           capabilities
1     312   root        udevd             full
1     950   named       named             net_bind_service, sys_resource +
1     966   root        rngd              full
20532 969   root        sshd              full
972   998   root        su                full
998   999   root        bash              full
999   1012  root        screen            full
1     1099  root        mysqld_safe       full
1     1884  root        master            full
1     1936  root        crond             full
1     1968  root        atd               full
1     2043  ntp         ntpd              net_bind_service, sys_time +
1     2147  root        mingetty          full
1     2149  root        mingetty          full
1     2151  root        mingetty          full
1     2153  root        mingetty          full
1     2155  root        mingetty          full
1     2157  root        mingetty          full
1     3694  root        screen            full
3694  3695  root        bash              full
3694  3710  root        bash              full
3694  3724  root        bash              full
3694  3737  root        bash              full
3694  3751  root        bash              full
3694  3764  root        bash              full
3764  3777  root        top               full
1     12799 root        nginx             full
1     13714 root        httpd             full
13714 13716 root        rotatelogs        full
13714 13717 root        rotatelogs        full
13714 13718 root        rotatelogs        full
13714 13719 root        rotatelogs        full
1     14780 root        rsyslogd          full
3724  17056 root        less              full
1     20532 root        sshd              full

Как распределяется admin_reserve_kbytes

Ответ: а хрен его знает, документация по данному флагу об этом умалчивает...

В сети куча постов с копи/пастой мнения, что увеличив значение admin_reserve_kbytes мы уже якобы "гарантируем" себе возможность логина в вот-вот зависающей машине, - для пользователя root возможно, но для остальных не факт!

Давайте "погульчитаем" документацию по admin_reserve_kbytes где говорится, что:

  1. "The amount of free memory in the system that should be reserved for users with the capability cap_sys_admin.", - резервируется размер свободной оперативки для пользователей с возможностями cap_sys_admin.
  2. "How do you calculate a minimum useful reserve? sshd or login + bash (or some other shell) + top (or ps, kill, etc.) ... For overcommit 'never', we can take the max of their virtual sizes (VSZ) and add the sum of their RSS." - если перевыделение памяти запрещено (overcommit 'never'), то значение должно составлять сумму VSZ + RSS, очевидно всех процессов запущенных системой под такого пользователя и им самим же, которому присвоен флаг cap_sys_admin.

Замечено, что на процессы запускаемые от root-a через systemd эта самая зарезервированная в размере admin_reserve_kbytes оперативка не выделяется, а просто себе болтается без дела, и в этом можно убедится опытным путём.

Опыт №1

Ручками сложить сумму VSZ (ака VmSize) + RSS (ака VmRSS) всех процессов из списка pscap|less по каждому отельному /proc/PID/status будет довольно геморно, и поэтому давайте "наговнокодим" баш-скрипт автоматизирующий всю эту работу (назовём его ./vsz-total.sh):

Только после полного отключения блокировщика скриптов и рекламы на этом месте появится полезная подсказка/ссылка/код/пример конфигурации/etc!

Запустив скрипт получим сумму значений VmSize и VmRSS всех процессов выданных прогой pscap:

$ ./vsz-total.sh
VmSize total: 1988240 kB
VmRSS total: 178204 kB
---
VmSize+VmRSS: 2166444 kB
VmRSS+(VmRSS/2): 267306 kB

Из результата видим, что рекомендуемое значение для admin_reserve_kbytes основанное на "VSZ + RSS" в данном случае составило 2166444 kB.

Увы, документация не уточняет какая именно "amount of free memory" резервируется параметром admin_reserve_kbytes = это от суммы СВОПа+РАМы или от одной РАМы? Вероятнее всего, от суммы СВОПа+РАМы.

Кроме того, сказано, что: "Changing this takes effect whenever an application requests memory.", - изменения вступают в силу, когда приложения запрашивают память.

Потому, как все системные процессы из списка pscap уже запросили память при запуске и она была им выделена, то в условиях:

# sysctl -w vm.overcommit_memory=2
# sysctl -w vm.overcommit_ratio=300
 
$ cat /proc/meminfo|less
...
CommitLimit: 10636404 kB
Committed_AS: 5546516 kB

Когда до достижения ограничения CommitLimit у нас осталось около 4,5 ГБ, мы выполним рекомендацию документации sysctl -w vm.admin_reserve_kbytes=2166444 (зарезервируем 2+ ГБ) и попробуем запустить Chromium, который при запуске перевыделяет 3+ ГБ - т.е. минимум на 0,5 превышаем допустимый CommitLimit, и получаем:

# less /var/log/messages|grep fault
Jun 19 09:16:40 localhost kernel: [425230.940278] Chrome_~dThread[3925]: segfault at 0 ip af501157 sp ae877050 error 6 in libxul.so[aefb0000+63b0000]
 
$ cat /proc/meminfo|less
bash: fork: Cannot allocate memory

Вернув же sysctl -w vm.admin_reserve_kbytes=262144 обратно к 256 МБ браузер успешно запущен и занял Committed_AS более 9хххххх kB (почти до 10 ГБ).

Теперь пишем данное значение vm.admin_reserve_kbytes=2166444 в /etc/sysctl.conf, но не применяем его, а закрываем все приложения и перезагружаем машину.

Опыт №2

Теперь, после перезагрузки, все процессы, запущенные от имени пользователя root, а он имеет неограниченные права и все флаги возможностей включая cap_sys_admin (reserved for users with the capability cap_sys_admin) , казалось должы были бы использовать большую часть admin_reserve_kbytes и "болтающейся" без дела памяти у нас быть не должно.

Однако, запуская все те же процессы, что были до перезагрузки, среди которых был и браузер palemoon:

$ firejail --name=palemoon /home/test/opt/palemoon/palemoon
...
fork(): Невозможно выделить память
[mp3 @ 0xae719c00] overread, skip -5 enddists: -2 -2
[mp3 @ 0x8236dc00] overread, skip -5 enddists: -2 -2
[mp3 @ 0x8236cc00] overread, skip -6 enddists: -4 -4
 
Parent is shutting down, bye...
 
$ cat /proc/meminfo|less
...
CommitLimit: 10635300 kB
Committed_AS: 8128504 kB

На значении чуть выше 8128504 кб браузер был "забит" и перед смертью выдал "fork(): Невозможно выделить память". Аналогичный результат можно получить при попытке запуска хрона:

# less /var/log/messages|grep fault
Jun 19 09:16:40 localhost kernel: [425230.940278] Chrome_~dThread[3925]: segfault at 0 ip af501157 sp ae877050 error 6 in libxul.so[aefb0000+63b0000]
 
$ cat /proc/meminfo|less
bash: fork: Cannot allocate memory

Вернув же sysctl -w vm.admin_reserve_kbytes=262144 обратно к 256 МБ браузер успешно запущен и занял Committed_AS более 9хххххх kB (почти до 10 ГБ).

Итоги опытов

В итоге опытным путём мы убедились, что на процессы запускаемые от имени root через systemd зарезервированная память из admin_reserve_kbytes не выделяется, а просто себе болтается без дела как до, так и после перезагрузки.

Вероятно выделение памяти из admin_reserve_kbytes для пользователей с флагом cap_sys_admin происходит при интерактивном взаимодействии с машиной, например при авторизации по SSH/SU и т.п.

Как дать пользователю возможности cap_sys_admin

cap_sys_admin для su-пользователей CentOS 6/7

# vi /etc/security/capability.conf
cap_sys_admin testuser

Далее подключаем PAM-модуль (Pluggable Authentication Modules) pam_cap.so. ВНИМАНИЕ! Строка подключения pam_cap.so должна быть перед строкой подключения pam_rootok.so!

# vi /etc/pam.d/su
#%PAM-1.0
auth optional pam_cap.so
auth sufficient pam_rootok.so
...
...

Авторизируемся с помощью su, проверяем capabilities для текущего пользователя:

$ su - testuser
 
$ capsh --print
Current: = cap_sys_admin+i
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36
Securebits: 00/0x0/1b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=1001(testuser)
gid=1001(testuser)
groups=1001(testuser)

Значение для admin_reserve_kbytes

Отметим, что многие описания параметров в документации ядра не только являются не полными, но даже вводят в заблуждение, вот например: "admin_reserve_kbytes defaults to min(3% of free pages, 8MB)", - 3% от свободных страниц, не объёма, а именно страниц (4 кб страница)? А, что это за 8MB?

Значение admin_reserve_kbytes по-умолчанию

Вот, пожалуйста, не у одного меня возник подобный вопрос на данную тему:

Во время загрузки ядра - когда подсистема MM инициализируется - она вычисляет 3% оставшейся свободной памяти. Если это больше, чем 8 МБ, значение устанавливается на 8 МБ.

"Если это больше, чем 8 МБ, значение устанавливается на 8 МБ.", - да хрен там... На машине с Debian Stretch где "MemTotal: 2059828 kB" (без свопа) "default value of the admin_reserve_kbytes" составляло около 6х МБ, что таки да соответствует тем самым 3% от MemTotal: 2059828 kB / 100 * 3 = 61794,84.

На машине с Debian Stretch где "MemTotal: 250400 kB" и vm.overcommit_memory = 0 составило vm.admin_reserve_kbytes = 7616

Однако на другой машине с той же ОС Debian Stretch где "MemTotal: 5083092 kB" и vm.overcommit_memory = 0 составило vm.admin_reserve_kbytes = 8192 

Возможно в других ОС и/или версиях ядра иная ситуация со значением admin_reserve_kbytes по-умолчанию и зависит от реализации функции init_admin_reserve(void), а также иных функций ею вызываемых global_zone_page_state(NR_FREE_PAGES) и т.п.: https://elixir.bootlin.com/linux/v5.0/source/mm/mmap.c#L3666

Таким образом, admin_reserve_kbytes:

  • defaults = как-то в расброс... То, 3% от RAM (без учёта свопа), то 8MB, а бывает и 7616 кб (Ой как бывает! Всё так зыбко и условно: https://www.youtube.com/watch?v=Ta4G51ZMZRM).

Если значение не указано явно, то admin_reserve_kbytes по-умолчанию может быть равно 3% (без учёта пространства подкачки страниц), и как видим не факт, что 8 МБ будет минимумом даже если 3% до 8 МБ не дотягивает, - короче, как повезёт. Если не хочется гадать, установи его явно.

Оптимальное значение для admin_reserve_kbytes

Если у нас нет удалённого доступа к нашей машине и соответственно пользователей с cap_sys_admin, vm.overcommit_memory = 2, то вполне будет достаточно символического 1 кб sysctl -w vm.admin_reserve_kbytes=1, - проверено, с таким значением ничто не загнулось, не взорвалось, ничего страшного не произошло.

А для нахождения оптимального значения admin_reserve_kbytes на удалённом сервере смотрим VmSize (virtual memory size: man proc) + VmRSS из /proc/PID/status, для всех основных программ с которыми мы удалённо будем работать, например:

# cat /proc/PID/status|less
Name:   sshd
State:  S (sleeping)
Tgid:   20532
Pid:    20532
PPid:   1
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
Utrace: 0
FDSize: 64
Groups:
VmPeak:    87872 kB
VmSize:    87868 kB
VmLck:         0 kB
VmHWM:      1560 kB
VmRSS:       584 kB
VmData:     8596 kB
VmStk:        88 kB
VmExe:       552 kB
VmLib:      8444 kB
VmPTE:       168 kB
VmSwap:      868 kB
Threads:        1

В случае overcommit_memory=2, сложив размер VmSize + VmRSS для базовых процессов (sshd/screen/bash) обеспечивающих удалённое взаимодествие с сервером - получим не менее 125-256 МБ, а то и все 512 МБ от общей (ака CommitLimit) системной (РАМ + СВОП + (RAM * overcommit_ratio / 100)) памяти (считать всё в kB разумеется, в МБ указано для удобства).

OOM killer

Немного про OOM killer (OOM: out-of-memory)...

oom_kill_allocating_task

This enables or disables killing the OOM-triggering task in out-of-memory situations.

If this is set to zero, the OOM killer will scan through the entire tasklist and select a task based on heuristics to kill. This normally selects a rogue memory-hogging task that frees up a large amount of memory when killed.

If this is set to non-zero, the OOM killer simply kills the task that triggered the out-of-memory condition. This avoids the expensive tasklist scan.

If panic_on_oom is selected, it takes precedence over whatever value is used in oom_kill_allocating_task.

The default value is 0.

---

Если это значение равно нулю, убийца OOM просканирует весь список задач и выберет задачу для уничтожения на основе эвристики. Обычно он выбирает задачу, которая при уничтожении освободит больше всего памяти.

Если для этого параметра установлено ненулевое значение, убийца OOM просто завершает задачу, которая вызвала состояние нехватки памяти. Это позволяет избежать дорогостоящего сканирования списка задач.

Здесь описание параметра совпадает с реальным его поведением. Если, допустим, у нас из "CommitLimit: 10635924 kB" занято "Committed_AS: 7450192 kB", и мы запустим процесс требующий более 3 ГБ, например Chromium, то OOM killer не обязательно грохнет сам Chromium, который собственно и вызвал "out-of-memory condition", а может выбрать процесс из списка уже работающих, - таким процессом для OOM killer-а оказался другой уже работающий веб-браузер Pale Moon, который занимал до 2 ГБ, в котором на тот момент было открыто много полезных страниц с несохранёнными данными. Т.е., при vm.oom_kill_allocating_task=0 делается упор на освобождение памяти от уже работающих процессов вугоду только что запущенных.

vm.oom_kill_allocating_task=1 заставит OOM killer-а тупо мочить любые процессы, которые стали причиной "out-of-memory condition", - этот вариаинт выбираю потому, что не хочу жертвовать уже работающими процессами. Данный вариант также должен гарантировать прибивание из уже работающих только "психанутых" процессов (вслучай чего с ними станется) вызвавших "out-of-memory condition", вместо наоборот завалить текстовый редактор с несохранёнными данными во благо свихнувшемуся php-cgi например.


Комментарии в блоге
Новое на форуме