Деплой, перезагрузка и конфиг как код

Рейтинг: 75.8% · 12 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Деплой, перезагрузка и конфиг как код

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код (вы здесь)
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Представь: пятница, 18:00, прод держит 40 тысяч живых соединений, и тебе надо выкатить новый location и пару заголовков безопасности. Если ты сделаешь это руками через ssh и killall - кому-то прилетит оборванная загрузка, кому-то 502, а тебе - инцидент и испорченные выходные. А ведь nginx умеет применять конфиг вообще без обрыва соединений. И умеет даже заменить свой бинарник на лету, без единого сброшенного коннекта. Проблема не в инструменте - проблема в том, что мало кто выстраивает вокруг него нормальный пайплайн. В этом уроке мы соберём цепочку lint -> test -> reload -> проверка так, чтобы деплой nginx стал скучной рутиной, а не аттракционом.

Graceful reload: что реально происходит при nginx -s reload

Команда nginx перезагрузка конфига - это

Код: Выделить всё

nginx -s reload
, которая под капотом шлёт мастер-процессу сигнал SIGHUP. Важно понимать модель процессов nginx: есть один мастер (запущен от root, держит слушающие сокеты) и пачка воркеров (работают от непривилегированного юзера, обрабатывают соединения). Мастер код запросов не трогает - он дирижёр.

Что делает мастер по SIGHUP по шагам:
  • Перечитывает и парсит конфиг. Если в нём синтаксическая ошибка - мастер ругается в error_log и остаётся на старом конфиге. Текущие воркеры продолжают жить, ничего не падает. Это ключевой момент: битый конфиг при reload не роняет прод, в отличие от рестарта.
  • Если конфиг валиден - заново открывает лог-файлы и слушающие сокеты (если listen изменился), применяет новые настройки.
  • Запускает новый набор воркеров с новым конфигом. Они сразу начинают принимать новые соединения.
  • Старым воркерам шлёт сигнал на graceful shutdown. Те перестают принимать новые коннекты, но дорабатывают уже открытые - текущие запросы, медленные загрузки, websocket-сессии. Когда все их соединения закрылись - старый воркер тихо умирает.
Вот как это выглядит в процессах прямо во время reload:

Код: Выделить всё

$ ps -eo pid,ppid,user,comm | grep nginx
  812     1 root     nginx: master process
 9001   812 nginx    nginx: worker process       # новый воркер
 9002   812 nginx    nginx: worker process       # новый воркер
 8730   812 nginx    nginx: worker process is shutting down   # старый, дорабатывает
 8731   812 nginx    nginx: worker process is shutting down
Строка "is shutting down" - это и есть старый воркер, который ещё держит чьи-то живые соединения. Когда они закроются, он исчезнет. Поэтому формально reload не мгновенный: если у тебя долгие соединения (стриминг, длинный websocket), старые воркеры могут висеть минутами. Управляется это директивой

Код: Выделить всё

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

Грабли тут две. Первая: если процессов "is shutting down" накапливается всё больше от reload к reload - значит у тебя залипают долгие коннекты, ставь разумный worker_shutdown_timeout. Вторая: reload не перечитывает изменения, требующие смены user, или некоторые настройки, заданные на старте мастера - такое требует полноценного рестарта.

Изображение

Бинарный апгрейд на лету: USR2 + QUIT без потери соединений

Reload меняет конфиг, но что если надо обновить сам бинарник nginx (новая версия, пересобранный с модулем)? Рестарт сервиса оборвёт всё. Здесь работает фокус с двумя сигналами - это родная фишка nginx, придуманная как раз для нулевого даунтайма.

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

Порядок такой:

Код: Выделить всё

# 1. Заменили бинарник на диске на новую версию (apt upgrade / положили вручную)
# 2. Шлём старому мастеру USR2 - он стартует НОВЫЙ мастер из нового бинарника
kill -USR2 $(cat /run/nginx.pid)

# Старый pid-файл переименован в nginx.pid.oldbin, новый мастер записал свой pid
$ ls /run/nginx*
/run/nginx.pid  /run/nginx.pid.oldbin

# 3. Теперь два мастера + два набора воркеров слушают порт. Проверяем версию/логи.
# 4. Старым воркерам говорим "хватит принимать новое" сигналом WINCH старому мастеру
kill -WINCH $(cat /run/nginx.pid.oldbin)

# Старые воркеры дорабатывают текущее и гаснут, новые держат весь трафик.
# 5а. Всё ок -> добиваем старого мастера
kill -QUIT $(cat /run/nginx.pid.oldbin)

# 5б. Что-то сломалось -> откат: будим старые воркеры обратно
kill -HUP  $(cat /run/nginx.pid.oldbin)   # старый мастер снова поднимает воркеры
kill -QUIT $(cat /run/nginx.pid)          # гасим новый мастер
Гениальность в шаге 5б: пока ты не послал QUIT старому мастеру, у тебя есть бесплатный мгновенный откат на старую версию без даунтайма. Это и есть канареечный деплой на уровне процессов. На практике в эпоху контейнеров этим пользуются реже (проще выкатить новый образ через оркестратор), но понимать механику обязательно - на bare-metal и в классических VM это до сих пор золотой стандарт зеро-даунтайм апгрейда. Процедура не менялась годами и одинаково работает в nginx 1.30, Angie и OpenResty.

Тестируй ДО деплоя: nginx -t, gixy и crossplane в nginx ci cd

Код: Выделить всё

nginx -s reload
не уронит прод при битом синтаксисе, но логическую дыру он применит молча. Поэтому деплой без предварительной проверки - игра в рулетку. Собираем три уровня контроля.

1. nginx -t - синтаксис и базовая валидация. Это первое, что должно стоять в любом nginx ci cd пайплайне. Проверяет, что конфиг парсится, файлы существуют, директивы на месте:

Код: Выделить всё

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Тонкость:

Код: Выделить всё

nginx -t
реально загружает модули и открывает файлы, поэтому в CI его удобнее всего гонять в том же docker-образе, что и прод - иначе локальная сборка без нужного модуля даст ложную ошибку. Именно поэтому в эталонном ngx-trace-gateway healthcheck контейнера - это

Код: Выделить всё

nginx -t
: контейнер считается здоровым, только если его конфиг валиден.

2. gixy - линтер безопасности. nginx gixy - это не про синтаксис (он и так валиден), а про логические дыры: SSRF через

Код: Выделить всё

proxy_pass
с переменной из запроса, обход (path traversal), небезопасные

Код: Выделить всё

add_header
, которые молча отключаются в дочерних блоках, отсутствие

Код: Выделить всё

internal
у служебных локаций. Это статический анализатор именно угроз.

Код: Выделить всё

$ gixy /etc/nginx/nginx.conf

==================== Results ===================
Problem: [http_splitting] Possible HTTP-Splitting vulnerability.
Severity: HIGH
Description: Using variables that can contain "\n" may lead to http injection.
Pseudo config:
  location ~ /v1/((?<action>[^.]*)\.json) {
      add_header X-Action $action;
  }
==================== Summary ===================
Total issues: Unspecified: 0  Low: 0  Medium: 0  High: 1
Знай контекст 2026: оригинальный gixy от Яндекса давно не развивался, активно поддерживаемый форк сейчас - Gixy-Next (сайт gixy.io, репозиторий MegaManSec/Gixy-Next), он умеет современный парсинг, больше плагинов и даже браузерный WASM-сканер. Ставится так же через pip, запускается так же. Не тащи в новый пайплайн заброшенную ветку - бери актуальный форк.

3. crossplane - парсер конфига в JSON. Это инструмент от команды nginx: он превращает любой конфиг (со всеми include) в структурированный JSON и обратно. Зачем в деплое - чтобы программно проверять инварианты, которые ни gixy, ни nginx -t не ловят. Например, политика компании: "везде должен быть server_tokens off" или "ни один proxy_pass не идёт на http без TLS внутрь периметра".

Код: Выделить всё

$ crossplane parse /etc/nginx/nginx.conf | \
  jq '.config[].parsed[] | select(.directive=="server_tokens")'
{
  "directive": "server_tokens",
  "line": 12,
  "args": ["off"]
}
Если jq ничего не вернул - значит правило нарушено, и пайплайн падает. crossplane превращает "конфиг как код" в по-настоящему проверяемый код.

Конфиг как код: nginx конфиг git, шаблоны и docker

Главный сдвиг мышления: конфиг nginx - это не файлы на сервере, которые кто-то правит вживую. Это исходники в репозитории. nginx конфиг git - база всего: любое изменение проходит через ветку, ревью и историю. Эталонный ngx-trace-gateway именно так и устроен - модульная структура (nginx.conf подключает events.conf и http.conf, тот тянет 14 файлов globals/* по порядку плюс sites-enabled/*), и всё это лежит в git. Когда конфиг разбит на маленькие осмысленные файлы, diff в PR читаем, а конфликты при мерже редки.

Шаблонизация. Один и тот же конфиг для dev/staging/prod не должен копироваться вручную - меняются только апстримы, домены, лимиты. Тут два рабочих подхода:
  • Ansible + Jinja2 - шаблоны nginx.conf.j2 с переменными окружения, рендер на деплое. Классика для статичной инфраструктуры.
  • consul-template - рендерит конфиг из сервис-дискавери Consul и сам дёргает reload при изменении набора бэкендов. Живые апстримы без ручного редактирования.
Важно: после рендера шаблона - снова прогоняем lint и nginx -t, потому что шаблонизатор легко вставит пустую переменную и сломает директиву.

Docker как способ доставки. Контейнер фиксирует версию nginx и набор модулей намертво. Вот компактный docker-compose в духе эталона:

Код: Выделить всё

services:
  gateway:
    image: openresty/openresty:latest
    volumes:
      - ./conf:/usr/local/openresty/nginx/conf:ro   # конфиг ТОЛЬКО на чтение
      - ./logs:/var/log/nginx                       # логи на хост
      - ./cache:/var/cache/nginx                    # кэш на хост, переживает рестарт
    ports:
      - "80:80"
      - "443:443"
    healthcheck:
      test: ["CMD", "nginx", "-t"]   # здоров = конфиг валиден
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
Разбор ключевых решений. Том с конфигом смонтирован ro (read-only) - изнутри контейнера никто не подменит конфиг, иммутабельность гарантирована. Логи и кэш вынесены на хост: логи не теряются при пересоздании контейнера, а кэш переживает рестарт (иначе после каждого деплоя холодный старт и шторм запросов на бэкенд). Образ openresty/openresty:latest даёт OpenResty с Lua на борту - даже если сейчас Lua-блоков нет, задел на *_by_lua есть. И healthcheck nginx -t - оркестратор не пустит трафик в контейнер с битым конфигом.

Откат при ошибке в этой модели тривиален: git revert на предыдущий тег образа, передеплой. История в git - твоя машина времени.

resty CLI для Lua. Если в гейтвее есть Lua-логика, синтаксис .lua-файлов нужно проверять до деплоя. Утилита resty (идёт с OpenResty) запускает Lua прямо в окружении nginx без поднятия сервера:

Код: Выделить всё

$ resty -e 'local cjson = require "cjson"; print(cjson.encode({ok=true}))'
{"ok":true}
Это твой быстрый тест-стенд для Lua: проверить парсинг JWT, логику роутинга, работу с cjson - всё локально, в CI, до того как код увидит прод.

Практика: безопасный пайплайн деплоя одним скриптом

Собираем всё в воспроизводимую цепочку lint -> test -> reload -> проверка. Это шаблон деплой-шага, который кладётся в репозиторий рядом с конфигом и вызывается из CI:

Код: Выделить всё

#!/usr/bin/env bash
set -euo pipefail   # любая ошибка останавливает деплой немедленно

CONF=/etc/nginx/nginx.conf

echo ">> 1/5 gixy: аудит безопасности"
gixy "$CONF"                       # SSRF, http-splitting, обход alias

echo ">> 2/5 crossplane: проверка инвариантов политики"
crossplane parse "$CONF" | \
  jq -e '[.. | objects | select(.directive=="server_tokens" and .args[0]=="off")] | length > 0'

echo ">> 3/5 nginx -t: синтаксис и загрузка модулей"
nginx -t

echo ">> 4/5 reload без даунтайма"
nginx -s reload

echo ">> 5/5 smoke-проверка живого ответа"
sleep 1
curl -fsS -o /dev/null -w "status=%{http_code}\n" http://127.0.0.1/healthz

echo ">> deploy OK"
Логика жёсткая:

Код: Выделить всё

set -euo pipefail
гарантирует, что если gixy нашёл HIGH, jq не подтвердил инвариант, или nginx -t упал - до

Код: Выделить всё

nginx -s reload
дело не дойдёт. Прод трогается только после того, как все проверки зелёные. А финальный curl на /healthz - это smoke-тест: убедились, что после reload nginx реально отвечает 200, а не просто молча применил конфиг. Если healthz вернул не-200 - скрипт упадёт на set -e, и ты сразу увидишь, что reload хоть и прошёл, но что-то сломалось логически.

Типичные грабли деплоя
  • reload вместо restart там, где нужен restart. Смена user, добавление модуля через load_module, изменение числа listen-сокетов в части случаев требуют полного рестарта. reload их молча проигнорирует, и ты будешь час искать, почему "конфиг применился, а поведение старое".
  • nginx -t в чужом окружении. Прогнал тест на ноуте, где собран другой nginx без нужного модуля - получил ложную ошибку или, хуже, ложный успех. Тестируй в том же образе, что катишь в прод.
  • Залипшие "is shutting down". Долгие соединения держат старые воркеры живыми после reload. Без worker_shutdown_timeout они копятся, жрут память. Поставь таймаут.
  • add_header исчезает. Любой add_header в дочернем блоке (location) отменяет все унаследованные add_header родителя. gixy это ловит - игнорировать его warning тут опасно, можно потерять заголовки безопасности.
  • Конфиг правят на сервере мимо git. Ssh, vim, перезагрузка - и репозиторий разошёлся с реальностью. Следующий деплой из git затрёт горячую правку. Единственный источник правды - репозиторий, сервер только разворачивает.
  • Забыли USR2.pid.oldbin. При бинарном апгрейде поспешили с QUIT старому мастеру до проверки нового - потеряли возможность мгновенного отката.
Мини-лаба: собери пайплайн руками

Повтори по шагам, нужен docker:
  • Подними контейнер:

    Код: Выделить всё

    docker run -d --name gw -p 8080:80 -v $PWD/conf:/etc/nginx:ro openresty/openresty:latest
    (подложи минимальный nginx.conf с location /healthz { return 200 "ok"; }).
  • Установи линтеры:

    Код: Выделить всё

    pip install gixy-next crossplane
    . Прогони

    Код: Выделить всё

    gixy conf/nginx.conf
    - убедись, что чисто.
  • Сломай конфиг намеренно: убери точку с запятой в любой директиве. Запусти

    Код: Выделить всё

    docker exec gw nginx -t
    - увидь, что тест падает с указанием строки. Это твой предохранитель в CI.
  • Почини, затем измени тело /healthz на "ok2" и выполни

    Код: Выделить всё

    docker exec gw nginx -s reload
    . Проверь

    Код: Выделить всё

    curl localhost:8080/healthz
    - ответ сменился без рестарта контейнера.
  • Посмотри процессы во время reload:

    Код: Выделить всё

    docker exec gw ps aux | grep nginx
    - найди мастер и воркеры. На долгом запросе поймай строку "is shutting down".
  • Собери bash-скрипт из практики и прогони его целиком. Сломай инвариант (убери server_tokens off) и убедись, что до reload дело не доходит.
Контрольные вопросы
  • Почему

    Код: Выделить всё

    nginx -s reload
    с битым синтаксисом конфига не приводит к даунтайму, а полный рестарт - привёл бы?
  • Что делает сигнал USR2 и в какой момент бинарного апгрейда у тебя ещё есть возможность мгновенного отката без потери соединений?
  • Чем задача gixy принципиально отличается от задачи nginx -t? Приведи пример дыры, которую поймает gixy, но пропустит nginx -t.
  • Зачем в docker-compose эталона конфиг монтируется ro, а логи и кэш - на хост? Что сломается, если кэш оставить внутри контейнера?
Итог

Нулевой даунтайм при деплое nginx - это не магия, а две встроенные механики (graceful reload по SIGHUP и бинарный апгрейд через USR2/QUIT) плюс дисциплина вокруг них. Дисциплина = конфиг живёт в git, проходит цепочку gixy -> crossplane -> nginx -t -> reload -> smoke-проверка, и катится через иммутабельный docker-образ с healthcheck nginx -t. Сделай этот пайплайн один раз - и деплой перестанет быть событием, требующим геройства. Именно так и должна выглядеть скучная, надёжная эксплуатация шлюза.
👍3 ❤️3 🔥1 😄 🤔4
Аватара пользователя
joe19901
Сообщения: 1
Зарегистрирован: 25 май 2026, 20:00

Re: Деплой, перезагрузка и конфиг как код

Сообщение joe19901 »

Вот про откат через HUP oldbin вообще не знал, всегда думал что USR2 это билет в один конец. Получается пока старого мастера не убил QUIT - можно вернуться. Огонь.
👍1 ❤️1 🔥 😄 🤔1
Аватара пользователя
debian_maker
Сообщения: 1
Зарегистрирован: 11 май 2026, 07:50

Re: Деплой, перезагрузка и конфиг как код

Сообщение debian_maker »

А worker_shutdown_timeout какое значение разумным считается на проде с вебсокетами? Боюсь поставить мало и рубить живые сессии, а без него старые воркеры копятся.
👍 ❤️ 🔥2 😄 🤔1
Ответить
← Предыдущая глава
Stream-модуль: проксирование TCP и UDP
Следующая глава →
Сценарий: статика, SPA и кэширование за CDN

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Установка и первый конфиг nginxnginx в Docker

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость