Nginx + Lua, гибкая балансировка нагрузки с сохранением сессии
При балансировке нагрузки важный вопрос — сохранение сессии клиента. Особенно, если за балансировщиком стоит какой-то интерактивный backend. И тем более, если захотелось сделать A/B тестирование и гибко регулировать порции клиентов к различному содержанию. «Nginx plus» предлагает такие возможности, но что делать, если хочется дёшево и быстро?
На помощь приходит возможность расширить функционал Nginx с помощью Lua.
Алгоритм прост. При первом запросе клиента, выставляем ему cookie, а при последующих в зависимости от значения, отправляем к конкретному бэкенду. Сами же куки распределяем подходящим алгоритмом с анализом нужных параметров.
В качестве мощного nginx-комбайна можно использовать сборку OpenResty, но для наших нужд это избыточно, потому соберём только нужный функционал на базе nginx 1.10.3
из репозитория.
Подопытным у нас будет:
Debian jessie 4.9.0-0.bpo.1-amd64
Nginx 1.10.3 (nginx.org)
libluajit-5.1-2
Необходимые компоненты сборки:
ngx_devel_kit-0.3.0
lua-nginx-module-0.10.8
lua-resty-core-0.1.11
lua-resty-lrucache-0.06
Устанавливаем пакеты для сборки deb-пакета:
# cd /usr/src/
# aptitude install quilt debhelper libluajit-5.1-dev libluajit-5.1-2
# apt-get -t jessie source nginx
Последняя команда скачивает исходные коды nginx-а, из настроенного репозитория. Мы используем nginx: пакеты для Linux.
Скачиваем и распаковываем текущие версии исходных кодов модулей: ngx_devel_kit и lua-nginx-module
# wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
# wget https://github.com/openresty/lua-nginx-module/archive/v0.10.8.tar.gz
# tar -xf v0.3.0.tar.gz
# tar -xf v0.10.8.tar.gz
Первый модуль необходим для сборки желанного второго.
Правим файл правил сборки deb-пакета по адресу nginx-1.10.3/debian/rules
, добавив в список параметров секции config.status.nginx: config.env.nginx
:
--add-module=/usr/src/ngx_devel_kit-0.3.0 --add-module=/usr/src/lua-nginx-module-0.10.8
Собираем и устанавливаем получившийся пакет:
# cd nginx-1.10.3 && dpkg-buildpackage -us -uc -b && cd ../
# dpkg -i nginx_1.10.3-1~jessie_amd64.deb
# aptitude hold nginx
Последняя команда зафиксирует установленный пакет, чтобы избежать его случайного обновления.
Кроме этого, нам потребуется ещё две lua-библиотеки из проекта OpenResty, предоставляющих Nginx API for Lua: lua-resty-core и lua-resty-lrucache. Они из себя представляют набор *.lua
файлов, устанавливаемых (по умолчанию) по пути /usr/local/lib/lua/
.
# wget https://github.com/openresty/lua-resty-core/archive/v0.1.11.tar.gz
# wget https://github.com/openresty/lua-resty-lrucache/archive/v0.06.tar.gz
# tar -xf v0.1.11.tar.gz
# tar -xf v0.06.tar.gz
# cd lua-resty-core-0.1.11 && make install && cd ../
# cd lua-resty-lrucache-0.06 && make install && cd ../
Подготовительная часть завершена, приступаем к настройке nginx-а. Приведу упрощённую конфигурацию с комментариями происходящего.
В нашем случае требовалось реализовать только варианты контента, потому и балансировщик и бэкенд будут на одном сервере и upstream будет указывать на локальные адреса с портами 800x.
Но гибкость реализации позволяют построить любые желаемые конфигурации. Итак по порядку.
В блоке http {}
инициализируем lua.
# путь до локально установленных *.lua библиотек с добавлением системных путей
lua_package_path "/usr/local/lib/lua/?.lua;;";
init_by_lua_block {
-- подключение основного модуля
-- в принципе, этот блок можно опустить
require "resty.core"
collectgarbage("collect") -- just to collect any garbage
}
в блоках *_lua_block
уже идёт lua-код со своим синтаксисом и функциями.
Основной сервер, который принимает на себя внешние запросы.
Блок upstream, который используя lua заменяет встроенную логику nginx.
Ну и простой демонстрационный бэкенд, на который в итоге придут клиенты.
При запуске nginx-a с этой конфигурацией в логи свалится предупреждение:
use of lua-resty-core with LuaJIT 2.0 is not recommended; use LuaJIT 2.1+ instead while connecting to upstream
которое можно убрать собрав и установив требуемую версию. Но и на 2.0 (libluajit-5.1-2) работает.
Теперь, используя браузер с инструментами разработчика, можем проверять работу сервера и выставляемые куки.
Таким образом мы получили необходимую для тестирования и статистики гибкость. И необходимое для правильной работы бэкенда сохранение сессии клиента. Ну и просто интересный опыт.
PS Подобные задачи можно решить и другими методами, например используя haproxy, который позволяет балансировать с учётом сессий. Или для разделения клиентов использовать ngx_http_split_clients_module и с помощью map сопоставлять одни значения в зависимости от других.
Но приведённый вариант распределения клиентов и выбора бэкенда позволяет гибче настраивать систему. И при необходимости, добавлять разнообразную логику в работу. При этом не перестраивая текущую систему.
Спасибо за внимание.