Nginx’i saf haliyle kullanırken bir noktadan sonra “keşke burada biraz mantık ekleyebilseydim” diye düşünmüşsünüzdür. Rate limiting için, request manipülasyonu için ya da dinamik routing için ayrı bir uygulama katmanı çıkarmak zorunda kalmak can sıkıcı. İşte tam bu noktada OpenResty ve Lua devreye giriyor. Nginx’in performansını koruyarak içine gerçek bir scripting dili gömmüş oluyorsunuz.
OpenResty Nedir ve Neden Kullanılır?
OpenResty, Nginx’in üzerine inşa edilmiş, içine LuaJIT entegre edilmiş bir web platformudur. Çin’li geliştirici Yichun Zhang (agentzh) tarafından başlatılan bu proje, Nginx’in event-driven mimarisini bozmadan Lua scriptlerini doğrudan request lifecycle’ına dahil etmenizi sağlar.
Standart Nginx’ten farkı şudur: Nginx modül yazacaksanız C bilmeniz gerekir, derlemeniz gerekir ve production’da hata ayıklamanız bir kabus olur. OpenResty ile Lua yazıyorsunuz, interpreter zaten içeride, hot-reload benzeri workflows mümkün oluyor.
LuaJIT burada kritik bir detay. Standart Lua interpreter’ından 10-50 kat daha hızlı çalışabilen bu just-in-time compiler sayesinde Lua kodu neredeyse C hızında koşuyor. Bu yüzden OpenResty, yüksek trafikli sistemlerde bile tercih edilebilir bir seçenek.
Gerçek dünyada OpenResty şu senaryolarda çok işe yarıyor:
- API Gateway olarak kullanım (rate limiting, auth, routing)
- Request/response manipülasyonu
- Redis ile entegre session yönetimi
- Dinamik upstream seçimi
- WAF (Web Application Firewall) ekleme
- A/B testing altyapısı
OpenResty Kurulumu
Ubuntu/Debian üzerinde kurulum oldukça basit:
# OpenResty resmi reposunu ekle
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"
| sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo apt install openresty
# Servis durumunu kontrol et
sudo systemctl start openresty
sudo systemctl enable openresty
sudo systemctl status openresty
# Kurulum yolunu doğrula
/usr/local/openresty/nginx/sbin/nginx -v
CentOS/RHEL için:
# Repo ekle
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo yum install openresty openresty-resty
# PATH'e ekle
echo 'export PATH=/usr/local/openresty/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
OpenResty kurulduktan sonra yapılandırma dosyaları /usr/local/openresty/nginx/conf/ altında bulunur. Standart Nginx konfig sözdiziminin birebir aynısını kullanırsınız, sadece Lua direktifleri eklenir.
Lua Direktifleri ve Request Lifecycle
OpenResty, Nginx’in request processing aşamalarına karşılık gelen Lua direktifleri sunar. Bunları anlamak çok önemli:
- init_by_lua_block: Worker process başlamadan önce, master process’te çalışır. Global değişken tanımları için idealdir
- init_worker_by_lua_block: Her worker başladığında çalışır. Background timer’lar için kullanılır
- set_by_lua_block: Nginx değişkeni set etmek için
- rewrite_by_lua_block: URL rewrite aşamasında çalışır
- access_by_lua_block: Erişim kontrolü için, upstream’e gitmeden önce
- content_by_lua_block: Response içeriği üretmek için
- header_filter_by_lua_block: Response header’larını modifiye etmek için
- body_filter_by_lua_block: Response body’sini modifiye etmek için
- log_by_lua_block: İstek tamamlandıktan sonra loglama için
İlk Lua Script: Hello World ve Temel Yapı
Basit bir başlangıç yapalım:
# /usr/local/openresty/nginx/conf/nginx.conf
worker_processes auto;
error_log /var/log/openresty/error.log;
events {
worker_connections 1024;
}
http {
# Lua modüllerinin aranacağı yollar
lua_package_path '/usr/local/openresty/lualib/?.lua;;';
lua_package_cpath '/usr/local/openresty/lualib/?.so;;';
# Global Lua kodu - worker başlamadan önce çalışır
init_by_lua_block {
-- Lua yorumları iki tire ile başlar
cjson = require "cjson"
redis = require "resty.redis"
ngx.log(ngx.INFO, "OpenResty başlatıldı, modüller yüklendi")
}
server {
listen 80;
server_name localhost;
location /hello {
content_by_lua_block {
-- Request bilgilerini al
local method = ngx.req.get_method()
local uri = ngx.var.uri
local remote_addr = ngx.var.remote_addr
-- JSON response oluştur
local response = {
message = "Merhaba OpenResty!",
method = method,
uri = uri,
client_ip = remote_addr,
timestamp = ngx.time()
}
ngx.header.content_type = "application/json"
ngx.say(cjson.encode(response))
}
}
}
}
Gerçek Senaryo 1: Rate Limiting ile API Koruması
Redis tabanlı sliding window rate limiting. Bu senaryoda her IP için dakikada maksimum istek sayısı koyuyoruz:
location /api/ {
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- ms cinsinden
-- Redis'e bağlan
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis bağlantı hatası: ", err)
-- Redis yoksa isteği geçir, sıkı davranmak istemiyorsak
return
end
local client_ip = ngx.var.remote_addr
local key = "rate_limit:" .. client_ip
local limit = 60 -- dakikada maksimum istek
local window = 60 -- saniye
-- Atomic increment
local count, err = red:incr(key)
if err then
ngx.log(ngx.ERR, "Redis INCR hatası: ", err)
return
end
-- İlk istek ise expire set et
if count == 1 then
red:expire(key, window)
end
-- Kalan hakkı header'a yaz
ngx.header["X-RateLimit-Limit"] = limit
ngx.header["X-RateLimit-Remaining"] = math.max(0, limit - count)
-- Limiti aştı mı?
if count > limit then
local ttl = red:ttl(key)
ngx.header["Retry-After"] = ttl
ngx.status = 429
ngx.header.content_type = "application/json"
ngx.say(cjson.encode({
error = "Too Many Requests",
retry_after = ttl,
message = "Rate limit aşıldı, lütfen bekleyin"
}))
ngx.exit(429)
end
-- Connection pool'a geri koy
red:set_keepalive(10000, 100)
}
proxy_pass http://backend_upstream;
}
Bu yaklaşımın güzel tarafı, ayrı bir rate limiting servisi çalıştırmak zorunda kalmıyorsunuz. Nginx’in kendisi, Redis ile konuşarak bu işi hallediyor.
Gerçek Senaryo 2: JWT Token Doğrulama
API gateway olarak kullanıldığında en sık ihtiyaç duyulan şeylerden biri JWT doğrulama. OpenResty ile bunu upstream uygulamaya bırakmadan Nginx katmanında yapabilirsiniz:
-- /usr/local/openresty/lualib/jwt_auth.lua
local cjson = require "cjson"
local hmac = require "resty.hmac"
local _M = {}
-- Base64 URL decode
local function base64url_decode(input)
local remainder = #input % 4
if remainder == 2 then
input = input .. "=="
elseif remainder == 3 then
input = input .. "="
end
input = input:gsub("-", "+"):gsub("_", "/")
return ngx.decode_base64(input)
end
function _M.verify(token, secret)
if not token then
return nil, "Token bulunamadı"
end
-- Bearer prefix'ini kaldır
token = token:match("Bearer%s+(.+)") or token
-- Token parçalarına ayır
local parts = {}
for part in token:gmatch("[^.]+") do
table.insert(parts, part)
end
if #parts ~= 3 then
return nil, "Geçersiz token formatı"
end
local header_str = base64url_decode(parts[1])
local payload_str = base64url_decode(parts[2])
if not header_str or not payload_str then
return nil, "Base64 decode hatası"
end
-- İmzayı doğrula
local signing_input = parts[1] .. "." .. parts[2]
local h = hmac:new(secret, hmac.ALGOS.SHA256)
local expected_sig = ngx.encode_base64(h:final(signing_input))
:gsub("+", "-"):gsub("/", "_"):gsub("=", "")
if expected_sig ~= parts[3] then
return nil, "İmza geçersiz"
end
-- Payload'u parse et
local ok, payload = pcall(cjson.decode, payload_str)
if not ok then
return nil, "Payload parse hatası"
end
-- Expiry kontrolü
if payload.exp and payload.exp < ngx.time() then
return nil, "Token süresi dolmuş"
end
return payload, nil
end
return _M
Bu modülü nginx.conf içinde şöyle kullanırsınız:
http {
lua_package_path '/usr/local/openresty/lualib/?.lua;;';
# JWT secret'ı environment'tan al
env JWT_SECRET;
init_by_lua_block {
cjson = require "cjson"
jwt_auth = require "jwt_auth"
JWT_SECRET = os.getenv("JWT_SECRET") or "default-secret-degistir"
}
server {
listen 80;
# Korumalı endpoint
location /api/protected/ {
access_by_lua_block {
local auth_header = ngx.req.get_headers()["Authorization"]
local payload, err = jwt_auth.verify(auth_header, JWT_SECRET)
if err then
ngx.status = 401
ngx.header.content_type = "application/json"
ngx.say(cjson.encode({
error = "Unauthorized",
message = err
}))
ngx.exit(401)
end
-- Kullanıcı bilgisini header olarak backend'e ilet
ngx.req.set_header("X-User-ID", payload.sub)
ngx.req.set_header("X-User-Role", payload.role or "user")
}
proxy_pass http://backend;
}
}
}
Gerçek Senaryo 3: Dinamik Upstream Seçimi
Canary deployment veya A/B testing için upstream’i dinamik olarak seçmek isteyebilirsiniz:
upstream backend_stable {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32;
}
upstream backend_canary {
server 10.0.0.3:8080;
keepalive 16;
}
server {
listen 80;
location / {
set_by_lua_block $upstream_target {
-- Canary cookie varsa canary'ye gönder
local cookie_canary = ngx.var.cookie_canary
if cookie_canary == "true" then
return "backend_canary"
end
-- User-Agent'a göre karar ver (test araçları için)
local user_agent = ngx.req.get_headers()["User-Agent"] or ""
if user_agent:find("CanaryBot") then
return "backend_canary"
end
-- %10 kullanıcıyı canary'ye at
local user_id = ngx.var.cookie_user_id
if user_id then
-- Kullanıcı ID'sinin son rakamına bak
local last_digit = tonumber(user_id:sub(-1)) or 0
if last_digit == 0 then -- %10 ihtimal
return "backend_canary"
end
end
return "backend_stable"
}
proxy_pass http://$upstream_target;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Shared Memory ve Cache Yönetimi
OpenResty’nin güçlü özelliklerinden biri lua_shared_dict. Tüm worker process’leri arasında paylaşılan, in-memory dictionary. Redis olmadan basit cache senaryolarını burada çözebilirsiniz:
http {
# Shared memory zone tanımla - 10MB
lua_shared_dict app_cache 10m;
lua_shared_dict rate_counters 5m;
server {
location /cached-data {
content_by_lua_block {
local cache = ngx.shared.app_cache
local cache_key = "data:" .. ngx.var.uri
-- Cache'den oku
local cached = cache:get(cache_key)
if cached then
ngx.header["X-Cache"] = "HIT"
ngx.header.content_type = "application/json"
ngx.say(cached)
return
end
-- Cache miss, veriyi hesapla/çek
ngx.header["X-Cache"] = "MISS"
-- Burada normalde DB sorgusu veya API çağrısı olurdu
local data = {
result = "hesaplanmış veri",
generated_at = ngx.time(),
worker_pid = ngx.worker.pid()
}
local json_data = cjson.encode(data)
-- 30 saniye cache'le
local success, err, forcible = cache:set(cache_key, json_data, 30)
if not success then
ngx.log(ngx.WARN, "Cache set hatası: ", err)
end
-- forcible true ise eski veriler silindi (memory dolu)
if forcible then
ngx.log(ngx.WARN, "Cache memory doldu, eski veriler silindi")
end
ngx.header.content_type = "application/json"
ngx.say(json_data)
}
}
}
}
Loglama ve Debugging
Production’da debug etmek için özel log formatları ve Lua tabanlı loglama çok işe yarıyor:
http {
# Özel log formatı - JSON
log_format json_log escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"bytes_sent":$bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"user_agent":"$http_user_agent",'
'"request_id":"$request_id"'
'}';
server {
# Her isteğe unique ID ekle
set_by_lua_block $request_id {
return string.format("%016x", math.random(0, 2^53))
}
location / {
# Response header'a request ID ekle
header_filter_by_lua_block {
ngx.header["X-Request-ID"] = ngx.var.request_id
}
# Request tamamlandıktan sonra detaylı log
log_by_lua_block {
local latency = tonumber(ngx.var.request_time) or 0
-- Yavaş requestleri ayrıca logla
if latency > 1.0 then
ngx.log(ngx.WARN, string.format(
"YAVAŞ REQUEST - ID: %s, URI: %s, Süre: %.3fs, Status: %s",
ngx.var.request_id,
ngx.var.uri,
latency,
ngx.var.status
))
end
-- 5xx hataları için alert log
local status = tonumber(ngx.var.status) or 0
if status >= 500 then
ngx.log(ngx.ERR, string.format(
"SERVER ERROR - ID: %s, URI: %s, Status: %d",
ngx.var.request_id,
ngx.var.uri,
status
))
end
}
proxy_pass http://backend;
}
}
}
OPM ile Lua Modül Yönetimi
OpenResty Package Manager (OPM), Lua modüllerini yönetmek için kullanılır. npm veya pip benzeri bir araç:
# OPM kurulumu (OpenResty ile genellikle gelir)
which opm
# Popüler modülleri kur
opm get bungle/lua-resty-http # HTTP client
opm get ledgetech/lua-resty-redis-connector
opm get SkyLothar/lua-resty-jwt # JWT kütüphanesi
opm get pintsized/lua-resty-crypto # Kriptografi
# Kurulu modülleri listele
opm list
# Alternatif: LuaRocks paket yöneticisi
luarocks install lua-cjson
luarocks install luasocket
Popüler OpenResty kütüphaneleri şunlar:
- resty.http: Lua’dan HTTP istekleri atmak için
- resty.redis: Redis bağlantısı için
- resty.mysql: MySQL bağlantısı için
- resty.jwt: JWT işlemleri için
- resty.template: HTML template rendering için
- resty.lock: Distributed locking için
Performans İpuçları ve Yaygın Hatalar
Connection pool kullanmayı unutmayın. Redis veya MySQL’e her istekte yeni bağlantı açmak performansı mahveder:
# Yanlış
local red = redis:new()
red:connect("127.0.0.1", 6379)
-- ... kullan
red:close() # Bağlantıyı kapat - YANLIŞ
# Doğru
local red = redis:new()
red:connect("127.0.0.1", 6379)
-- ... kullan
red:set_keepalive(10000, 100) # Pool'a geri koy - DOĞRU
Blocking I/O yapmayın. Standart Lua io kütüphanesi, dosya okuma gibi işlemler Nginx’in event loop’unu bloklar. OpenResty’nin ngx.io fonksiyonlarını kullanın veya bunları init_by_lua_block içinde yapın.
pcall ile hata yönetimi yapın. Lua’da unhandled exception worker’ı çökertemez ama isteği mahveder:
# Güvenli JSON parse
local ok, data = pcall(cjson.decode, raw_json)
if not ok then
ngx.log(ngx.ERR, "JSON parse hatası: ", data)
ngx.exit(400)
return
end
ngx.shared.dict boyutlandırmasına dikkat edin. Çok küçük tutarsanız sürekli eviction olur ve cache verimliliği düşer. ngx.shared.dict:info() ile doluluk oranını takip edin.
Test Ortamı ve CI/CD Entegrasyonu
OpenResty tabanlı scriptleri test etmek için Test::Nginx framework’ü kullanılır:
# Test::Nginx kurulumu
cpan Test::Nginx
# Basit bir test dosyası
# t/rate_limit.t
use Test::Nginx::Socket 'no_plan';
run_tests();
__DATA__
=== TEST 1: Normal istek rate limit aşmıyor
--- config
location /api {
access_by_lua_file /path/to/rate_limit.lua;
return 200 "ok";
}
--- request
GET /api
--- response_headers
X-RateLimit-Limit: 60
--- error_code: 200
# Testleri çalıştır
prove -r t/
# OpenResty ile birlikte gelen resty CLI ile script test et
resty -e "
local cjson = require 'cjson'
local data = {name='test', value=42}
print(cjson.encode(data))
"
Sonuç
OpenResty ve Lua kombinasyonu, Nginx’i statik bir web sunucusu ya da basit bir reverse proxy olmaktan çıkarıp gerçek anlamda programlanabilir bir platform haline getiriyor. Rate limiting, authentication, caching, A/B testing gibi cross-cutting concern’leri uygulama kodundan çekip infrastructure seviyesine taşıyabiliyorsunuz.
Özellikle mikroservis mimarilerinde API gateway ihtiyacı için ayrı bir servis ayağa kaldırmak yerine OpenResty tabanlı bir çözüm ciddi kaynak tasarrufu sağlayabilir. Kong ve APISIX gibi popüler API gateway ürünleri de tam olarak bu OpenResty altyapısı üzerine kurulu.
Başlangıç için önerim şu yolu izleyin: Önce mevcut Nginx kurulumlarınızdan birini OpenResty’ye taşıyın, basit bir rate limiting veya JWT doğrulama scripti yazın, shared dict ile bir şeyler cache’leyin. LuaJIT’in hızını, kodun okunabilirliğini ve deployment kolaylığını görünce neden bu kadar kullanıcısının olduğunu anlayacaksınız.
Lua öğrenmek için fazla zaman harcamanıza gerek yok, söz dizimi çok temiz. OpenResty wiki’si ve GitHub’daki lua-resty-* repoları başlamak için mükemmel kaynaklar.