Администрирование Web и БД: Nginx и PostgreSQL

Углубленный курс по настройке отказоустойчивого веб-стека. Вы научитесь конфигурировать Nginx как высокопроизводительный reverse-proxy и обеспечивать сохранность данных в PostgreSQL через репликацию и стратегии резервного копирования.

1. Архитектура Nginx: обработка соединений и структура конфигурационных файлов

Архитектура Nginx: обработка соединений и структура конфигурационных файлов

В 1999 году инженер Дэн Кегель сформулировал «проблему C10K»: как заставить веб-сервер обрабатывать 10 000 одновременных соединений. Доминирующий в то время Apache использовал модель «один процесс (или поток) на одно соединение». При 10 000 клиентах операционная система тратила больше процессорного времени на переключение контекста между 10 000 потоков, чем на саму передачу данных, а потребление оперативной памяти выходило за разумные пределы. Nginx был написан Игорем Сысоевым именно как ответ на эту проблему. Вместо создания новых процессов под каждого клиента, Nginx применил асинхронную событийно-ориентированную архитектуру, которая сегодня является стандартом де-факто для высоконагруженных веб-узлов.

Событийно-ориентированная модель и процессы

В основе производительности Nginx лежит строгий отказ от блокирующих операций и разделение обязанностей между процессами. При запуске Nginx не начинает сразу обрабатывать сетевой трафик единым монолитом. Вместо этого он порождает иерархию процессов.

Главный процесс называется Master process. Он запускается от имени суперпользователя (root), так как ему необходимо выполнять привилегированные операции: чтение конфигурационных файлов, открытие сетевых портов (например, 80 и 443) и управление дочерними процессами. Master-процесс не занимается обработкой HTTP-запросов. Его задача — оркестрация.

Для фактической работы с клиентскими соединениями Master-процесс порождает рабочие процессы — Worker processes. Они запускаются от имени непривилегированного пользователя (в Ubuntu/Debian это обычно www-data), что минимизирует риски безопасности в случае компрометации веб-сервера.

!Архитектура процессов Nginx: Master и Workers

Количество рабочих процессов обычно привязывается к количеству физических или виртуальных ядер процессора. В конфигурации это задается директивой worker_processes auto;. Логика здесь математически строгая: если рабочий процесс никогда не блокируется в ожидании ввода-вывода (I/O), то создание большего числа процессов, чем есть ядер, приведет лишь к бесполезным затратам на переключение контекста (context switching) на уровне ядра Linux. Идеальное состояние — когда каждый Worker жестко закреплен за своим ядром (CPU pinning) и работает непрерывно.

Каждый Worker использует системный вызов ядра Linux — epoll (или kqueue во FreeBSD). Это механизм мультиплексирования ввода-вывода. Вместо того чтобы опрашивать каждое соединение по очереди («У тебя есть данные? А у тебя?»), Worker просто запрашивает у ядра операционной системы: «Дай мне список дескрипторов, в которых произошли события».

Событием может быть установка нового TCP-соединения, поступление HTTP-запроса от клиента или готовность буфера для отправки ответа. Worker берет событие из очереди, выполняет необходимые вычисления (например, парсит HTTP-заголовки), инициирует неблокирующее чтение файла с диска или отправку запроса к базе данных, и немедленно переходит к следующему событию.

Максимальное количество одновременных подключений, которое может обслужить сервер, вычисляется по формуле:

Где — это лимит открытых файловых дескрипторов на один рабочий процесс (задается в блоке events).

Главная уязвимость такой архитектуры — блокирующие вызовы. Если Worker начнет синхронно читать огромный файл с медленного NFS-диска или выполнять тяжелый скрипт, он остановится. В этот момент тысячи других соединений, закрепленных за этим Worker-ом, будут заморожены, так как цикл событий (Event Loop) перестанет вращаться. Именно поэтому Nginx никогда не выполняет PHP или Python код внутри своих процессов, а делегирует это внешним серверам приложений через протоколы FastCGI или HTTP.

Контексты конфигурации и иерархия

Конфигурация Nginx строится не как плоский список параметров, а как древовидная структура изолированных областей, которые называются контекстами. Понимание этой иерархии критически важно для предсказуемого поведения сервера.

Глобальный контекст (часто называемый main) находится за пределами любых фигурных скобок. Здесь задаются базовые параметры демона: пользователь, пути к PID-файлу и глобальным логам ошибок, а также количество рабочих процессов.

Внутри глобального контекста располагаются специализированные структурные блоки:

  • events — управляет параметрами сетевых соединений на уровне Worker-ов (тот самый worker_connections).
  • http — корневой контекст для всей логики обработки веб-трафика. Здесь задаются MIME-типы, форматы логов, таймауты и настройки сжатия Gzip, применимые ко всем сайтам на сервере.
  • Внутри контекста http могут располагаться один или несколько блоков server. Каждый такой блок описывает виртуальный хост — отдельный сайт или сервис. Nginx позволяет обслуживать сотни разных доменов на одном IP-адресе, направляя трафик в нужный блок server на основе заголовка Host из HTTP-запроса клиента.

    Наконец, внутри server находятся блоки location. Они отвечают за маршрутизацию запросов на основе URI (пути в адресной строке). Именно в location принимается решение: отдать статический файл с диска, проксировать запрос на backend-приложение или вернуть ошибку 403.

    Правила наследования директив

    Директивы в Nginx делятся на скалярные (принимающие одно значение) и списочные (массивы значений). Поведение при наследовании у них кардинально различается.

    Скалярные директивы (например, root, index, client_max_body_size) наследуются сверху вниз. Если вы задали root /var/www/html; в контексте server, все вложенные location унаследуют этот путь, пока вы явно не переопределите его в конкретном location. Это классическое и интуитивно понятное поведение.

    Списочные директивы работают иначе, и это частая причина инцидентов при настройке. Яркий пример — директива add_header. Если вы определите заголовки безопасности в контексте server, а затем добавите специфичный заголовок в контексте location, Nginx не объединит эти списки. Вложенная директива полностью затрет все значения, унаследованные от родителя.

    Рассмотрим конфигурацию:

    При запросе к /api/ клиент получит только заголовок CORS. Чтобы сохранить базовые заголовки безопасности, их придется дублировать внутри location /api/. Это поведение справедливо для всех директив, формирующих массивы: access_log, error_page, fastcgi_param.

    Маршрутизация запросов: логика блоков server и location

    Когда Nginx получает HTTP-запрос, он выполняет двухэтапную маршрутизацию. Сначала он выбирает правильный блок server, а затем — правильный блок location внутри него.

    Выбор виртуального хоста (server)

    Алгоритм выбора server базируется на директивах listen (IP и порт) и server_name (доменное имя). Сначала Nginx отсеивает все блоки server, которые не соответствуют IP-адресу и порту, на которые пришел запрос. Среди оставшихся он ищет совпадение директивы server_name со значением HTTP-заголовка Host, который прислал браузер.

    Порядок поиска server_name строго регламентирован:

  • Точное имя (например, example.com).
  • Имя с wildcard в начале (например, *.example.com).
  • Имя с wildcard в конце (например, mail.*).
  • Регулярное выражение (начинается с тильды ~, например, ~^(www\.)?example\.com {
  • # Передача запроса на бэкенд fastcgi_pass 127.0.0.1:9000; } ` Если злоумышленник запросит /admin/config.php, Nginx сначала найдет префикс /admin/. Так как модификатора ^~ нет, он продолжит поиск по регулярным выражениям. Регулярное выражение ~ \.php$ совпадет, поиск остановится, и запрос будет передан на бэкенд. Ограничение доступа по IP-адресу, настроенное в /admin/, будет полностью проигнорировано, так как был выбран другой контекст.

    Чтобы исправить эту брешь, необходимо использовать модификатор ^~ для защищенной директории: location ^~ /admin/. В этом случае, найдя префикс, Nginx откажется от проверки регулярных выражений и применит правила доступа.

    Архитектура Nginx требует от инженера понимания того, как конфигурация проецируется на внутренние структуры данных в оперативной памяти Master-процесса. Конфигурация — это не скрипт, выполняющийся построчно, а набор правил, из которых Nginx строит дерево поиска (для префиксов) и линейные списки (для регулярных выражений) на этапе старта. Любое изменение конфигурации требует отправки сигнала SIGHUP Master-процессу (команда nginx -s reload`). При этом Master проверяет синтаксис, порождает новых Worker-ов с новой конфигурацией и дает команду старым Worker-ам плавно завершить работу после обслуживания текущих соединений, что обеспечивает нулевое время простоя (Zero Downtime) при обновлении инфраструктуры.