OpenResty XRay がどのようにしてアプリケーションの問題特定と効率化を支援するかをご覧ください。

詳細はこちら LIVE DEMO

この OpenResty チュートリアルシリーズでは、まっさらな Linux サーバーから、動作する Nginx + Lua アプリケーションまでを、手を動かす 7 つのステップで作り上げていきます——Lua の事前知識は不要です。

OpenResty は、Nginx に LuaJIT ランタイムを組み込んで拡張した、フル機能の Web プラットフォームです。Nginx の挙動をカスタマイズするために C モジュールを書く代わりに、Lua を書きます——そして、Nginx を高速にしているのと同じノンブロッキングなイベントループが、記述した Lua コードも高速にします。単一の OpenResty ワーカープロセスは、スレッドを生成することなく数万の同時接続を処理できます。

以下の各セクションは、学習パス上の 1 つのマイルストーンを扱い、構築内容の概要と、完全な実践記事へのリンクを添えています。OpenResty が初めての方は順番に進めてください。特定のトピックを探している方は、任意のセクションに直接ジャンプしても構いません。

OpenResty とは何か?

OpenResty は、Nginx、LuaJIT、および一連の Lua ライブラリを 1 つのディストリビューションにまとめたものです。リクエストが到着すると、Nginx はそれをイベント駆動のパイプラインでルーティングし、設定可能な各フェーズで制御を Lua コルーチンに渡します。そのコルーチンは、ワーカースレッドをブロックすることなく、ノンブロッキング I/O——TCP 接続、DNS ルックアップ、タイマー——を実行できます。なぜなら、OpenResty の cosocket API がコルーチンを中断し、データの準備ができたときに再開するからです。

その結果、次のようなアーキテクチャが生まれます:

  • 1 つの OS スレッドが数千の同時リクエストを多重化する。
  • Lua コードがカスタムロジック——レート制限、認証、動的ルーティング、レスポンス変換——において C を置き換える。
  • LuaJIT がホットな Lua パスをネイティブの機械語にコンパイルし、CPU バウンドな処理において手書きの C との差を縮める。

OpenResty は世界中で 4000 万を超えるドメインにデプロイされており、Kong、Apache APISIX、OpenResty Edge といった製品の基盤となっています。

このチュートリアルの対象読者

本シリーズは、すでに HTTP を理解し、Nginx をリバースプロキシや静的ファイルサーバーとして使ったことのあるエンジニアに向けて書かれています。Lua の事前知識は不要です——各記事は、そのステップに必要なだけの Lua をちょうど紹介します。Python、Go、Node.js の背景をお持ちの方なら、このノンブロッキングモデルに馴染みを感じるはずです。主な調整点は、すべてが独立したプロセスではなく Nginx のイベントループの内部で動作するという点です。

OpenResty チュートリアルシリーズ

本シリーズは段階的な学習パスをたどります。各記事は 1 つ前の記事の上に積み重なっていきます。

ステップ 1 —— OpenResty のインストール

Lua を書き始める前に、動作する OpenResty のインストールが必要です。OpenResty はすべての主要な Linux ディストリビューション向けにビルド済みのバイナリパッケージを提供しています——公式の APT または YUM リポジトリを使えばインストールは 5 分もかからず、コンパイルは不要です。

OpenResty のインストール方法

サポートされているすべての Linux ディストリビューションを網羅した公式パッケージリポジトリの手順については、OpenResty 公式 Linux Packages ページを参照してください。

インストール後、(システムの Nginx ではなく)OpenResty の Nginx バイナリを実行していることを、以下で確認します:

export PATH=/usr/local/openresty/nginx/sbin:$PATH
which nginx
nginx -v

ステップ 2 —— Hello World:最初の OpenResty アプリケーション

OpenResty での Hello World HTTP の例

最初の記事では、OpenResty アプリケーションをゼロから実行するために必要な最小限のプロジェクト構造を解説します。conf/logs/ ディレクトリを作成し、最小限の nginx.conf を書き、content_by_lua_block で Lua を埋め込み、curl で結果を確認します。

location / {
    default_type text/plain;
    content_by_lua_block {
        ngx.say("Hello World")
    }
}

読み終える頃には、nginx -p の prefix 規約を理解し、サーバーの起動・停止の方法を知り、本シリーズの後続のすべての記事で再利用できるプロジェクトの雛形を手に入れているでしょう。

ステップ 3 —— Lua モジュール:アプリケーションコードの整理

OpenResty で Lua モジュールを書く方法

インラインの content_by_lua_block コードはワンライナーには十分ですが、実際のアプリケーションでは再利用可能な関数を別々のファイルに置く必要があります。本記事では _M テーブルパターン——OpenResty で Lua モジュールを宣言する標準的な方法——を紹介し、require が実行時にモジュールを見つけられるよう lua_package_path を設定する方法を示します。

local _M = {}

function _M.greet(name)
    ngx.say("Greetings from ", name)
end

return _M

本記事ではまた、サーバー起動時にモジュールを事前ロードして最初の実リクエストがロードコストを負担しないようにする init_by_lua_block も扱います。これは後続のすべての記事の基礎となります。

ステップ 4 —— リクエスト間のデータ共有

OpenResty におけるリクエスト間のデータ共有

OpenResty のワーカーは、デフォルトではメモリを共有しない独立した OS プロセスです。本記事では、OpenResty がリクエストをまたいで状態を共有するために提供する 2 つのメカニズムを扱います:

Lua モジュール変数lua_shared_dict
スコープ単一のワーカープロセスすべてのワーカープロセス
値の型任意の Lua 型文字列、数値、真偽値
典型的な用途ワーカーごとの LRU キャッシュ、設定カウンター、レート制限、共有フラグ
アトミック操作いいえはい(incraddset

カウンター、レートリミッター、コネクションプールなど、状態を追跡するものを作る前に、ワーカーごとの共有とワーカー間の共有の違いを理解しておくことが不可欠です。

ステップ 5 —— HTTP レスポンスのストリーミング

OpenResty で ngx.flush を使った HTTP レスポンスのストリーミング

OpenResty の ngx.flush(true) 呼び出しは完全にノンブロッキングです。バッファされた出力をクライアントに送り出し、書き込みが完了するまで現在のコルーチンを中断し、その間ワーカースレッドを解放して他のリクエストを処理させます。これにより、大きなレスポンスのストリーミング——あるいは Server-Sent Events の実装——を、ブロッキングなしで簡単に行えます。

本記事では次のことを実演します:

  • チャンク化されたストリーミング出力のための location の設定
  • ループ内での ngx.flush(true)ngx.sleep() の使用
  • 単一のワーカープロセスに対して 500 の同時低速ストリーミング接続をベンチマーク(結果:120+ リクエスト/秒、単一の OS スレッド)
  • XMLHttpRequest を介してブラウザからストリームを消費する

ステップ 6 —— CLI ツール:resty と restydoc

OpenResty には、日々の開発を加速する 2 つのコマンドラインユーティリティが同梱されています。それぞれ異なる目的を持つため、別々の記事で扱います。

resty コマンドラインランナー

OpenResty resty CLI チュートリアル

resty は裏でヘッドレスの Nginx を立ち上げ、Lua コードをターミナルから直接実行します——nginx.conf は不要です。cosocket 呼び出しのテスト、モジュール関数のプロトタイピング、LuaJIT のコンパイル出力の確認を行う、最も速い方法です。

resty -e 'ngx.say("Hello World")'
resty -I lua/ -e 'require "mymodule".run()'
resty --shdict 'cache 10m' -e 'print(ngx.shared.cache:set("k", 1))'
resty -jv bench.lua   # JIT トレースを確認する

restydoc でドキュメントを調べる

restydoc で OpenResty のドキュメントを調べる方法

restydoc は、完全な OpenResty リファレンス——Nginx ディレクティブ、ngx.* Lua API、LuaJIT 拡張、同梱の Lua モジュールドキュメント——を、ブラウザを開くことなくターミナルに持ち込みます。Perl の perldoc をモデルにしており、less ページャを使用します。

restydoc -s ngx.flush          # OpenResty Lua API
restydoc -s lua_shared_dict    # Nginx ディレクティブ
restydoc -s string.find        # 標準 Lua
restydoc resty.redis           # 完全な Lua モジュールドキュメント

ステップ 7 —— パフォーマンス:ベンチマークと LuaJIT バイトコード

Lua コードを正しくベンチマークする

OpenResty で Lua コードをベンチマークする方法

time resty で Lua のパフォーマンスを測定するのは誤解を招きます——resty プロセスの起動が約 11 ミリ秒のオーバーヘッドを加え、ナノ秒スケールの関数コストを覆い隠してしまうからです。正しいアプローチは、経過時間の測定に ngx.now() を使い、記録開始前に関数が JIT コンパイルされていることを保証するためのウォームアップループを回し、保留中の GC オブジェクトをクリアするために collectgarbage() を使うことです。

local function target()
    ngx.re.find("hello, world.", [[\w+\.]], "jo")
end

for i = 1, 100 do target() end  -- JIT ウォームアップ

ngx.update_time()
local begin = ngx.now()
local N = 1e7
for i = 1, N do target() end
ngx.update_time()
ngx.say("elapsed: ", (ngx.now() - begin) / N)

Lua モジュールを LuaJIT バイトコードにプリコンパイルする

OpenResty における LuaJIT バイトコード:Lua モジュールをプリコンパイルする方法

大きな Lua モジュールでは、.ljbc バイトコードファイルへのプリコンパイルが起動時間を大幅に短縮します。ロードに 12 ミリ秒かかっていた 1.6 MB の Lua ソースファイルは、プリコンパイル後には 2 ミリ秒に短縮されました——6 倍の改善です。バイトコードファイルはソースよりも 50% 以上小さくもなります。

/usr/local/openresty/luajit/bin/luajit -bg mymodule.lua mymodule.ljbc

nginx.conf では、OpenResty に .ljbc ファイルを優先させます:

lua_package_path "$prefix/lua/?.ljbc;$prefix/lua/?.lua;;";

注意:.ljbc ファイルは OpenResty のビルドモード(x64 と GC64)をまたいで可搬ではありません。OpenResty をアップグレードした後は、必ずソースからバイトコードを再コンパイルしてください。

コアコンセプト クイックリファレンス

コンセプト何をするか導入された記事
content_by_lua_blocklocation の content フェーズに Lua ハンドラを埋め込むHello World
ngx.say()レスポンスバッファに 1 行書き込むHello World
lua_package_pathrequire.lua.ljbc ファイルの場所を教えるLua モジュール
init_by_lua_blockサーバー起動時に Lua を 1 回実行する。モジュールの事前ロードに使うLua モジュール
lua_shared_dictすべてのワーカーがアクセスできる名前付き共有メモリゾーンを宣言するデータ共有
ngx.shared.DICT共有メモリディクショナリを読み書きする Lua APIデータ共有
ngx.flush(true)書き込みバッファをクライアントへノンブロッキングにフラッシュするストリーミング
ngx.sleep()ワーカースレッドをブロックせずに現在のコルーチンを譲るストリーミング
resty -e設定ファイルなしでターミナルからインライン Lua を実行するresty CLI
restydoc -sターミナルのページャでディレクティブや API セクションを調べるrestydoc
ngx.now()ミリ秒精度の壁時計時間を秒単位で返すベンチマーク
.ljbcLuaJIT のプリコンパイル済みバイトコードファイル。.lua ソースより速くロードされるバイトコード

よくある質問

OpenResty は無料で使えますか?

はい。OpenResty は無料かつオープンソースのソフトウェアです。Nginx と同じ BSD ライセンスの下で配布されています。完全なソースコード、Linux 向けのビルド済みバイナリパッケージ、同梱の Lua ライブラリは、すべて openresty.org から無料で入手できます。本番環境で OpenResty を動かすのに有料ライセンスは必要ありません。

OpenResty と Nginx の違いは何ですか?

OpenResty は Nginx のスーパーセットです。OpenResty 公式サイトによれば、OpenResty は「NGINX と LuaJIT をベースにした動的 Web プラットフォーム」です。有効な nginx.conf はすべて OpenResty 上でそのまま動作し、Lua を段階的に——1 つの location ずつ——追加できます。違いは、OpenResty が LuaJIT を組み込み、ノンブロッキングな Lua API——ngx.* 名前空間——を公開している点にあり、これにより C モジュールをコンパイルせずにカスタムのリクエスト処理ロジックを書けます。プログラマブルなリクエストロジックが必要なら OpenResty を使ってください。カスタムコードのない素のリバースプロキシや静的ファイルサーバーが必要なだけなら、Nginx 単体で十分です。

OpenResty は何に使われますか?

OpenResty は、プログラマブルな API ゲートウェイ、Web アプリケーションファイアウォール(WAF)、カスタム認証やルーティングロジックを備えたリバースプロキシ、そして CDN のエッジノードとして最もよく使われます。Lua が Nginx のイベントループの内部でナノ秒レベルのレイテンシで動作するため、本来ならサイドカープロセスや外部サービスへの往復が必要になるようなタスク——トークン検証、レート制限、リクエストの書き換え、A/B ルーティングなど——に非常に適しています。

OpenResty はどのバージョンの Lua を使いますか?

OpenResty は LuaJIT 2.x を使用します。これは Lua 5.1 言語に、少数の Lua 5.2 互換拡張を加えたものを実装しています。OpenResty は、正確性とパフォーマンスのための追加パッチを含む独自の LuaJIT フォークを維持しています。標準的な Lua 5.1 コードは変更なしで動作し、FFI や bit ライブラリといった LuaJIT 固有の機能も利用できます。

すでに Nginx を知っている場合、OpenResty の習得は難しいですか?

Nginx 設定の側は同一です。新しいのは Lua 層です:ngx.* API、コルーチンベースの並行モデル、そして Nginx のリクエストフェーズ(access_by_lua_blockcontent_by_lua_blocklog_by_lua_block など)という概念です。本チュートリアルシリーズは各概念を、それが最初に必要になる箇所で紹介するので、ついていくのに Lua の事前知識は不要です。

OpenResty にエンタープライズ版はありますか?

はい。OpenResty Edge は、OpenResty の上に構築された商用製品です。プライベート CDN、ユニバーサルゲートウェイ、WAF、DDoS 防御、Kubernetes 統合を、単一のオンプレミスプラットフォームにまとめています——手で nginx.conf を編集するのではなく、Web UI 経由で管理します。OpenResty Edge は、OpenResty の作者が創業した OpenResty Inc. によって開発・サポートされています。

OpenResty で Lua のエラーをデバッグするには?

Lua のランタイムエラーは Nginx のエラーログに書き込まれます。最初に見るべき場所は次のとおりです:

tail -f logs/error.log

より構造化されたデバッグには、restydoc -s ngx.log がロギング API を説明しています。resty コマンドラインユーティリティも、稼働中のサーバーの外でバグを切り分けて再現するのに役立ちます。

このシリーズの先にあるもの

上記の 7 ステップを完了すれば、本番環境の OpenResty アプリケーションを構築する基礎が身につきます。探求すべきトピックには、次のものがあります:

  • resty.lrucache —— ワーカー間の共有を必要としないホットデータのための、ワーカーごとの LRU キャッシュモジュール
  • cosocket 接続 —— Redis、MySQL、PostgreSQL などのバックエンドへノンブロッキングに接続する ngx.socket.tcp()
  • ngx.timer.atngx.timer.every —— リクエストサイクルの外で定期的なタスクを実行するバックグラウンドタイマー
  • OpenResty XRay —— コードを変更することなく、稼働中の OpenResty アプリケーションのプロファイリングとトラブルシューティングを行う非侵襲的な動的トレーシングツール

このチュートリアルシリーズが気に入っていただけましたら、このブログと当社の YouTube チャンネルをご購読ください。

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵襲的な障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。心より感謝申し上げます!