OpenResty

NginxとLuaJITを統合した高性能Webプラットフォーム。スクリプト言語でありながらネイティブC並みの性能を実現。API Gateway、マイクロサービス、動的コンテンツ処理に特化。

WebサーバーNginxLua高性能スクリプタブルAPI Gatewayマイクロサービス

Webサーバー

OpenResty

概要

OpenRestyは、NginxとLuaJITを統合した高性能Webアプリケーションプラットフォームです。NginxのコアにLuaスクリプト実行環境を組み込むことで、スクリプト言語でありながらネイティブC言語並みの性能を実現します。動的コンテンツ処理、API Gateway、マイクロサービス環境での利用に特化し、柔軟性と高性能を両立させた革新的なプラットフォームです。

詳細

OpenRestyは中国のCloudflareエンジニアYichun Zhang(agentzh)によって2009年に開始され、現在では世界市場で10.8%のシェアを獲得しています。特にAPI Gatewayやマイクロサービスアーキテクチャでの採用が増加しており、豊富なLuaライブラリエコシステムが構築されています。

主要な技術的特徴

  • LuaJIT統合: Just-In-Timeコンパイラによる高速スクリプト実行
  • 非同期I/O: Nginxベースのイベントドリブンアーキテクチャ
  • 豊富なLuaライブラリ: 100以上の専用ライブラリが利用可能
  • リアルタイム処理: リクエスト処理の各フェーズでLua実行
  • データベース統合: MySQL、PostgreSQL、Redis等への非同期アクセス

用途

  • API Gateway・プロキシサーバー
  • 動的コンテンツ生成
  • リアルタイムWeb アプリケーション
  • マイクロサービス間の通信制御
  • 高性能Webサービス開発

メリット・デメリット

メリット

  • ネイティブ級性能: LuaJITによるC言語並みの実行速度
  • 開発効率: スクリプト言語による迅速な開発・デプロイ
  • 豊富なライブラリ: Redis、MySQL、HTTP クライアント等が利用可能
  • 柔軟な処理: リクエストの各段階でカスタムロジック実行
  • Nginx互換: 既存のNginx設定資産を活用可能
  • スケーラビリティ: 非同期処理による高い同時接続処理能力

デメリット

  • 学習コスト: LuaとNginxの両方の知識が必要
  • デバッグの難しさ: 非同期処理とスクリプトの組み合わせ
  • コミュニティサイズ: NginxやApacheと比較して小規模
  • 複雑な設定: 高度な機能利用時の設定の複雑さ
  • メモリ使用量: Luaランタイムによる追加のメモリ消費

参考ページ

書き方の例

基本的なOpenResty設定

# nginx.conf
worker_processes auto;
error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    # Lua package path
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    
    server {
        listen 80;
        server_name example.com;
        
        location /hello {
            content_by_lua_block {
                ngx.say("Hello, OpenResty!")
            }
        }
    }
}

Lua ファイルによるAPIエンドポイント

-- /usr/local/openresty/nginx/lua/api.lua
local json = require "cjson"
local redis = require "resty.redis"

local _M = {}

function _M.get_user(user_id)
    -- Redis接続
    local red = redis:new()
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect to redis: ", err)
        return ngx.exit(500)
    end
    
    -- ユーザー情報取得
    local user_data, err = red:get("user:" .. user_id)
    if not user_data or user_data == ngx.null then
        ngx.status = 404
        ngx.say(json.encode({error = "User not found"}))
        return
    end
    
    -- レスポンス返却
    ngx.header.content_type = "application/json"
    ngx.say(user_data)
    
    red:close()
end

return _M

API Gateway設定例

# API Gateway設定
upstream backend_auth {
    server auth-service:8080;
}

upstream backend_users {
    server user-service:8080;
}

upstream backend_orders {
    server order-service:8080;
}

server {
    listen 80;
    server_name api.example.com;
    
    # 認証チェック
    location /auth {
        internal;
        proxy_pass http://backend_auth/validate;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
    
    # API v1ルーティング
    location /api/v1/users {
        access_by_lua_block {
            local auth_result = ngx.location.capture("/auth")
            if auth_result.status ~= 200 then
                ngx.status = 401
                ngx.say('{"error":"Unauthorized"}')
                ngx.exit(401)
            end
        }
        
        proxy_pass http://backend_users;
        proxy_set_header X-User-ID $upstream_http_x_user_id;
    }
    
    location /api/v1/orders {
        access_by_lua_file /usr/local/openresty/nginx/lua/auth_check.lua;
        proxy_pass http://backend_orders;
    }
}

レート制限とセキュリティ

-- /usr/local/openresty/nginx/lua/rate_limit.lua
local limit_req = require "resty.limit.req"

-- レート制限設定(1分間に100リクエスト)
local lim, err = limit_req.new("rate_limit_store", 100, 60)
if not lim then
    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
    return ngx.exit(500)
end

local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)

if not delay then
    if err == "rejected" then
        ngx.status = 429
        ngx.header.content_type = "application/json"
        ngx.say('{"error":"Too Many Requests"}')
        ngx.exit(429)
    end
    ngx.log(ngx.ERR, "failed to limit req: ", err)
    return ngx.exit(500)
end

if delay >= 0.001 then
    ngx.sleep(delay)
end

データベース接続例

-- /usr/local/openresty/nginx/lua/db_handler.lua
local mysql = require "resty.mysql"
local json = require "cjson"

local _M = {}

function _M.get_products()
    local db, err = mysql:new()
    if not db then
        ngx.log(ngx.ERR, "failed to instantiate mysql: ", err)
        return ngx.exit(500)
    end
    
    db:set_timeout(1000)
    
    local ok, err, errno, sqlstate = db:connect{
        host = "127.0.0.1",
        port = 3306,
        database = "shop",
        user = "app_user",
        password = "password",
        charset = "utf8",
        max_packet_size = 1024 * 1024,
    }
    
    if not ok then
        ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errno, " ", sqlstate)
        return ngx.exit(500)
    end
    
    local query = "SELECT id, name, price FROM products WHERE active = 1 LIMIT 10"
    local res, err, errno, sqlstate = db:query(query)
    
    if not res then
        ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".")
        return ngx.exit(500)
    end
    
    ngx.header.content_type = "application/json"
    ngx.say(json.encode(res))
    
    db:close()
end

return _M

WebSocket プロキシ

# WebSocket対応プロキシ設定
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 80;
    server_name ws.example.com;
    
    location /ws {
        access_by_lua_block {
            -- WebSocket接続の認証
            local auth_token = ngx.var.arg_token
            if not auth_token then
                ngx.status = 401
                ngx.say("Missing auth token")
                ngx.exit(401)
            end
            
            -- トークン検証ロジック
            -- 実際の実装では外部認証サービスを呼び出し
        }
        
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 86400;
    }
}

ログ処理とモニタリング

-- /usr/local/openresty/nginx/lua/logging.lua
local json = require "cjson"

-- カスタムログ出力
local function log_request()
    local log_data = {
        timestamp = ngx.time(),
        method = ngx.var.request_method,
        uri = ngx.var.request_uri,
        status = ngx.var.status,
        response_time = ngx.var.request_time,
        user_agent = ngx.var.http_user_agent,
        remote_addr = ngx.var.remote_addr,
        request_id = ngx.var.request_id
    }
    
    ngx.log(ngx.INFO, "REQUEST_LOG: ", json.encode(log_data))
end

-- リクエスト終了時に実行
ngx.ctx.log_request = log_request