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

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

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

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

Правило

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

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

Две команды через && - привычка, экономящая инциденты. Но помни границу: -t проверяет синтаксис и только его. Существует ли файл сертификата, свободен ли порт, пускает ли SELinux в каталог - выясняется уже при применении.

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

Стенд: через 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) и сам бинарник. Для них нужен рестарт или приём из следующего урока.

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

Ротация логов без сигнала. 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 секунд) и сделать релоад; помня, что уже висящие воркеры эта строка не догонит, их придётся убрать рестартом в спокойное время. Чтобы не повторилось - та же строка в конфиге плюс алерт на число процессов nginx: их должно быть ровно worker_processes плюс мастер.

Главное

reload = HUP: мастер перечитывает конфиг, поднимает новых воркеров, старым даёт доработать начатое; битый конфиг не применяется, и сайт живёт на прежнем. Код возврата reload не доказывает применение - при занятом порте он равен нулю, а правда только в error_log; nginx -t проверяет синтаксис, но не файлы, порты и права. Старый воркер с долгим соединением не уходит никогда (замер: жив и через семнадцать секунд), лечится worker_shutdown_timeout, у которого нет умолчания и который не догоняет уже уходящих воркеров. Счётчики лимитов и кеш переживают reload, а смена параметров зоны их обнуляет. Логи ротируются только вместе с nginx -s reopen.

Комментарии

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

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

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