Lua в Nginx: динамическая маршрутизация запросов

от автора

Привет, Хабр!

Сегодня рассмотрим то, как использовать Lua в Nginx: динамическую маршрутизацию, балансировку трафика, подмену заголовков и трансформацию тела запроса в реальном времени. OpenResty и lua‑nginx‑module позволяют перенести часть логики на уровень веб‑сервера, сокращая задержки и повышая гибкость.

Динамическое изменение upstream через lua-resty-balancer

Одной из самых крутых фич, за которую я люблю OpenResty, является возможность изменять upstream в рантайме. Вместо того чтобы перезагружать сервер при смене конфигурации, просто обновляем значение в lua_shared_dict — и всё работает.

Пример конфигурации:

http {     lua_shared_dict balancer_cache 10m;      upstream dynamic_upstream {         server 127.0.0.1:8080;  # Резервный сервер по умолчанию     }      server {         listen 80;         server_name example.com;          location / {             access_by_lua_block {                 local balancer_cache = ngx.shared.balancer_cache                 local new_upstream = balancer_cache:get("current_upstream")                 if new_upstream then                     ngx.var.upstream = new_upstream                 else                     ngx.var.upstream = "dynamic_upstream"                 end                 ngx.log(ngx.INFO, "Используем upstream: ", ngx.var.upstream)             }             proxy_pass http://$upstream;         }     } }

Храним текущий upstream в lua_shared_dict и можем динамически менять его, например, через REST API или cron‑задачу. Так даже можно реализовывать failover‑механизмы, A/B‑тесты и какие‑нибудь схемы балансировки. Но, как говорится, с большой силой приходит большая ответственность: всегда добавляйте обработку ошибок и логирование.

Подмена заголовков и изменение тела запроса/ответа на ходу

Еще есть возможность изменять заголовки и тело запросов/ответов в реальном времени.

Пример изменения заголовков:

http {     lua_shared_dict balancer_cache 10m;      upstream dynamic_upstream {         server 127.0.0.1:8080;  # Резервный сервер по умолчанию     }      server {         listen 80;         server_name example.com;          location / {             access_by_lua_block {                 local balancer_cache = ngx.shared.balancer_cache                 local new_upstream = balancer_cache:get("current_upstream")                 if new_upstream then                     ngx.var.upstream = new_upstream                 else                     ngx.var.upstream = "dynamic_upstream"                 end                 ngx.log(ngx.INFO, "Используем upstream: ", ngx.var.upstream)             }             proxy_pass http://$upstream;         }     } }

Подменяем заголовок User‑Agent. Но на этом возможности не заканчиваются: можно динамически добавлять, удалять или модифицировать любые заголовки, в зависимости от бизнес‑логики. А теперь пример изменения тела ответа:

server {     listen 80;     server_name example.com;      location /modify_body {         proxy_pass http://backend_service;         header_filter_by_lua_block {             ngx.header["Content-Type"] = "application/json"         }         body_filter_by_lua_block {             local chunk = ngx.arg[1]             if chunk then                 -- Пример: добавляем поле "extra": "value" в JSON-ответ.                 chunk = string.gsub(chunk, "}", ', "extra": "value"}')             end             ngx.arg[1] = chunk         }     } }

Приправляем JSON‑ответ, добавляя новое поле. Конечно, тут еще потребуется более аккуратное парсирование JSON и проверка корректности данных. Но главное здесь — понять принцип: можно менять данные на ходу, без вмешательства в бек.

Nginx как API Gateway с динамической маршрутизацией

Используем lua_shared_dict для хранения динамических правил маршрутизации, что позволяет адаптироваться к изменяющимся условиям — будь то A/B‑тестирование, экстренное переключение на резервный сервер или плавное обновление конфигурации.

http {     lua_shared_dict route_config 1m;      server {         listen 80;         server_name api.example.com;          location / {             access_by_lua_block {                 local route_config = ngx.shared.route_config                 local uri = ngx.var.uri                 local route = route_config:get(uri)                  if route then                     ngx.var.target = route                 else                     ngx.var.target = "default_backend"                     ngx.log(ngx.WARN, "Маршрут для URI ", uri, " не найден, используем default_backend")                 end                  ngx.log(ngx.INFO, "Маршрутизируем URI: ", uri, " к ", ngx.var.target)             }             proxy_pass http://$target;         }     } }

lua_shared_dict route_config позволяет хранить и изменять правила маршрутизации без перезагрузки Nginx, что по сути база для обновлений через API или по расписанию. В access_by_lua_block определяем маршрут на основе URI, а при его отсутствии автоматом используем default_backend, тем самым делая некую отказоустойчивость.

Redis+Lua: управление балансировкой трафик

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

Пример интеграции Redis с Nginx:

http {     lua_package_path "/usr/local/openresty/lualib/?.lua;;";     lua_shared_dict redis_cache 10m;      server {         listen 80;         server_name api.example.com;          location /traffic {             access_by_lua_block {                 local redis = require "resty.redis"                 local red = redis:new()                 red:set_timeout(1000) -- Таймаут 1 секунда                  local ok, err = red:connect("127.0.0.1", 6379)                 if not ok then                     ngx.log(ngx.ERR, "Ошибка подключения к Redis: ", err)                     return ngx.exit(500)                 end                  local route, err = red:get("route:" .. ngx.var.uri)                 ngx.var.backend = (route and route ~= ngx.null) and route or "default_backend"                  ngx.log(ngx.INFO, "Выбран backend: ", ngx.var.backend)                  -- Возвращаем соединение в пул                 local ok, err = red:set_keepalive(10000, 100)                 if not ok then                     ngx.log(ngx.ERR, "Ошибка возврата соединения в пул: ", err)                 end             }             proxy_pass http://$backend;         }     } }

Nginx перед каждым запросом обращается к Redis по ключу route:/traffic для выбора целевого backend, что позволяет централизованно управлять балансировкой и оперативно перенаправлять нагрузку; при этом высокая производительность достигается за счёт минимальных задержек Redis и использования lua_shared_dict для кеширования, а пул соединений, настроенный через set_keepalive, экономит ресурсы, возвращая неиспользованные TCP‑соединения (до 100 соединений с таймаутом 10 секунд) вместо создания нового для каждого запроса.


Если у вас возникнут вопросы или захотите поделиться своими фишками — пишите в комментариях.

Больше актуальных навыков по IT‑инфраструктуре вы можете получить в рамках практических онлайн‑курсов от экспертов отрасли. В каталоге можно посмотреть список всех программ, а в календаре — записаться на открытые уроки.


ссылка на оригинал статьи https://habr.com/ru/articles/892090/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *