PHP-процесс убит: как подтвердить OOM в Linux и контейнере

Проверяем, почему завершился PHP-процесс: сопоставляем журналы ядра, ограничения PHP и события контейнера. Выбираем исправление по подтверждённой причине.

PHP-процесс исчез, выгрузка оборвалась или сайт вернул 502? Сообщение Killed и код выхода 137 ещё не доказывают OOM — завершение из-за нехватки памяти. Нужны записи ядра или события контейнера за тот же интервал, связанные с нужным процессом.

Сначала сохраните время сбоя, PID и журналы, затем определите уровень ограничения: PHP, контейнер или весь сервер. Увеличивать memory_limit до этой проверки опасно: несколько обработчиков могут вместе исчерпать память, даже если каждый укладывается в свой предел.

1. Сохраните признаки до перезапуска

Зафиксируйте время с часовым поясом, URL или имя задания, PID процесса, имя контейнера и недавние изменения. Для CLI код завершения можно получить командой echo $? сразу после сбоя; другая команда уже заменит это значение.

Если посетители видят 502 Bad Gateway, сопоставьте ошибку прокси с журналом PHP-FPM. Сам HTTP-код не указывает, почему пропал обработчик.

Не воспроизводите тяжёлый импорт на рабочем сайте ради доказательства. Сначала соберите уже имеющиеся записи: после пересоздания контейнера часть диагностических данных исчезнет.

2. Отделите лимит PHP от OOM ядра

Проверьте журнал PHP и стандартный поток ошибок процесса:

  • Allowed memory size … exhausted указывает на ограничение памяти внутри PHP;
  • Hard memory limit … exhausted в версиях с таким механизмом означает немедленное завершение самим PHP; возможен код 137 без OOM ядра;
  • Killed без пояснений требует проверки журналов ОС и среды запуска.

Проверьте версию и действующие настройки именно того обработчика, который упал: конфигурация CLI и PHP-FPM может различаться. Не публикуйте phpinfo() на общедоступной странице.

Поведение лимитов зависит от версии: детали описаны в руководстве PHP. Отсутствие записи в обычном PHP error log само по себе не подтверждает OOM: сообщение могло попасть в stderr или процесс завершился до штатной обработки ошибки.

3. Найдите событие в журнале Linux

На сервере с systemd администратор может прочитать журнал ядра за интервал сбоя. Пример ниже показывает последние полчаса; для старого инцидента задайте точные границы времени.

sudo journalctl -k --since "30 minutes ago" --no-pager

Ищите сообщения Out of memory, oom-kill, Killed process, затем сопоставляйте PID, имя процесса и время. Не ограничивайтесь поиском слова PHP: память мог занять другой сервис, а ядро выбрало PHP жертвой.

Различайте нехватку памяти на хосте и ограничение cgroup — группы процессов с общим ресурсным пределом. Свободная RAM на сервере не исключает OOM внутри такой группы. Текущие показатели после завершения процесса тоже не показывают, сколько памяти было в момент аварии.

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

4. Проверьте контейнер и его предел

Для Docker используйте имя пострадавшего контейнера вместо CONTAINER:

docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.FinishedAt}}' CONTAINER
docker inspect --format '{{.HostConfig.Memory}}' CONTAINER

Значение OOMKilled=true — признак OOM, который нужно сопоставить с временем и журналом хоста. HostConfig.Memory показывает заданный предел Docker в байтах; ноль означает отсутствие этого явного предела, но не безграничную память.

Код 137 совместим с SIGKILL, но не объясняет причину: процесс могли принудительно остановить вручную или после истечения срока штатного завершения. Это различие следует из справки Docker о кодах завершения.

В cgroup v2 администратор проверяет файлы memory.max, memory.current и memory.events именно нужной группы: это предел, текущее потребление и счётчики событий. Рост счётчика oom_kill подтверждает OOM-завершения процессов в ней, но счётчик накопительный и сам по себе не называет PHP или время события. Сравнивайте снимки до и после инцидента и учитывайте дочерние группы: описание счётчиков Linux.

Не подставляйте случайный путь из чужой инструкции: cgroup v1, v2 и контейнерные пространства имён показывают разные структуры. Если убит только дочерний PHP-процесс, контейнер может продолжить работу — отсутствие перезапуска не закрывает проверку.

5. Выберите изменение и проверьте тот же сценарий

После подтверждения причины меняйте один фактор:

  1. Лимит отдельного задания PHP. Измерьте расход памяти на тестовой копии данных; разбейте импорт на порции или уберите загрузку всей выборки в память.
  2. Предел контейнера. Сравните потребление всех его процессов с доступным ресурсом хоста. Изменение лимита согласуйте с общей нагрузкой: контейнер не получает дополнительную физическую RAM автоматически.
  3. Нехватка памяти сервера. Проверьте конкурирующие задания и число обработчиков. Если насыщен пул, используйте отдельную диагностику очереди PHP-FPM.

Не отключайте OOM killer как способ восстановления: документация Docker предупреждает о риске для остальных процессов хоста. Перед повтором задания выясните, не успело ли оно частично записать данные: слепой повтор импорта способен создать дубли.

Проверьте результат на сопоставимом объёме и параллельной нагрузке: задание завершается, новых OOM-событий нет, сайт отвечает ожидаемым кодом и содержимым. Внешний мониторинг Web-Puls помогает заметить недоступность, но не читает память хоста и не устанавливает причину OOM.

Если доступа к журналам нет или сбой повторяется, отправьте заявку на диагностику: укажите публичный URL, время, сценарий и обезличенные фрагменты ошибок. Для выбора способа связи есть контакты; пароли, Cookie и содержимое пользовательских заявок передавать не нужно.

Проверьте свой сайт прямо сейчас

Введите адрес сайта: Web-Puls покажет HTTP-код, время ответа и базовую диагностику. Для постоянного контроля можно подключить мониторинг.

Нужна помощь с диагностикой ошибки?

Опишите симптомы в короткой заявке: URL, код ответа, время появления и что менялось перед сбоем. Специалист поддержки оценит задачу и предложит формат работ.