Динамическая конфигурация и service discovery

Рейтинг: 45.3% · 9 голосов
Самый подробный курс по 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

Динамическая конфигурация и service discovery

Сообщение 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 и аудит конфигурации
Боль: каждый автоскейл - это reload, а reload бьёт по продакшену

Представь типичную инфру 2026 года. Бэкенды живут в контейнерах, оркестратор поднимает и гасит реплики по нагрузке, IP-адреса меняются каждые пару минут, а Consul или Docker рисуют тебе актуальную карту сервисов. И посреди этого стоит твой nginx с прибитым гвоздями статическим upstream:

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

upstream backend {
    server 10.0.0.1:9000;
    server 10.0.0.2:9000;
    keepalive 64;
}
Поднялась третья реплика - её адреса в конфиге нет. Старую погасили - nginx упорно шлёт туда трафик, ловит connection refused, пассивный max_fails выкидывает пир только после нескольких ошибок. Единственный честный способ обновить список - переписать файл и сделать reload. И вот тут начинается боль, про которую новичкам не рассказывают.

Reload не бесплатный. Команда nginx -s reload (или systemctl reload) запускает мягкую перезагрузку: мастер читает новый конфиг, поднимает новые воркеры, а старым говорит "доработайте текущие соединения и умрите". Звучит гладко, но на дистанции это так:
  • обнуляются все счётчики stub_status и внутренние метрики воркеров - твой Prometheus видит пилу на графиках;
  • старые воркеры висят в состоянии shutting down, держа долгие keepalive и WebSocket-соединения, и при частых reload их накапливается десятки (так называемые shutting-down worker leak);
  • прогретые кэши (open_file_cache, ssl_session_cache в памяти воркера, lua shared-структуры) сбрасываются;
  • если конфиг внезапно невалиден - reload молча откатится на старый, и ты не сразу заметишь, что деплой бэкендов не доехал.
Делать это на каждый чих автоскейла - значит превратить шлюз в мигалку. Нам нужна динамика без reload: менять список бэкендов, веса, выводить пир в обслуживание - на лету, не трогая процесс. Разберём четыре рабочих подхода 2026 года и главное - когда какой брать.

Изображение

nginx resolver и переменная в proxy_pass: DNS как точка истины

Самый старый и самый коварный трюк. По умолчанию nginx резолвит имена бэкендов один раз при старте/reload и кэширует IP навсегда. Для контейнеров это катастрофа: имя app в Docker-сети сегодня указывает на 172.18.0.5, завтра на 172.18.0.9, а nginx про это не узнает.

Лечится двумя способами. Первый - старый, через переменную в proxy_pass. Как только адрес апстрима задан переменной, nginx обязан резолвить его в рантайме через директиву resolver:

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

location /api/ {
    resolver 127.0.0.11 valid=10s ipv6=off;   # встроенный DNS Docker
    set $upstream_app http://app:9000;
    proxy_pass $upstream_app$request_uri;
}
Здесь 127.0.0.11 - это embedded DNS самого Docker. valid=10s говорит "не верь TTL дольше 10 секунд". Это и есть главная грабля nginx resolver: без явного valid nginx уважает TTL из DNS-ответа, а контейнерные DNS часто отдают TTL=0 или совсем мелкий, и ты либо ддосишь резолвер запросами, либо, наоборот, держишь протухший IP. Второй подводный камень: переменная в proxy_pass выключает балансировку по upstream-блоку - ты теряешь keepalive-пул, веса, max_fails. proxy_pass по переменной просто берёт первый подходящий адрес. Для одиночного сервиса за внутренним DNS это ок, для пула - уже нет.

Поэтому в современном nginx (параметр resolve в open-source появился в 1.27.3 и доехал до stable 1.30) правильнее резолвить прямо в upstream-блоке:

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

resolver 127.0.0.11 valid=10s ipv6=off;

upstream backend {
    zone backend 256k;             # ОБЯЗАТЕЛЬНО: пиры в shared memory
    server app.internal:9000 resolve max_fails=2 fail_timeout=5s;
    keepalive 32;
}

server {
    location /api/ { proxy_pass http://backend; }
}
Параметр resolve заставляет nginx в фоне перепрашивать DNS по TTL (или по valid) и обновлять список пиров без reload: одно DNS-имя за round-robin DNS разворачивается в несколько IP, и они становятся отдельными бэкендами с полноценной балансировкой и keepalive. Но есть жёсткое условие - директива zone, иначе nginx откажется стартовать с ошибкой про shared memory: рантайм-резолвинг требует, чтобы пиры лежали в общей памяти, видимой всем воркерам. Это уже годный nginx dynamic upstream для контейнеров и k8s-headless-сервисов, и работает он в чистом open-source.

Angie API upstream: добавить бэкенд без reload одним PATCH

DNS - это всё равно опосредованно: ты управляешь не nginx, а зоной. Хочется прямого рычага - REST-ручки, которая меняет состав upstream немедленно. В мире nginx это исторически было привилегией платного nginx Plus (его /api позволяет POST/PATCH/DELETE серверов). В 2026 живая бесплатная альтернатива - Angie, форк nginx с детальным /status JSON по http/upstreams/zones и динамическим управлением апстримами через REST.

Сначала включаем API. Эндпоинт api отдаёт и метрики, и (в Angie PRO) запись в конфиг:

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

http {
    upstream backend {
        zone backend 1m;          # zone обязателен для динамики
        server 10.0.0.1:9000;
    }

    server {
        listen 127.0.0.1:8080;
        location /status/ { api /status/; }          # чтение метрик
        location /config/ {                          # запись (PRO)
            api /config/;
            allow 10.0.0.0/8;
            deny all;
        }
    }
}
Чтение состояния - обычный GET, читаемый JSON:

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

$ curl -s http://127.0.0.1:8080/status/http/upstreams/backend
{
  "peers": {
    "10.0.0.1:9000": {
      "server": "10.0.0.1:9000",
      "state": "up",
      "selected": { "current": 3, "total": 1841 },
      "health": { "fails": 0, "unavailable": 0 }
    }
  }
}
Теперь самое вкусное - angie api upstream на запись. Чтобы добавить новый бэкенд на лету, ты PUT-ишь его в /config (в Angie PRO раздел /config/ принимает PUT/PATCH/DELETE, и все апдейты атомарны - применяются целиком или никак):

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

# добавить бэкенд без reload
$ curl -s -X PUT \
    http://127.0.0.1:8080/config/http/upstreams/backend/servers/10.0.0.2:9000/ \
    -d '{ "weight": 2, "max_fails": 2, "fail_timeout": "5s" }'
{ "success": "Created", ... }
Снять пир в обслуживание (drain - дать дотечь текущим сессиям, новые не слать) и потом удалить:

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

# мягкий вывод: drain
$ curl -X PATCH http://127.0.0.1:8080/config/http/upstreams/backend/servers/10.0.0.1:9000/ \
    -d '{ "drain": true }'

# окончательное удаление пира
$ curl -X DELETE http://127.0.0.1:8080/config/http/upstreams/backend/servers/10.0.0.1:9000/
Ни один reload не выполнен, метрики и keepalive живых пиров не сброшены, счётчики не обнулились. Это и есть nginx upstream без reload в чистом виде. Грабля: в open-source Angie /status доступен на чтение, а полноценная запись в /config (и сохранение динамически добавленных серверов между рестартами через state) - фишка Angie PRO. Так что для прода планируй редакцию заранее: open-source даёт метрики + Docker-discovery (ниже), PRO даёт write-API и активные health-checks.

Service discovery: Docker-метки в Angie и Consul-template для всех остальных

REST-ручку всё равно надо кому-то дёргать. В динамичной инфре этим занимается service discovery. Два практичных пути.

1. Нативное автообнаружение по Docker-меткам (Angie 1.10+). Это, пожалуй, главная фича 2026 для тех, кто живёт в docker-compose. Angie сам коннектится к Docker-сокету, слушает поток событий (контейнер поднялся/упал) и по меткам контейнера сам наполняет upstream - без внешних скриптов и без reload:

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

http {
    docker_endpoint unix:/var/run/docker.sock;

    upstream backend {
        zone backend 1m;        # пиры из Docker кладутся сюда
    }
}
А в docker-compose у сервиса просто вешаешь метки:

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

services:
  app:
    image: myapp:latest
    labels:
      - "angie.network=app_net"
      - "angie.http.upstreams.backend.port=9000"
      - "angie.http.upstreams.backend.weight=2"
      - "angie.http.upstreams.backend.max_fails=2"
Метка angie.http.upstreams.backend.port=9000 говорит "добавь IP этого контейнера в upstream backend на порт 9000". Масштабируешь docker compose up --scale app=5 - и пять контейнеров сами влетают в апстрим; гасишь - сами вылетают. Никакого reload, никаких внешних агентов. Для одиночного хоста и Swarm - это самый дешёвый путь к nginx service discovery.

2. Consul-template - универсальный, но через reload. Когда источник истины - это Consul (k8s, мультидата-центр, классический nginx без Angie), используют связку consul-template. Это отдельный демон: он подписывается на каталог Consul, при изменении состава сервиса рендерит .conf из шаблона и дёргает reload:

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

upstream backend {
    zone backend 1m;
{{ range service "app" }}
    server {{ .Address }}:{{ .Port }} max_fails=2 fail_timeout=5s;
{{ else }}
    server 127.0.0.1:9000 down;   # заглушка, чтоб конфиг был валиден
{{ end }}
}
Запускается так:

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

consul-template \
  -template "backend.ctmpl:/etc/nginx/upstream.conf:nginx -t && nginx -s reload"
Здесь честно признаём минус: nginx consul через consul-template - это всё-таки reload на каждое изменение, со всеми его болями (обнуление метрик, накопление shutting-down воркеров). Поэтому ставь debounce (wait в consul-template), чтобы не релоадить на каждый чих, и обязательно nginx -t перед reload - иначе пустой service в Consul отрендерит upstream без единого сервера, конфиг не пройдёт проверку, а consul-template без -t убьёт прод битым файлом. Если нужен честный no-reload поверх Consul - смотри в сторону Angie с его write-API, который Consul-агент будет дёргать вместо рендера файлов.

balancer_by_lua: полностью динамический выбор апстрима на OpenResty

Когда логика выбора бэкенда сложнее, чем "round-robin по списку" - канареечный роутинг по заголовку, шардирование по user_id, выбор дата-центра по гео - на сцену выходит OpenResty и фаза balancer_by_lua. Идея радикальная: upstream-блок становится почти пустым, а реального пира на каждый запрос выбирает Lua через ngx.balancer.set_current_peer. Это самый гибкий механизм Lua-балансировки, но и самый низкоуровневый.

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

upstream dynamic {
    server 0.0.0.1;                 # фейк-пир: обязателен, реальный выберет Lua
    balancer_by_lua_block {
        local balancer = require "ngx.balancer"

        -- сюда твою логику discovery: из shared dict, из Consul, из DNS
        local peers = ngx.shared.peers
        local addr  = peers:get("app") or "10.0.0.1:9000"
        local host, port = addr:match("^(.-):(%d+)$")

        local ok, err = balancer.set_current_peer(host, tonumber(port))
        if not ok then
            ngx.log(ngx.ERR, "set_current_peer failed: ", err)
            return ngx.exit(502)
        end
        balancer.set_more_tries(2)   -- разрешить ретрай на другом пире
    }
    keepalive 16;
}
Фейковый server 0.0.0.1 - не опечатка: nginx требует хотя бы один сервер в upstream, но реальный адрес мы подменяем из Lua. Откуда брать адреса? Если у тебя домены, а не IP - резолвить руками через lua-resty-dns, потому что в balancer_by_lua обычный resolver недоступен, и ты не можешь блокирующе ходить в DNS на каждый запрос:

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

-- worker_init или таймер, НЕ в самом balancer-фазе на каждый запрос
local resolver = require "resty.dns.resolver"
local r = resolver:new{ nameservers = {"127.0.0.11"}, timeout = 2000 }
local answers = r:query("app.internal", { qtype = r.TYPE_A })
-- сложить answers[idx].address в ngx.shared.peers по таймеру раз в N секунд
Грабля Lua-балансировки: в фазе balancer_by_lua нельзя делать тяжёлые блокирующие вызовы (cosocket-резолв, HTTP в Consul) на каждый запрос - это убьёт латентность. Discovery выноси в фоновый ngx.timer.every, который раз в несколько секунд обновляет shared dict, а balancer только читает из памяти. И помни: cosocket-пул per-worker, соединения из таймера нельзя шарить между воркерами - каждый воркер крутит свой опрос. balancer_by_lua бери, когда стандартного round-robin и Angie API мало; для 90% задач хватает resolve или Angie-discovery.

Когда что: короткая матрица выбора
  • resolver + resolve в upstream - бэкенды за внутренним DNS (Docker, k8s headless, Consul DNS). Чистый open-source, no-reload. Грабля: TTL/кэш, нужен zone.
  • Angie Docker-метки - docker-compose/Swarm, хочешь zero-config discovery без скриптов. Бесплатно, no-reload.
  • Angie API (/config PATCH) - есть свой оркестратор/агент, нужен прямой императивный контроль и drain. Write-API - это PRO.
  • consul-template - источник истины Consul, классический nginx. Universal, но через reload (ставь debounce + nginx -t).
  • balancer_by_lua - нестандартная логика выбора (канарейка, шардинг, гео). Максимум гибкости, максимум ответственности.
Мини-лаба: добавляем бэкенд на лету (Angie + DNS)

Повтори руками, 15 минут. Нужен Docker.
  • Подними Angie: docker run -d --name angie -p 8080:8080 -v $PWD/angie.conf:/etc/angie/angie.conf:ro -v /var/run/docker.sock:/var/run/docker.sock:ro docker.angie.software/angie:latest
  • В конфиге включи docker_endpoint unix:/var/run/docker.sock; и пустой upstream backend { zone backend 1m; } плюс location /status/ { api /status/; }
  • Запусти два контейнера-эхо с метками: docker run -d --label "angie.http.upstreams.backend.port=80" --label "angie.network=bridge" hashicorp/http-echo -text=one (повтори для two).
  • Проверь, что оба пира появились БЕЗ reload: curl -s localhost:8080/status/http/upstreams/backend - в peers два адреса.
  • Погаси один контейнер docker stop ... и снова дёрни /status - пир исчез сам. Никакого nginx -s reload ты не вызывал ни разу.
  • Бонус: замени метки на upstream через DNS - добавь resolver 127.0.0.11 valid=10s; и server tasks.app resolve; в zone-upstream, отмасштабируй сервис и смотри, как меняется список.
Если пир не появился - первое, что проверь: метка angie.network должна совпадать с сетью, в которой реально живёт контейнер, иначе Angie не найдёт его IP.

Контрольные вопросы
  • Почему reload - это не бесплатная операция, и какие три конкретных побочных эффекта он даёт на нагруженном шлюзе?
  • Зачем upstream с параметром resolve обязательно требует директиву zone, и что произойдёт без неё?
  • Чем drain отличается от немедленного DELETE пира в Angie API, и в каком сценарии drain критичен?
  • Почему в фазе balancer_by_lua нельзя резолвить домен через lua-resty-dns на каждый запрос, и куда вынести discovery?
Итог

Статический upstream хорош для статичной инфры, но автоскейл и контейнеры требуют менять состав бэкендов без reload, который обнуляет метрики и плодит зомби-воркеры. Четыре инструмента 2026: resolver + resolve в upstream (DNS-истина, чистый open-source), нативное Angie-discovery по Docker-меткам (zero-config для compose), Angie API с PATCH/drain (императивный контроль, write - PRO), consul-template (универсально, но через reload) и balancer_by_lua для нестандартной логики выбора. Выбирай по источнику истины и требуемой гибкости - и держи софт обновлённым, динамика динамикой, а CVE никто не отменял.
👍4 ❤️2 🔥3 😄 🤔
✔ Лучший ответ сформирован автоматически — nixosninja
А подскажите: если в balancer_by_lua воткнуть resolve через resolver в самом upstream - оно сработает или фаза перебьёт? У меня домены, не IP, и таймер с lua-resty-dns кажется костылём.
Перейти к ответу →
Аватара пользователя
main_vasya
Сообщения: 1
Зарегистрирован: 06 июн 2026, 08:48

Re: Динамическая конфигурация и service discovery

Сообщение main_vasya »

Сидел на consul-template год и не понимал откуда зомби-воркеры в ps, а это reload на каждый деплой копился. Перевёл discovery на Angie с docker-метками - график метрик наконец перестал быть пилой, спасибо.
👍4 ❤️ 🔥 😄 🤔
Аватара пользователя
nixosninja
Сообщения: 1
Зарегистрирован: 08 июн 2026, 11:37
Репутация: 183

Re: Динамическая конфигурация и service discovery

Сообщение nixosninja »

✔ Лучший ответ — сформирован автоматически
А подскажите: если в balancer_by_lua воткнуть resolve через resolver в самом upstream - оно сработает или фаза перебьёт? У меня домены, не IP, и таймер с lua-resty-dns кажется костылём.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Angie: современный форк Nginx 2026
Следующая глава →
Nginx как API-gateway и Kubernetes Gateway API

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

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

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

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

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