Этот сайт работает на домашнем сервере с 3,2 ГБ памяти, рядом с десятком других проектов. Весь сайт — пять контейнеров, и вместе они занимают около 310 МБ. Разберу, из чего он состоит и какие настройки по умолчанию мы поменяли.
Это первая часть цикла «Сервер для WordPress». Дальше — пул PHP-FPM и его аварии, свой образ PHP, Redis, сеть Docker, почта, сертификаты, диск, крон и сборка.
Пять контейнеров
| Контейнер | Что делает | Память сейчас |
|---|---|---|
| nginx | отдаёт статику и кэш страниц, передаёт PHP остальное | 9 МБ |
| php | PHP-FPM: WordPress, ajax, крон | 271 МБ |
| mariadb | база | 23 МБ |
| redis | объектный кэш WordPress | 7 МБ |
| маленький прокси | выход к одному внешнему API | меньше 1 МБ |
Плюс два общих сервиса на весь сервер: traefik принимает HTTPS и раздаёт запросы сайтам по имени, а планировщик запускает крон каждого сайта раз в минуту (почему крон должен быть системным — во второй части цикла про WordPress на кэшируемом сайте).
Сокеты вместо сети
nginx передаёт запросы в PHP-FPM, а PHP ходит в базу через Unix-сокеты в общих томах, без TCP:
php:
volumes:
- phpsocket:/var/run/php # nginx → PHP-FPM
- mysqlsocket:/var/run/mysqld # PHP → MariaDB
// wp-config.php
define('DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock');
Плюсы: нет сетевого порта базы, который можно случайно открыть, и не нужно имя базы в сети. Последнее оказалось важнее, чем кажется: сетевое имя базы могло перехватить запросы соседей к самому сайту — об этом в седьмой части.
База на диете
MariaDB по умолчанию рассчитана на сервер, где она одна. У нас база своя у каждого сайта, и память нужно делить. Для небольшого сайта хватает таких настроек:
mariadb:
command: ["mariadbd",
"--innodb-buffer-pool-size=16M",
"--innodb-log-file-size=16M",
"--performance-schema=OFF",
"--key-buffer-size=8M",
"--tmp-table-size=16M",
"--max-heap-table-size=16M",
"--max-connections=30",
"--table-open-cache=64"]
Итог — 23 МБ на базу. Для каталога на десятки тысяч товаров буфер нужен больше: его размер подбирают по объёму активных данных, а не ставят «на всякий случай».
Отключение performance_schema само по себе экономит заметную часть памяти: это внутренняя статистика базы, которая на маленьком сайте никому не нужна.
PHP-FPM: процессы по требованию
[www]
pm = ondemand
pm.max_children = 6
pm.process_idle_timeout = 30s
request_terminate_timeout = 300s
request_slowlog_timeout = 5s
- ondemand — процессы запускаются при запросах и гаснут через 30 секунд простоя. Для сайта, который почти весь отдаётся из кэша, держать процессы наготове незачем;
- 6 процессов — потолок, который сервер выдержит вместе с соседями;
- request_terminate_timeout — предохранитель от зависших запросов. Без него один запрос однажды положил сайт на 5 часов 40 минут — об этом вторая часть;
- slowlog — стек каждого запроса дольше 5 секунд в журнал.
Для сайта с постоянным трафиком без кэша ondemand не подходит: запуск процесса на каждый всплеск добавляет задержку. Там нужен dynamic с тёплым запасом — и своя грабля, о ней третья часть.
Redis только внутри
Redis подключён к отдельной сети с internal: true: у неё нет выхода наружу, и кроме PHP туда никто не попадает. Пароль у такого Redis — дополнительная мера, а не единственная.
networks:
redisnet:
internal: true
Здесь Redis — объектный кэш WordPress, и настроен он противоположно Redis под кэш фрагментов на других наших сайтах. Почему — шестая часть.
Журналы — в системный журнал
logging:
driver: journald
options:
tag: "site-php"
Журналы всех контейнеров попадают в journald с меткой сайта: их не надо ротировать вручную, а нужное ищется одной командой — journalctl -t site-php --since "-1 hour".
Что дальше
Почти все аварии, которые мы разбирали на своих серверах, случились не от нагрузки, а от настроек по умолчанию. С самой дорогой из них — пулом PHP-FPM без предохранителя — начнём завтра.
Нужен сервер под WordPress или навести порядок на текущем?
Пришлите, что у вас сейчас, — соберу стек, который не падает от одного зависшего запроса.
// загружаю комментарии…