OpenResty: Nginx как платформа на LuaJIT

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

OpenResty: Nginx как платформа на LuaJIT

Сообщение 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 и аудит конфигурации
Ты прошёл длинный путь: location, upstream, proxy_cache, rate limit, map, TLS. И в какой-то момент упёрся в стену. Тебе нужно: проверить токен в внешнем сервисе прямо в момент запроса, сходить в Redis за флагом фичи, собрать ответ из трёх бэкендов, переписать тело по хитрому правилу, или принять решение о маршруте на основе данных из БД. Директивами это не выражается. map умеет сопоставлять строки, if умеет ровно то, за что его все ненавидят, а njs хорош для лёгких вставок, но это не язык под весь шлюз. Когда конфига мало, на сцену выходит OpenResty.

В этом уроке разберёмся, что такое openresty, чем он отличается от связки "nginx плюс какой-то скриптинг", почему под капотом лежит особый LuaJIT, как поставить и пощупать resty CLI, и напишем первый content_by_lua. И честно поговорим, когда openresty оправдан, а когда ты просто усложняешь себе жизнь там, где хватило бы ванильного nginx или Angie.

OpenResty что это: не скриптинг, а платформа

Сначала разведём понятия, потому что путаница тут массовая. Есть три разные вещи.
  • nginx lua module (ngx_http_lua_module, он же lua-nginx-module) - это C-модуль, который встраивает интерпретатор Lua прямо в рабочие процессы nginx и даёт API ngx.* для работы с запросом. Сам по себе это просто модуль.
  • OpenResty - это готовый дистрибутив: ядро nginx плюс набор уже вкомпилированных модулей (lua-nginx-module, stream-lua, ngx_http_headers_more, lua-upstream, set-misc, encrypted-session и другие) плюс свой форк LuaJIT плюс большая коллекция Lua-библиотек lua-resty-*. Это не "nginx с плагином", это самостоятельная серверная платформа.
  • lua-resty-* библиотеки - готовые модули на Lua поверх этого API: lua-resty-redis, lua-resty-mysql, lua-resty-http, lua-resty-jwt, lua-resty-lock, lua-resty-mlcache и десятки других. Они и превращают nginx в платформу для бэкенда.
Ключевая мысль: nginx lua - это не "встроенный bash для конфига". Это способ выполнять полноценную бизнес-логику внутри event-loop nginx, в тех же воркерах, без отдельного приложения. Запрос приходит, ты в Lua сходил в Redis, проверил подпись JWT, решил куда его слать, при необходимости склеил ответ - и всё это в одном процессе, на неблокирующих сокетах, на скорости, близкой к C. Поэтому openresty - типовая основа для API-gateway: авторизация, маршрутизация, агрегация, защита, A/B - всё в шлюзе, а не в десяти микросервисах-прокладках.

Свежая на 2026 год версия - OpenResty 1.31.1.1 (вышла 5 июня 2026), на ядре nginx 1.31.1. То есть ты получаешь и все современные плюшки nginx (HTTP/2 как отдельная http2 on, HTTP/3, keepalive к бэкенду по умолчанию с proxy_http_version 1.1), и Lua-слой сверху.

Изображение

Nginx luajit: почему свой форк, а не ванильный

Вот это место, которое многие пропускают, а оно принципиально. OpenResty поставляет не ванильный LuaJIT. Он тащит собственный форк - openresty/luajit2.

Зачем? Ванильный LuaJIT от Mike Pall де-факто заморожен: проект жив как код, но активного развития почти нет, новые фичи и серьёзные фиксы туда не приезжают. А OpenResty гоняет LuaJIT под чудовищной нагрузкой в проде по всему миру и не может ждать. Поэтому luajit2 - это LuaJIT с патчами: исправленные краши, новые байткод-инструкции под нужды ngx_lua, профайлеры (точный memory-профайлер, time-профайлер), расширенные C-API и, главное, по умолчанию включённый GC64-режим.

GC64 - это представление указателей в 64 бита вместо упаковки в NaN-tagged 32-битные значения. Старый LuaJIT исторически держал GC-объекты в нижних 2 ГБ адресного пространства - и на больших дата-плоскостях ты ловил аллокаторные сбои "cannot allocate memory" не из-за нехватки RAM, а из-за исчерпания нижних адресов. GC64 эту мину убирает: heap может расти спокойно. Платишь чуть большим размером объектов и микроскопическим оверхедом, получаешь стабильность под реальной памятью. В openresty/luajit2 GC64 включён по умолчанию - это одна из причин, почему "просто поставить ванильный LuaJIT и lua-nginx-module руками" - плохая идея для прода. Бери дистрибутив целиком, он собран согласованно.
Практическое правило: не собирай ngx_lua вручную против системного LuaJIT, если только ты точно не знаешь зачем. Версии luajit2, lua-nginx-module и lua-resty-core должны быть совместимы между собой - OpenResty гарантирует эту совместимость в одном релизе.
И ещё один кит, без которого ничего не летает - lua-resty-core. Это переписанный на LuaJIT FFI слой API: ngx.* реализован не через медленный Lua C API (который JIT компилировать не умеет, код всегда интерпретируется), а через FFI, который JIT-компилируется. lua-resty-core автозагружается начиная с OpenResty 1.15.8.1 и обязателен для ngx.balancer, ngx.ssl, ngx.semaphore и прочего. Без него ты теряешь главное - скорость.

Производительность LuaJIT и ловушка NYI

LuaJIT - это JIT-компилятор: горячие пути твоего Lua-кода транслируются в машинный код и работают почти как C. Именно поэтому openresty не "тормозной скриптовый слой", а пригоден держать десятки тысяч rps на воркер.

Но есть подвох, на котором ломаются новички - NYI, not-yet-implemented. Часть операций LuaJIT компилировать в трассы не умеет. Когда JIT натыкается на такую операцию, он прерывает трассу (trace abort) и сваливается обратно в интерпретатор. Если NYI-операция стоит в горячем цикле, ты теряешь весь профит JIT именно там, где он нужнее всего.

Что в NYI: классические виновники - string.gsub в некоторых режимах, pcall/xpcall кое-где, многое из io.*/os.*, иные конструкции с varargs, разные стандартные функции. Канонический список NYI есть на luajit.org/ext_c_api и в вики проекта. На практике:
  • Не вызывай тяжёлые NYI-функции в горячем цикле, который должен быть скомпилирован.
  • Профилируй. luajit2 умеет точный time-профайлер; в OpenResty есть resty-cli с флагами под профилирование и SystemTap/bpftrace-тулзы, которые показывают trace aborts.
  • Помни: для типичного gateway узкое место чаще сеть и бэкенд, а не Lua. Не оптимизируй вслепую - сначала измерь.
Главное правило производительности в openresty другое и оно важнее NYI: никогда не блокируй воркер. Один воркер обслуживает тысячи соединений в одном event-loop. Если ты в Lua вызвал блокирующую функцию (os.execute, синхронный сокет из стандартной библиотеки, тяжёлый luasql), ты останавливаешь ВСЕ соединения этого воркера. Поэтому в БД, Redis, по HTTP ходят только через cosocket-API (ngx.socket.tcp и библиотеки lua-resty-*, построенные на нём) - они кооперативно отдают управление event-loop, пока ждут сеть. Об этом будет отдельный разговор в уроках про cosocket и пулы.

OpenResty установка и resty CLI

Самый честный способ для прода - официальный docker-образ. Эталонный gateway, на который мы опираемся в курсе, собран ровно так:

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

FROM openresty/openresty:latest

# конфиги монтируются томами в режиме ro
# healthcheck гоняет валидацию конфига
HEALTHCHECK CMD ["openresty", "-t"]
В образе openresty/openresty уже лежит всё: ядро nginx, luajit2 с GC64, lua-nginx-module, lua-resty-core, набор lua-resty-* библиотек, утилита opm (OpenResty Package Manager) для доустановки пакетов и resty CLI. Бинарь называется openresty (это тот же nginx, символическая обёртка), и openresty -V покажет тебе версию ядра и список вкомпилированных модулей.

Для пакетной установки на хост есть официальные репозитории под основные дистрибутивы (openresty.org/en/installation.html) - пакет ставит бинарь в /usr/local/openresty/. Внутри увидишь luajit, nginx, набор Lua-модулей в lualib.

Теперь про resty CLI - это твой быстрый цикл "написал-проверил" без полноценного сервера. resty запускает Lua-скрипт так, будто это командная утилита: под капотом он поднимает headless nginx - без server-блоков, без слушающих сокетов, отключает демонизацию и мастер-процесс, и выполняет твой код в фазе init_worker через ngx.timer. То есть тебе доступен весь ngx.* API (cosocket, таймеры, shared dict), но без необходимости писать конфиг и слушать порт. Идеально для прототипов и тестов библиотек.

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

# проверить версию
resty -V

# выполнить строку Lua
resty -e 'print("hello from luajit ", jit.version)'

# выполнить файл
resty test.lua
Вывод resty -e 'print(jit.version)' покажет что-то вроде LuaJIT 2.1.x с пометкой про форк - это и есть подтверждение, что у тебя GC64-сборка openresty, а не системный Lua. Если jit вернёт nil - значит ты случайно на обычном Lua, JIT нет, и это совсем другая история по скорости.

Первый content_by_lua: разбираем по косточкам

Перейдём от CLI к серверу. Минимальный nginx.conf, который отдаёт ответ из Lua:

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

worker_processes auto;

events { worker_connections 1024; }

http {
    server {
        listen 8080;

        location = /hello {
            default_type text/plain;
            content_by_lua_block {
                ngx.say("hello from OpenResty")
                ngx.say("uri: ", ngx.var.uri)
                ngx.say("remote: ", ngx.var.remote_addr)
            }
        }

        location = /json {
            default_type application/json;
            content_by_lua_block {
                local cjson = require "cjson.safe"
                ngx.status = 200
                ngx.say(cjson.encode({ ok = true, ts = ngx.now() }))
                return ngx.exit(200)
            }
        }
    }
}
Что тут происходит и почему именно так:
  • content_by_lua_block - это директива фазы content. Она генерирует тело ответа целиком. В одном location может быть только один content-обработчик - либо content_by_lua, либо proxy_pass, либо root, не вместе. Это терминальная фаза.
  • ngx.say / ngx.print - пишут в тело ответа. say добавляет перевод строки, print нет. Они шлют данные клиенту неблокирующе.
  • ngx.var.uri, ngx.var.remote_addr - доступ к переменным nginx прямо из Lua. Любая $-переменная конфига видна как ngx.var.имя. Это мост между миром директив и миром кода.
  • cjson.safe - сериализация JSON, идёт в комплекте OpenResty, очень быстрая. Вариант .safe не бросает исключение на битых данных, а возвращает nil плюс ошибку - в проде бери именно его.
  • ngx.exit(200) - явно завершить обработку с кодом. Хороший тон, чтобы не уехать в следующие фазы случайно.
Важный момент про фазы, раз уж мы платформу строим. content_by_lua - лишь одна из точек входа. Полный набор примерно такой (по порядку обработки): set_by_lua (вычислить переменную), rewrite_by_lua (ранняя логика, маршрутизация), access_by_lua (авторизация, тут самое место проверять JWT и ходить в auth-сервис), content_by_lua (генерация ответа), header_filter_by_lua и body_filter_by_lua (правка заголовков и тела ответа на лету), log_by_lua (метрики и логи после отдачи ответа, не задерживает клиента), а также init_by_lua/init_worker_by_lua (инициализация при старте) и особые balancer_by_lua, ssl_certificate_by_lua, ssl_client_hello_by_lua для динамической балансировки и сертификатов по SNI. Понимание фаз - это половина мастерства в openresty: ты раскладываешь логику туда, где она дешевле и корректнее. Авторизацию - в access, а не в content. Метрики - в log, чтобы не тормозить ответ.

Ещё нюанс синтаксиса: есть content_by_lua_block { ... } (код прямо в конфиге, удобно для мелочи) и content_by_lua_file /path/app.lua (код в отдельном файле, для нормального проекта - так и надо). Файлы кэшируются и JIT-ятся; для разработки можно lua_code_cache off, чтобы перечитывался без reload, но в проде - строго on, иначе каждый запрос будет перекомпилировать Lua и убьёт перформанс.

Эталонный gateway: OpenResty без единой строчки Lua

Тут будет неожиданный поворот, и он важен для трезвого взгляда. Наш эталонный проект ngx-trace-gateway собран на образе openresty/openresty:latest - но Lua-блоков в нём нет. Вся его логика - сквозная трассировка через цепочки map, структурные JSON-логи на 50+ полей, rate-limit зоны, проксирование, кэш, TLS, security-заголовки - сделана на чистых директивах ванильного nginx. include events.conf, http.conf, четырнадцать globals/* по порядку, sites-enabled/*. healthcheck гоняет openresty -t.

Почему так и почему это правильно? Два повода.
  • База на будущее. Проект уже стоит на OpenResty. Это значит, что в любой момент, когда понадобится логика сверх директив - сложная авторизация, динамическая маршрутизация по данным из Redis, агрегация ответов, трансформация тела - её можно добавить через access_by_lua/content_by_lua/balancer_by_lua, ничего не переустанавливая и не меняя образ. Платформа уже под рукой. Цена этого решения близка к нулю: образ openresty по поведению полностью совместим с обычным nginx-конфигом.
  • Дисциплина "не тащи Lua без нужды". То, что выражается директивами - делается директивами. map быстрее, проще, безопаснее и понятнее ревьюеру, чем эквивалентный Lua. Трассировку хопов в эталоне делают каскадом map ($http_x_request_id в $trace_request_id, склейка $hop_name:$local_request_id в $current_hop, наращивание цепочки в $request_hops_chain) - и это абсолютно правильно, Lua тут был бы оверинжинирингом.
Это и есть зрелый подход к openresty: ты держишь платформу наготове, но включаешь Lua точечно, ровно там, где конфиг бессилен. Не "переписать весь gateway на Lua", а "добавить Lua-фазу в нужный location".

Типичные грабли
  • Блокирующие вызовы в воркере. os.execute, io.popen, синхронный luasql, обычные сетевые библиотеки на LuaSocket - всё это блокирует event-loop и роняет latency всех соседних запросов. Только cosocket и lua-resty-*.
  • Глобальные переменные. В Lua переменная без local - глобальная, общая на весь Lua VM воркера. Это утечка состояния между запросами и потенциальная гонка. Всегда local. OpenResty по умолчанию ругается на запись в глобалы в рантайме - не игнорируй это предупреждение.
  • Хранение состояния между запросами на уровне модуля. То, что ты положил в переменную модуля (загруженного через require), переживает запрос и шарится. Для разделяемого состояния - lua_shared_dict (общий на воркеры) или резолверы; не складывай туда соединения и запросные данные.
  • lua_code_cache off в проде. Удобно в dev, смертельно в prod - каждый запрос перекомпилирует Lua.
  • Ванильный LuaJIT руками. Несовместимость версий luajit2/lua-nginx-module/lua-resty-core, отсутствие GC64, "cannot allocate memory" на большом heap. Ставь дистрибутив целиком.
  • NYI в горячем цикле. Trace abort убивает JIT там, где он критичен. Профилируй, выноси тяжёлые NYI-функции наружу.
  • Lua там, где хватило бы map. Самая частая стратегическая ошибка. Сначала спроси: это не выражается директивами? Часто выражается.
Мини-лаба: пощупать руками

Делается за 10 минут, нужен только Docker.
  • Шаг 1. Запусти контейнер и проверь сборку:

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

    docker run --rm -it openresty/openresty:latest openresty -V
    
    В выводе найди версию nginx (ожидаем ядро 1.31.x для openresty 1.31.1.1) и список модулей - убедись, что ngx_http_lua_module присутствует.
  • Шаг 2. Проверь, что под капотом именно LuaJIT с GC64, через resty CLI:

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

    docker run --rm -it openresty/openresty:latest \
      resty -e 'print(jit and jit.version or "NO JIT"); print(jit.os, jit.arch)'
    
    Должен увидеть строку LuaJIT 2.1.x, а не "NO JIT".
  • Шаг 3. Создай файл nginx.conf из урока (location /hello и /json), сохрани в текущую папку.
  • Шаг 4. Подними сервер, смонтировав конфиг:

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

    docker run --rm -p 8080:8080 \
      -v "$PWD/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro" \
      openresty/openresty:latest
    
  • Шаг 5. В другом терминале дёрни оба эндпоинта:

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

    curl -s localhost:8080/hello
    curl -s localhost:8080/json
    
    Увидишь свой текст с uri и remote_addr, и валидный JSON с меткой времени из ngx.now().
  • Шаг 6 (бонус). Добавь в location /hello строку с access_by_lua_block перед content, который пишет в лог через ngx.log(ngx.WARN, "hit hello") - и посмотри, как фазы отрабатывают по очереди в docker-логах.
Контрольные вопросы
  • Чем openresty отличается от "просто nginx, в который добавили lua-nginx-module"? Назови минимум два компонента, которые даёт дистрибутив сверх голого модуля.
  • Почему OpenResty тащит свой форк LuaJIT (luajit2), а не ванильный? Что решает GC64-режим и какую проблему он убирает на больших дата-плоскостях?
  • Что такое NYI в LuaJIT и почему NYI-операция в горячем цикле опаснее, чем та же операция в холодном коде, выполняемом раз за запрос?
  • Эталонный gateway собран на OpenResty, но без Lua. Объясни, зачем так сделано и в какой ситуации ты добавишь первый *_by_lua-блок.
Итог

OpenResty - это не "nginx со скриптами", а полноценная платформа: ядро nginx плюс собственный LuaJIT-форк luajit2 с GC64 и патчами, плюс FFI-слой lua-resty-core, плюс библиотеки lua-resty-*. Она позволяет выполнять настоящую бизнес-логику внутри воркеров nginx на скорости, близкой к C - авторизацию, маршрутизацию, агрегацию, трансформацию, обращения в Redis и БД в момент запроса. resty CLI даёт быстрый цикл прототипирования через headless nginx, а content_by_lua и остальные фазы - точки входа в твой код. Помни два правила: не блокируй воркер и не тащи Lua туда, где справится map. Эталонный gateway держит платформу наготове, но включает Lua точечно - бери этот подход на вооружение. Дальше в курсе мы и начнём писать эти фазы по-настоящему.
👍2 ❤️2 🔥1 😄 🤔
✔ Лучший ответ сформирован автоматически — scalageek
Долго не доходило в чём фишка, спасибо за разведение понятий. Думал openresty это просто nginx плюс lua, а оказывается там свой luajit2 с GC64 и целый лес lua-resty. И момент про эталон без единого Lua-блока прям отрезвляет - реально часто хочется сразу content_by_lua прикрутить туда где map бы хватило.
Перейти к ответу →
Аватара пользователя
scalageek
Сообщения: 1
Зарегистрирован: 28 май 2026, 11:39

Re: OpenResty: Nginx как платформа на LuaJIT

Сообщение scalageek »

✔ Лучший ответ — сформирован автоматически
Долго не доходило в чём фишка, спасибо за разведение понятий. Думал openresty это просто nginx плюс lua, а оказывается там свой luajit2 с GC64 и целый лес lua-resty. И момент про эталон без единого Lua-блока прям отрезвляет - реально часто хочется сразу content_by_lua прикрутить туда где map бы хватило.
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
k8smaker
Сообщения: 1
Зарегистрирован: 15 май 2026, 13:36

Re: OpenResty: Nginx как платформа на LuaJIT

Сообщение k8smaker »

Вопрос по resty CLI: если он поднимает headless nginx без server-блоков, то shared_dict в нём работает или нет? Хочу прогонять юнит-тесты для lua-resty-mlcache локально без полного конфига, реально так или придётся всё равно сервер поднимать?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Траблшутинг: 502, 504, 403, 404 и debug-лог
Следующая глава →
Фазы обработки запроса и Lua-хуки

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: OpenResty: nginx на Lua

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

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

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