Привет, Хабр!
Сегодня рассмотрим то, как использовать 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/
Добавить комментарий