1. 1
  2. 2
  3. 3
  4. 4

# теория · шаг 1 из 4

Что делает reload и чего не делает

Коротко

nginx -s reload - это сигнал HUP: мастер перечитывает конфиг, поднимает новых воркеров и даёт старым доработать начатые запросы. Битый конфиг просто не применяется, сайт живёт на старом. Но успешный код возврата не означает, что конфиг применился: если новый порт занят, reload вернёт ноль, а правда останется только в error_log. Старый воркер с живым соединением не уйдёт никогда - для этого есть worker_shutdown_timeout: по его истечении оставшиеся соединения закрываются. Логи ротируются лишь в паре с nginx -s reopen.

Знаешь про HUP и worker_shutdown_timeout - листай до «Что ломается без этого»: там про молчаливый ноль от reload и про ротацию.

Конфиг прошёл nginx -t, ты выполняешь nginx -s reload, команда возвращает ноль. Значит ли это, что новый конфиг работает?

$  команда
nginx -t && nginx -s reload; echo "код возврата: $?"
↳  выводстенд, порт 9090 занят чужим процессом
nginx: configuration file /etc/nginx/nginx.conf test is successful
код возврата: 0

А сайт при этом продолжает отвечать по старому конфигу, и единственный след правды - строка в логе:

↳  вывод/var/log/nginx/error.log
bind() to 0.0.0.0:9090 failed (98: Address in use)

На что это похоже

Reload - это не «перезапуск», а пересменка. Смена, стоящая за стойкой, дообслуживает своих посетителей и уходит; новая смена принимает всех, кто пришёл после. Дверь при этом не закрывается ни на секунду.

Отсюда два следствия, о которых обычно не думают. Уходящая смена остаётся столько, сколько длится разговор с последним посетителем. И если новая смена не смогла заступить, старая просто продолжает работать - об этом никто не объявит.

Механизм: пересменка воркеров

мастер перечитал конфиг старые воркеры дорабатывают начатое новые воркеры берут новые соединения конфиг с ошибкой: ничего не меняется, старые продолжают работать
Начатые запросы не рвутся - и ни один не торопится закончиться, пока не задан worker_shutdown_timeout (по его истечении оставшиеся соединения закрываются). Сразу рвутся только простаивающие keepalive-соединения

Правило

По HUP мастер проверяет конфиг, открывает новые слушающие сокеты, запускает новых воркеров и командует старым завершиться плавно: новых соединений они не берут, начатые дорабатывают. Простаивающее keepalive-соединение старый воркер закрывает сразу (замер на 1.31.8: 0,1 с после HUP) - клиент просто открывает новое. Битый конфиг не применяется вовсе - сайт остаётся на прежнем.

$  команда
nginx -t && nginx -s reload

Две команды через && - привычка, экономящая инциденты. Но помни границу: -t разбирает конфиг и открывает то, что в нём названо. Отсутствующий сертификат, include или каталог лога он поймает (замер: cannot load certificate). А вот свободен ли порт и есть ли на диске каталог из root - выясняется уже при применении и на живом запросе.

Разбор: старый воркер, который не уходит

Стенд: через nginx идёт соединение к бэкенду, который отвечает по кусочку две минуты. Делаем reload и смотрим процессы.

↳  выводps -o args
nginx: master process /usr/sbin/nginx
nginx: worker process is shutting down
nginx: worker process

Через две секунды после reload старый воркер на месте, запрос жив. Через семнадцать - всё ещё на месте, запрос жив. Он честно ждёт конца соединения, и для WebSocket или SSE это «никогда». Десять релоадов за день - десять таких воркеров в памяти, каждый со своим старым конфигом.

Лечится одной строкой, у которой нет значения по умолчанию:

/etc/nginx/nginx.conf
worker_shutdown_timeout 5s;

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

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

Разбор: что переживает reload

Проверяем на limit_req с rate=1r/m:

действие ответ
первый запрос 200
второй сразу 503
после reload 503
после смены размера зоны с 1m на 2m 200

Счётчики лимитов живут в разделяемой памяти и переживают перезагрузку конфига - это правильно, иначе релоад стал бы способом обойти лимит. А вот изменение параметров зоны заставляет создать её заново, и счётчики обнуляются. Ровно так же ведёт себя кеш: содержимое proxy_cache_path остаётся, но правка параметров зоны его отвязывает.

Заодно стоит помнить, чего reload не меняет вовсе: сам бинарник - для него нужен рестарт или приём из следующего урока. А вот user и load_module reload подхватывает: мастер перечитывает конфиг целиком, и новые воркеры стартуют уже под новым пользователем. На стенде nginx 1.31.6 после смены user nginx на user nobody и reload воркер работал от nobody. Поэтому права на файлы, сокеты и каталоги кеша проверяют ДО перезагрузки, а не после.

Что ломается без этого

Ротация логов без сигнала. logrotate переименовывает файл, а nginx держит открытый дескриптор и продолжает писать в него - под новым именем. Замер:

↳  выводстенд
mv access.log access.log.1 ; curl .../after-rename
новый access.log создан: нет
строк с after-rename в переименованном файле: 1

После nginx -s reopen появляется новый файл, и следующая строка идёт уже туда. Пока сигнала нет, место на диске занимают файлы, которых не видно в ls, а свежий лог не растёт - и это замечают не сразу, потому что мониторинг обычно следит за размером каталога, а не за датой последней строки.

Молчаливый ноль от reload. Из крючка в начале урока: команда вернула успех, конфиг не применился. Такое бывает всегда, когда ошибка обнаруживается не при разборе конфига, а при его применении: занятый порт, отсутствующий файл сертификата, нехватка прав. Поэтому после выкладки смотрят не код возврата, а error_log и поведение сайта.

Текст системной ошибки зависит от библиотеки. На alpine строка выглядит как (98: Address in use), на debian - (98: Address already in use). Номер одинаков, слова разные; искать в логах надо по номеру, иначе grep из чужой статьи не находит ничего.

Зачем это в работе

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

Практический минимум для боевого сервера: worker_shutdown_timeout в конфиге, nginx -s reopen в postrotate и привычка после каждой выкладки посмотреть хвост error_log. Проверить второе стоит один раз:

$  команда
grep -A3 postrotate /etc/logrotate.d/nginx

Теперь сам

Сайт с WebSocket. Ты выкатываешь конфиги по десять раз в день и однажды замечаешь, что nginx съел четыре гигабайта памяти, а в ps два десятка процессов worker process is shutting down. Что произошло и что сделать сейчас, а что - чтобы не повторилось?

Ответ: каждый релоад оставлял по воркеру, который ждёт конца WebSocket-соединений, а они не кончаются. Сейчас - дописать worker_shutdown_timeout (например, 30 секунд) и сделать релоад; помня, что уже висящие воркеры эта строка не догонит, их придётся убрать рестартом в спокойное время. Чтобы не повторилось - та же строка в конфиге плюс алерт на воркеры в состоянии is shutting down, которые живут дольше нескольких минут. Просто считать процессы нельзя: во время reload поколений воркеров два, а при proxy_cache рядом живут ещё cache manager и cache loader.

Главное

После reload проверяй error_log и новый ответ сервера. Успешная отправка HUP не означает, что мастер смог открыть новые сокеты и применить конфиг. Следи также за уходящими воркерами: уже начатые долгие соединения могут удерживать старые процессы после нескольких выкладок.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.