OpenResty
NginxとLuaJITを統合した高性能Webプラットフォーム。スクリプト言語でありながらネイティブC並みの性能を実現。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