Администрирование PHP-FPM: выбор режима pm и оптимизация под нагрузку
Основные подходы к настройке PHP-FPM
Как настроить базовый пул PHP-FPM для обработки HTTP запросов?
PHP-FPM (FastCGI Process Manager) является реализацией FastCGI для PHP. Основная конфигурация сосредоточена в файлах пулов, обычно расположенных в /etc/php/<version>/fpm/pool.d/. По умолчанию используется файл www.conf. Ниже приведен пример минимальной рабочей конфигурации.
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 200
Php fpm server (настройка сервера php-fpm)
В данной конфигурации:
- listen указывает на Unix-сокет. Можно использовать TCP (например, 127.0.0.1:9000).
- pm выбран dynamic - количество процессов меняется в зависимости от нагрузки.
- pm.max_children - максимальное количество дочерних процессов (ограничивает общее потребление памяти).
- pm.start_servers, pm.min_spare_servers, pm.max_spare_servers управляют начальным и резервным количеством процессов.
- pm.max_requests - максимальное число запросов до перезапуска процесса (помогает избежать утечек памяти).
Запуск и проверка:
sudo systemctl start php8.3-fpm
sudo systemctl status php8.3-fpm
Типичные ошибки:
- 502 Bad Gateway - сокет или порт не доступен. Проверить, что PHP-FPM запущен и listen соответствует конфигурации веб-сервера.
- Неверные права на сокет - веб-сервер не имеет доступа. Убедиться в совпадении пользователя и группы или использовать listen.mode.
- Нехватка процессов - при пиковой нагрузке запросы встают в очередь. Увеличить pm.max_children (с учетом доступной памяти).
Как настроить динамическое управление процессами (pm = dynamic) для изменяющейся нагрузки?
Динамический режим позволяет пулу увеличивать и уменьшать количество процессов в заданных пределах. Параметры pm.min_spare_servers и pm.max_spare_servers определяют резерв простаивающих процессов. Пример конфигурации для средне-нагруженного сайта:
pm = dynamic
pm.max_children = 100
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500
При резком увеличении нагрузки возможна задержка на создание новых процессов. Рекомендуется установить pm.process_idle_timeout (по умолчанию 10 секунд) для быстрого освобождения ресурсов неактивными процессами.
Ошибка: процессы не успевают создаваться при спайке трафика. Решение: временно увеличить pm.start_servers или перейти на статический режим.
Когда и как использовать статический режим (pm = static) для стабильной нагрузки?
Статический режим фиксирует число процессов. Он подходит для высоконагруженных проектов с предсказуемым трафиком. Пример:
pm = static
pm.max_children = 50
Важно точно рассчитать максимальное количество процессов, исходя из среднего потребления памяти на один процесс. Перерасход приведет к нехватке оперативной памяти и падению сервера.
Частая ошибка: установка слишком большого pm.max_children при ограниченной памяти. Рекомендуется вычислять лимит по формуле: (общая память - память ОС)/память на один PHP процесс. Пример: 16ГБ ОЗУ, 40МБ на процесс -> (16000-1024)/40 ≈ 374, но с запасом 300.
Как настроить PHP-FPM для экономии ресурсов с pm = ondemand?
Режим ondemand создает процесс только при поступлении запроса и завершает его после простоя. Подходит для окружений с малой нагрузкой. Пример:
pm = ondemand
pm.max_children = 20
pm.process_idle_timeout = 10
Недостаток: задержка при первом запросе после простоя (холодный старт). Рекомендуется для серверов разработки или микро-сервисов.
Проблема: при внезапном всплеске запросов создание процессов может не успевать. Решение: увеличить pm.max_children или использовать динамический режим.
Как создать изолированные пулы для разных приложений и пользователей?
Каждый пул может запускаться от своего пользователя и группы, иметь собственный сокет/порт и лимиты. Это повышает безопасность. Пример двух пулов:
[site1]
user = site1
group = site1
listen = /run/php/php8.3-site1.sock
pm = dynamic
pm.max_children = 20
[site2]
user = site2
group = site2
listen = /run/php/php8.3-site2.sock
pm = dynamic
pm.max_children = 30
Конфигурация веб-сервера (например, Nginx) должна указывать на соответствующий сокет:
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-site1.sock;
...
}
Ошибка: пул не запускается из-за отсутствия пользователя. Необходимо создать системного пользователя: sudo useradd -r -s /bin/false site1.
Как выбрать между unix сокетом и TCP соединением?
Unix-сокет быстрее и безопаснее (недоступен из сети). TCP полезен для распределенной архитектуры (веб-сервер и PHP-FPM на разных хостах). Пример TCP:
listen = 192.168.1.10:9000
listen.allowed_clients = 192.168.1.20
На веб-сервере:
fastcgi_pass 192.168.1.10:9000;
Проблема: использование TCP может увеличить задержки из-за сетевых накладных расходов. Рекомендуется использовать Unix-сокет для локальных установок.
Как ограничить время выполнения и память для процессов, и выявить медленные запросы?
Параметры request_terminate_timeout, request_slowlog_timeout и slowlog помогают контролировать производительность.
request_terminate_timeout = 30s
request_slowlog_timeout = 5s
slowlog = /var/log/php/slow.log
Также можно ограничить память через php_admin_value[memory_limit] в пуле:
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 30
Ошибка: медленные запросы не записываются в лог. Проверить, что каталог для slowlog существует и доступен для записи пользователем пула.
Как настроить chroot для дополнительной изоляции пула?
Chroot ограничивает доступ пула к файловой системе. Пример:
chroot = /var/www/site1
chdir = /
Необходимо, чтобы все необходимые файлы (PHP, библиотеки, /dev/urandom) были внутри chroot. Это сложная конфигурация, используется редко, в основном для shared hosting.
Проблема: многие функции PHP могут сломаться (например, работа с сокетами, временными файлами). Требуется тщательное тестирование.
Расширенные примеры настройки PHP-FPM
Пример 1: Высоконагруженный пул с pm static и мониторингом
[highload]
user = app
group = app
listen = /run/php/highload.sock
pm = static
pm.max_children = 200
pm.status_path = /status
ping.path = /ping
ping.response = pong
access.log = /var/log/php/access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
Статус мониторинга доступен через /status (нужно настроить location в Nginx). Пример результата:
pool: highload process manager: static start time: 10/Nov/2024:12:00:00 +0000 start since: 3600 accepted conn: 50000 listen queue: 0 max listen queue: 5 listen queue len: 0 idle processes: 50 active processes: 150 total processes: 200 max active processes: 180 max children reached: 0 slow requests: 2
Пример 2: Настройка пула с ограничением по памяти и использовании переменных окружения
[envpool]
user = deploy
group = deploy
listen = 127.0.0.1:9001
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 1000
request_terminate_timeout = 60s
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 60
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 128M
env[APP_ENV] = production
env[DB_HOST] = localhost
Переменные окружения доступны в PHP через getenv(). Это удобно для конфигурации приложений.
Пример 3: Конфигурация для работы с несколькими версиями PHP на одном сервере
Устанавливаются разные версии PHP-FPM, каждая со своим портом/сокетом. Пример для PHP 7.4 и 8.3:
# Пулы для PHP 7.4
[www74]
listen = /run/php/php7.4-fpm.sock
...
# Пулы для PHP 8.3
[www83]
listen = /run/php/php8.3-fpm.sock
...
В Nginx используем разные location для разных расширений или каталогов:
location ~ \.php$ {
if ($request_filename ~* /legacy/) {
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
...
}
Пример 4: Логирование медленных запросов и ошибок
request_slowlog_timeout = 2s
slowlog = /var/log/php/slow-$pool.log
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/error.log
Анализ slowlog:
[10-Nov-2024 14:30:00] [pool www] pid 1234 script_filename = /var/www/site/index.php [0x00007f1234567890] function() /var/www/site/index.php:10 [0x00007f1234567890] ...
Пример 5: Команды управления службой и тестирование конфигурации
sudo php-fpm8.3 -t # проверка синтаксиса конфигурации
sudo systemctl reload php8.3-fpm # перезагрузка без прерывания текущих соединений
sudo systemctl restart php8.3-fpm # полный перезапуск
sudo systemctl enable php8.3-fpm # автозапуск при загрузке