【無料ツール】画像をPDFに変換するPhotoPDF Appを公開しました!

【Nginx応用講座 第4回】Nginxをプログラマブルに。Lua言語(OpenResty)による動的処理と機能拡張

記事内に広告が含まれています。
[PR]

設定ファイル(conf)の「限界」を感じたことはありませんか?

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回は、マイクロサービス時代のルーティング技術(gRPC, WebSocket, auth_request)について学びました。

しかし、現場の要求はさらにエスカレートします。
「特定のユーザーIDの時だけ、カナリアリリース用のサーバーに飛ばしたい(データベースを見て判断したい)」
「リクエストの中身を解析して、JSONを書き換えてからバックエンドに渡したい」
「WAFのような複雑なフィルタリングを、自前のロジックで実装したい」

これらを標準の nginx.confif 文だけで書こうとすると、設定ファイルはスパゲッティ状態になり、メンテナンス不能に陥ります。
そこで登場するのが、Nginxに「脳」を与える技術、OpenResty(Lua)です。

コウ君

先生、実はまさにそれで悩んでます。
「A/Bテストをしたいから、Redisに入ってるユーザー情報を元に振り分け先を変えて」って言われたんですけど……。
NginxからRedisなんて叩けるんですか? アプリ側でやるべき処理じゃないんですか?

リナックス先生

ふふふ、アプリ側でやると「一度アプリまでリクエストを通す」オーバーヘッドがかかるでしょ?
Nginxの段階でRedisを高速に参照して、瞬時に振り分ける。
これができれば、バックエンドに無駄な負荷をかけずに高度な制御ができるの。
それを実現するのが「Lua言語」と、それを組み込んだNginxディストリビューション「OpenResty」よ。
インフラエンジニアが「コードを書く」時代の到来ね!

本記事では、OpenRestyの導入から、Luaスクリプトを使った動的ルーティング、Redisとの連携、そしてJWTトークンの検証ロジックの実装まで、Nginxを「プログラマブル」に使い倒す方法を解説します。

🚀 Nginx応用講座(全8回)カリキュラム

現在地:【第4回】Nginxをプログラマブルに。Lua言語(OpenResty)による動的処理と機能拡張

※基本講座の復習はこちら:Nginx基本講座(全8回)アーカイブ


第1章:OpenRestyとは何か?なぜLuaなのか?

OpenResty = Nginx + LuaJIT + 便利なモジュール群

標準のNginxにLuaモジュールを後から追加することも可能ですが、バージョンの整合性を取るのが大変です。
そこで、Nginx本体に最初から「LuaJIT(Luaの高速実行環境)」と、よく使われるライブラリ(Redis接続、JSON処理など)をバンドルしてパッケージ化したものが「OpenResty(オープンレスティ)」です。
世界中のCDNプロバイダや大規模Webサービスで採用されている、信頼性の高いプラットフォームです。

なぜLua言語なのか?

PythonやRubyではなく、なぜLuaなのでしょうか?

  1. 圧倒的に速い: LuaJITはスクリプト言語の中で最速クラスです。C言語で書かれたNginxの内部APIを、ほぼオーバーヘッドなしで呼び出せます。
  2. 省メモリ: 非常に軽量で、大量の同時接続をさばくNginxの特性を邪魔しません。
  3. ノンブロッキングI/O: Luaスクリプト内でDBへの問い合わせを行っても、Nginxのイベントループをブロックしません(ここが最重要!)。

つまり、「Nginxの性能を落とさずに、複雑なロジックを組み込める唯一の解」がLuaなのです。


第2章:OpenRestyのインストール

それでは、標準のNginxではなく、OpenRestyをインストールしてみましょう。
※OS: AlmaLinux 9 を前提とします(Ubuntuなども公式リポジトリがあります)。

1. リポジトリの追加

# 必要なツールのインストール
sudo dnf install -y 'dnf-command(config-manager)'

# OpenResty公式リポジトリの追加
sudo dnf config-manager --add-repo https://openresty.org/package/rhel/openresty.repo

2. インストール

sudo dnf install -y openresty openresty-resty

これでインストール完了です。
バイナリは /usr/local/openresty/nginx/sbin/nginx に、設定ファイルは /usr/local/openresty/nginx/conf/nginx.conf に配置されます。

3. サービス起動

sudo systemctl enable openresty
sudo systemctl start openresty

既存のNginxが動いている場合はポートが競合するため、停止するかポートを変更してください。


第3章:Hello Lua World!(基本構文と実行フェーズ)

まずは簡単なLuaスクリプトを動かして、Nginxの設定ファイル内にどう記述するかを見てみましょう。

content_by_lua_block

レスポンスの中身(Content)をLuaで生成するディレクティブです。

location /hello {
    default_type 'text/plain';
    
    content_by_lua_block {
        ngx.say("Hello, OpenResty!")
        ngx.log(ngx.ERR, "これはエラーログに出力されます")
    }
}

curl http://localhost/hello にアクセスすると、「Hello, OpenResty!」と返ってきます。
ngx.say はNginxの出力バッファにデータを書き込む関数です。

Nginxの処理フェーズとLua

Luaコードは、Nginxのリクエスト処理の「どのタイミング」で実行するかを指定できます。

ディレクティブ 実行フェーズ 主な用途
set_by_lua 変数のセット 複雑な計算をして変数に代入する。
access_by_lua アクセス制限 認証、IP制限、WAF的な処理。
content_by_lua コンテンツ生成 レスポンスボディの生成、APIの中継。
header_filter_by_lua ヘッダフィルタ レスポンスヘッダの書き換え。
log_by_lua ログ記録 カスタムログの送信(Fluentd等へ)。

第4章:実践1「Redisを使った動的ルーティング」

ここからが本番です。
「Redisに保存されたユーザー情報(例:beta_user:123 = true)」を見て、ベータ版サーバーへ振り分けるロジックを実装します。

Luaスクリプトの作成

設定ファイルに直接書くと長くなるので、外部ファイル(/usr/local/openresty/nginx/lua/routing.lua)に記述します。

-- ライブラリのロード
local redis = require "resty.redis"
local red = redis:new()

-- Redisへの接続(タイムアウト1秒)
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)

if not ok then
    ngx.log(ngx.ERR, "Redis connection failed: ", err)
    return
end

-- クエリパラメータからユーザーIDを取得 (?uid=123)
local args = ngx.req.get_uri_args()
local uid = args["uid"]

-- デフォルトのバックエンド
local target_backend = "http://stable_backend"

if uid then
    -- Redisからベータフラグを取得
    local is_beta, err = red:get("beta_user:" .. uid)
    
    if is_beta == "true" then
        target_backend = "http://beta_backend"
    end
end

-- 変数にセット(後でproxy_passで使う)
ngx.var.target = target_backend

-- 接続プールに戻す(これがないと接続が開いたままになる!)
red:set_keepalive(10000, 100)

nginx.confの設定

http {
    upstream stable_backend { server 10.0.0.1:80; }
    upstream beta_backend   { server 10.0.0.2:80; }

    server {
        listen 80;
        
        location /app {
            # 転送先を格納する変数を初期化
            set $target "";
            
            # アクセスフェーズでLuaを実行
            access_by_lua_file /usr/local/openresty/nginx/lua/routing.lua;
            
            # Luaで決定したバックエンドへ転送
            proxy_pass $target;
        }
    }
}

これで、アプリを改修することなく、インフラ側だけで高度なA/Bテストやカナリアリリースが可能になります。


第5章:実践2「Nginx層でのJWT検証」

APIサーバーへの全てのリクエストで、アプリケーション側でJWT(JSON Web Token)の検証を行っていませんか?
不正なトークンや期限切れのトークンは、アプリに届く前にNginxで弾いてしまえば、アプリサーバーのCPUリソースを大幅に節約できます。

lua-resty-jwt の利用

OpenRestyにはコミュニティ製の便利なライブラリがたくさんあります。
lua-resty-jwt を使うと、簡単に検証ができます。

access_by_lua_block {
    local jwt = require "resty.jwt"
    
    -- Authorizationヘッダーからトークン取得
    local auth_header = ngx.var.http_Authorization
    if not auth_header then
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end
    
    local token = string.sub(auth_header, 8) -- "Bearer "を除く
    
    -- 秘密鍵で検証
    local jwt_obj = jwt:verify("MY_SECRET_KEY", token)
    
    if not jwt_obj.verified then
        ngx.status = ngx.HTTP_UNAUTHORIZED
        ngx.say("Invalid Token: " .. jwt_obj.reason)
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end
    
    -- 検証OKならバックエンドへ(ここでユーザーIDをヘッダに詰めることも可能)
    ngx.req.set_header("X-User-Id", jwt_obj.payload.sub)
}

このように、「認証」という重い処理をゲートウェイ(Nginx)にオフロードすることで、マイクロサービス全体のスループットを向上させることができます。


第6章:ノンブロッキングI/Oと「サブリクエスト」

OpenRestyの真骨頂は、「サブリクエスト(ngx.location.capture)」です。
これは、Nginx内部で擬似的にHTTPリクエストを発行し、その結果をLuaで受け取る機能です。

例えば、「ユーザー情報API」と「商品情報API」を同時に叩いて、結果を合体させて返す(APIアグリゲーション)といったことが可能です。

content_by_lua_block {
    -- 2つのAPIを並列(ノンブロッキング)で実行
    local res1, res2 = ngx.location.capture_multi({
        { "/api/user" },
        { "/api/product" }
    })
    
    if res1.status == 200 and res2.status == 200 then
        local cjson = require "cjson"
        local user = cjson.decode(res1.body)
        local product = cjson.decode(res2.body)
        
        -- 結果をマージ
        local result = {
            user_name = user.name,
            product_title = product.title
        }
        
        ngx.say(cjson.encode(result))
    else
        ngx.exit(500)
    end
}

Node.jsなどでBFF(Backend For Frontend)を作るのと似ていますが、Nginx内で完結するため圧倒的に高速です。


第7章:プロの注意点(ブロッキングの罠)

Luaは強力ですが、使い方を間違えるとNginx全体の性能を道連れにして死にます。
以下の点に注意してください。

1. ブロッキング処理を書かない

Luaコード内で os.execute("sleep 10") や、時間のかかる重い計算(暗号化のループなど)を行ってはいけません。
Nginxはイベント駆動モデルなので、1つのリクエスト処理が止まると、他の数万リクエストも道連れになって止まります。
外部通信を行う場合は、必ず lua-resty-* 系のノンブロッキング対応ライブラリを使用してください。

2. グローバル変数を使わない

Luaのグローバル変数は、全てのリクエストで共有されてしまいます(レースコンディションの原因)。
変数は必ず local を付けて宣言しましょう。

3. キャッシュを活用する

Redisへの接続などは、set_keepalive を使ってコネクションプーリングを行いましょう。
毎回 connect していては、TCP接続のオーバーヘッドで性能が出ません。


まとめ:インフラを「コード」で制御せよ

お疲れ様でした!
今回は、Nginxの世界を一変させるOpenRestyとLuaについて解説しました。

今回の重要ポイント:

  • OpenRestyは、NginxにLuaJITを組み込んだ最強のプラットフォーム。
  • content_by_luaaccess_by_lua で、処理フェーズごとにロジックを挟める。
  • Redisと連携することで、動的なルーティングやレートリミットが実現できる。
  • JWT検証などの「認証」をNginx層で行うことで、バックエンドを保護・軽量化できる。
  • 必ず local 変数を使い、ノンブロッキングなライブラリを選ぶこと。

これで、あなたのNginxは単なる「土管(パケットを流すだけ)」から、「頭脳を持ったゲートウェイ」へと進化しました。
「こんなことNginxでできないかな?」と思ったら、まずはOpenRestyで実現できないか考えてみてください。

さて、ここまで高度なことができるようになると、このNginxサーバー自体が落ちてしまった時のダメージが計り知れません。
「単一障害点(SPOF)」を作らないためには、サーバーを冗長化し、1台が死んでも自動的に予備機に切り替わる仕組みが必要です。

次回、第5回は「絶対停止させない高可用性(HA)。KeepalivedによるVIP管理とフェイルオーバー」です。
仮想IPアドレス(VIP)を使って、アクティブ・スタンバイ構成を組むインフラエンジニア必須の技術「VRRP」の世界へご案内します。
物理サーバーでもクラウドでも使える、可用性設計の真髄を学びましょう。お楽しみに!

▼ OpenRestyを実践する環境を作る ▼

Luaスクリプトを試す
「VPS」で自分専用環境

おすすめVPSを見る

インフラ開発スキルを活かす
「ITエンジニア転職」

転職エージェントを見る

[PR]

💡 サーバー構築やコマンドの練習には、VPSが圧倒的におすすめです

手元のパソコンや大切なメイン環境で検証を行うと、設定ミスでシステムを壊してしまったり、不要なパッケージが溜まって動作が不安定になるリスクがあります。

もしあなたが実務レベルのスキルを最短で身につけたいのであれば、月額ワンコインから使えて、失敗しても数分で初期状態にリセットできるVPS(仮想専用サーバー)を利用するのが、プロも実践する最も確実で安全な学習方法です。

▼ プロも推奨するVPS環境はこちら ▼

Nignx講座
linux工房をフォローする

コメント