Пойман неприятный баг на web-сервере Apache. Симптомы простые, все сайты перестают отвечать: белый экран. Помогает перезагрузка Apache.
На сервере:
- Ubuntu 22
- Apache 2
- PHP 7.2
- PHP-FPM
- php_prefork
В бою такой сервер использовать невозможно. Три часа работы, потом всё висит. Можно поставить проверку и автоматически перезагружать Apache, но это костыль.
В логах можно увидеть сообщение о том, что веб-сервер перезагружается:
caught SIGWINCH, shutting down gracefully
Перезагрузку наблюдает модуль php_prefork. Этот модуль служит для изоляции процессов и ускорения работы веб-сервера с помощью предварительной загрузки тяжёлых PHP-приложений. При большой нагрузке на сервер данный модуль может сам перезагружать Apache. Если ваша проблема состоит только в том, что веб-сервер периодически перезагружается, то стоит обратить внимание на настройку конфигурации php_prefork, выделив под него дополнительные ресурсы.
Однако, проблему зависания сервера данный модуль не решает. В логах у нас есть такое:
child 5023 said into stderr: "(5023): Error Cannot kill process 2349: Operation not permitted!"
И это баг, товарищи. Вроде как пишут, что баг исправлен в версиях PHP 7.4.16 и 8.0.3. Так что проблему можно попытаться устранить обновлением PHP. Но это не всегда возможно.
Вероятно, проблема вызвана кривое реализацией PHP-FPM в связке с OPCache. Модуль OPCache занимается кэшированием данных. На сервере все пулы для определенной версии PHP совместно используют один и тот же главный процесс (принадлежащий root) и используют один и тот же пул памяти, выделенный для OPCache. Однако сами пулы принадлежат учетной записи веб-сервера (www-data).
Opcache периодически перезапускает пулы. Иногда ему требуется кильнуть всё, он дёргает функцию kill_all_lockers(). И прав ему не хватает. Что делать?
Вариант 0
Обновить PHP. Не всегда возможно.
Вариант 1
Отключить модуль OPCache. Ошибка устранена, но кеширования у вас больше нет. Не всегда можно на это пойти.
Вариант 2
Перейти на другой PHP handler — FastCGI. И это тоже не всегда возможно.
Вариант 3
Запретить вызов функции kill_all_lockers. В php.ini в раздел disable_functions добавляем kill_all_lockers. Способ хороший, но почему-то не всегда срабатывает, непонятно почему.
Вариант 4
Перезапуск PHP-FPM каждые несколько минут. Так себе решение, может подойти только для временного решения проблемы.
Вариант 5
Самый нормальный вариант после обновление PHP. Дело в том, что OPCache не всегда вызывает функцию kill_all_lockers(). Он сначала убивает пулы по очереди, а ели не успевает по таймауту, то грохает сразу всё. Этот самый таймаут установлен в параметре opcache.force_restart_timeout — 180 секунд. Если мы увеличим его, скажем, до бесконечности, то функция kill_all_lockers не будет вызываться.
opcache.force_restart_timeout = 0
Настройки OPCache могут быть не только в файле php.ini, но и в подключаемых отдельных файлах конфигураций.