Защищаем сервер от исчерпания ресурсов с помощью cgroups

Типичная проблема с которой встречаются администраторы веб-серверов:

На сайт приходит шквал запросов, Apache занимает всю оперативную память и как только залазит в своп - до сайта не достучаться и даже на ssh зайти проблематично. Рано или поздно и swap закончится и... как думаете, что будет делать в ситуации когда программа просит памяти, а памяти нет?

А вот и нет :P, Linux начнет килять все процессы вподряд освобождая память (8.

cgroups_terminal_example.png

Есть стандартное средство - ограничение числа запущенных процессов Apache. Только надо заранее прикинуть сколько один процесс ОЗУ сожрет, и оставить 1-2 ГБ запаса. Проблема в том, что сколько апач сожрет памяти заранее может быть неизвестно и это число может плавать. Если ошибся - сервер завис. Если перестраховался - неоптимально расходуешь ресурсы.

Есть уже несколько лет другой способ ограничения ресурсов: cgroups или контрольные группы. Позволяет в весовом эквиваленте ограничивать утилизацию дисков (блочных устройств), памяти, сети, процессора и даже более тонкие штуки, вплоть до контроля в кеше какого ядра процессора держать данные. И привязывается это все на пользователя или на процессы.

Еще один плюс: ограничения работают на все что процесс породит. Т.е. мы можем зарезать Apache по памяти и нам уже не нужно считать сколько потомков он породит и сколько памяти они вместе сожрут.

В RHEL cgroups появились еще в 6 версии, а в 7 уже вовсю используются в системе инициализации systemd. Также на основе cgroups реализуется управление ресурсами в системах виртуаллизации, таких как docker и KVM.

Более подробную информацию можно посмотреть с курсов Яндекса и в офф. руководстве RHEL6.

Ограничение памяти для процесса[править]

Я буду работать, впрочем, с CentOS7 и опущу установку и сразу перейду к настройке. Дело в том, что в RHEL7 предлагается применять cgroups не так как написано в большинстве руководств по cgroups, включая руководство RHEL6. В RHEL7 для запуска используется systemd 205 и управление cgroups рекомендуется осуществлять через systemd, а не всюду описанным cgconfig. При этом именно в systemd 205 была отключена возможность тонкой настройки cgroups через ControlGroupAttribute и т.п. команды :).

Итак, хотим ограничивать процесс. Но в RHEL7 cgroups работают через systemd, а тот работает со службами, а не исполняемыми файлами. Поэтому натравливать cgroups будем на службы systemd:

Итак, набираем

systemctl set-propery <name.service> <name>=<value>

Для ограничения занимаемого объема ОЗУ надо поставить, например, MemoryLimit=4096M

Если применяется php-fpm, то

systemctl set-propery php-fpm.service MemoryLimit=8G

Затем надо выполнить

systemctl default-reload

И перезапустить сервис (т.к. cgroups нельзя применить к уже запущенным процессам)

systemctl stop php-fpm

systemctl start php-fpm

Снижение приоритета процесса[править]

В cgroups есть работоспособный аналог команды nice. Надо установить:

CPUShares=<number>

По умолчанию CPUShares=1024 и приоритет можно понизить установив меньшее значение типа:

systemctl set-propery php-fpm.service MemoryLimit=8G CPUShares=300

systemctl  default-reload; systemctl stop php-fpm.service; systemctl start php-fpm.service

Конечно, вам уже хочется узнать какие еще команды есть? Смотрите в руководстве RHEL.

Motivation[править]

Лично я взялся за cgroups не из-за Apache или php-fpm. Начал тестировать кластерную файловую систему GlusterFS и, когда php-fpm выжирает всю память, файловая система начинает падать. Причем довольно хитро, в статусе показывая "все OK", всех пиров в кластере типа вижу, а по факту надо все перезагружать. Дополнительно на этой упавшей файловой системе как раз файлы лежат, необходимые для php-fpm, от чего php-fpm тоже подвисает не освобождая память и ситуация усугубляется.