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

詳細はこちら LIVE DEMO

オンラインの Lua アプリケーション(OpenResty や Nginx で実行されているものを含む。Nginx の場合でも、OpenResty のコアコンポーネントである Lua Nginx モジュールを使用しています)のトラブルシューティング時に、永続的な Lua データ構造を検査することは一般的な要件です。Lua データ構造は、読み込まれた Lua モジュールの深部に埋め込まれたり、アップバリューを介して Lua 関数から参照されたりすることがあります。従来の Lua データ検査方法は、特別な API を暴露して対応する Lua データをシリアライズしダンプするというものでした。しかし、動的トレース技術を使用して実行中のプロセス内で Lua データを調べる方が、はるかに柔軟で効率的です。

OpenResty XRay は、YLua と呼ばれる Lua 互換言語を提供しており、100% 非侵襲的な方法で実行中のプロセス内の任意の Lua データを検査するためのツールを作成することができます。このチュートリアルでは、実行中の OpenResty および LuaJIT アプリケーションにおいて、読み込まれたすべての Lua モジュールの名前をダンプする簡単なユースケースを紹介します。今後のチュートリアルでは、YLua を使用したより複雑なユースケースを紹介する予定です。

システム環境

ここでは例として Red Hat Enterprise Linux 8 システムを使用します。OpenResty XRay がサポートする Linux ディストリビューションであれば、Ubuntu、Debian、Fedora、Rocky、Alpine などどれでも同様に動作するはずです。

対象アプリケーションとして、変更を加えていないオープンソースの OpenResty バイナリビルドを使用します。OpenResty や Nginx のバイナリは、自分でコンパイルしたものも含めて、何でも使えます。 既存のサーバーインストールやプロセスに特別なビルドオプション、プラグイン、ライブラリは必要ありません。これが 動的トレース 技術の美しさです。真に非侵襲的なのです。

また、同じシステム上で OpenResty XRay の Agent デーモンを実行し、openresty-xray-cli パッケージからコマンドラインユーティリティをインストールして設定しています。

読み込み済み Lua モジュールの名前

標準ツールの使用

対象プロセスで読み込まれた Lua モジュールをダンプする最も簡単な方法は、OpenResty XRay の標準ツール lj-loaded-modules を使用することです:

ps コマンドを使用して、分析したい nginx ワーカープロセスを見つけます。ここでは 1046 です。

$ ps aux | grep nginx
1 S root     1059    1  0  80   0 - 73941 -      15:03 ?        00:00:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
5 S nobody   1046 1059  0  80   0 - 82123 -      15:03 ?        00:00:00 nginx: worker process

次に、openresty-xray-cli パッケージの orxray コマンドラインユーティリティを使用して、標準アナライザー lj-dump-loaded-mods を実行します。-p オプションは分析対象のプロセス ID(または PID)を指定します。

# 4096 is the target process's PID
$ orxray analyzer run lj-loaded-modules -p 4096
Start tracing...
jit: table
math: table
coroutine: table
debug: table
...
Go to https://x5vrki.xray.openresty.com/targets/68/history/751715 for charts.

カスタム YLua ツールの使用

標準ツールは、内部で YLua 言語を使用して実装されています。ここでは、YLua を使用して、このツールを自分で再作成する方法を見ていきましょう。LuaJIT VM(および標準 Lua 5.1 インタープリタの VM)では、ロードされた Lua モジュールが常に package.loaded にキャッシュされることを知っています。キーはモジュール名で、値はモジュールデータです。ここで、例として my-dump-loaded-mods.ylua という名前の新しい YLua ソースファイルを作成できます:

probe process.begin
    for k, v in pairs(package.loaded) do
        print(k, ": ", type(v))
    end
    exit()
end

ここで、probe キーワードは、ツールがターゲットプロセスにアタッチされたときにトリガーされる process.begin プローブポイントの新しいプローブハンドラを定義します1。そして、YLuapackage.loaded Lua テーブルを反復処理することで Lua モジュールをダンプします。これは Lua と全く同じ構文です。モジュール名とそのデータ型を出力した後、exit() を呼び出してトレースプロセス全体を終了します。そして、openresty-xray-cli パッケージの run-ylua コマンドラインユーティリティを使用して、この YLua ソースファイルを実行できます。

OpenResty または Nginx Lua アプリケーションの検査

分析したい nginx ワーカープロセスを見つけるには ps コマンドを使用します。ここでは 1046 です。

$ ps aux | grep nginx
1 S root     1059    1  0  80   0 - 73941 -      15:03 ?        00:00:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
5 S nobody   1046 1059  0  80   0 - 82123 -      15:03 ?        00:00:00 nginx: worker process

前回の YLua ソースファイルを、openresty-xray-cli パッケージの run-ylua コマンドラインユーティリティを使用して実行してみましょう。-p オプションは、分析対象のプロセス ID(または PID)を指定します。

$ run-ylua -p 1046 my-dump-loaded-mods.ylua
Start tracing...
resty.core.uri: table
resty.core.exit: table
resty.core.base64: table
resty.core.request: table
resty.core.response: table
string: table
pgmoon: table
openresty_org.controller: table
ndk: table
table: table
resty.core.utils: table
resty.lrucache: table
table.new: function
debug: table
table.clear: function
...
package: table
math: table
resty.core.base: table
jit.opt: table
ngx: table
_G: table
resty.core.var: table
resty.core.worker: table
resty.core.regex: table
resty.core.shdict: table
resty.core.time: table
resty.core.hash: table

簡潔にするため、出力の大部分は省略しています。出力を wc -l コマンドにパイプして、合計数を確認することができます:

$ run-ylua -p 1046 my-dump-loaded-mods.ylua | wc -l
Start tracing...
38

モジュールの大部分は table 型です。通常、Lua モジュールは Lua テーブルで表現されます。一部のモジュールは function 型であり、ユニークです。例えば、table.newLuaJIT によって特別に実装された関数型モジュールです。

  • ioosstringtablemathdebug は Lua 5.1 言語で定義された標準 Lua モジュールです。
  • jitbitffijit.opttable.newtable.clear は標準 LuaJIT モジュールです。
  • resty.*coroutinendkngx は OpenResty によって導入されたモジュールです。

スタンドアロンの LuaJIT アプリケーションの検査

Nginx や OpenResty を使用せずに、スタンドアロンの luajit プロセスを検査することもできます。

まず、いつものように luajit プロセスの PID を見つけます:

$ ps aux | grep luajit
root     3371845  0.0  0.0   4428  2524 pts/3    S    22:17   0:00 luajit t.lua

そして、このPID 3371845 を使用して run-ylua コマンドを実行します:

$ orxray analyzer run lj-loaded-modules -p 3371845
Start tracing...
jit: table
math: table
coroutine: table
debug: table
os: table
_G: table
package: table
string: table
jit.opt: table
bit: table
io: table
table: table

ご覧の通り、スタンドアロンの luajit プログラムは通常、デフォルトで読み込む Lua モジュールの数が少なくなっています。

出力を wc -l コマンドにパイプして、総数を確認することができます:

$ orxray analyzer run lj-loaded-modules -p 3371845 | wc -l
Start tracing...
12

Web コンソールで直接実行

ユーザーは、このチュートリアルで紹介したツールを OpenResty XRay の Web コンソールで直接実行することができます。高い CPU 使用率などの興味深いイベントが発生した際に、自動的にトリガーすることも可能です。openresty-xray-cli のコマンドラインユーティリティはデモンストレーション目的に便利です。また、DevOps や SRE の担当者が他のシステムに容易に自動化・統合することができます。

コンテナ内のアプリケーションのトレース

OpenResty XRay ツールは、コンテナ化されたアプリケーションを透過的にトレースすることをサポートしています。Docker と Kubernetes (K8s) のコンテナは透過的に動作します。通常のアプリケーションプロセスと同様に、対象のコンテナにアプリケーションや追加の特権は必要ありません。OpenResty XRay エージェントデーモンは、対象のコンテナの外部(ホストオペレーティングシステム上や特権を持つ独自のコンテナ内など)で実行する必要があります。

例を見てみましょう。まず、docker ps コマンドでコンテナ名またはコンテナ ID を確認します。

$ docker ps
CONTAINER ID   IMAGE                                       COMMAND                  CREATED         STATUS         PORTS     NAMES
4465297209d9   openresty/openresty:1.19.3.1-2-alpine-fat   "/usr/local/openrest…"   18 months ago   Up 3 minutes             angry_mclaren

ここでは、コンテナ名が angry_mclaren です。次に、このコンテナ内の対象プロセスの PID を特定します。

$ docker top angry_mclaren
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                3373047             3373026             0                   22:23               ?                   00:00:00            nginx: master process /usr/local/openresty/bin/openresty -g daemon off;
nobody              3373101             3373047             0                   22:23               ?                   00:00:00            nginx: worker process

openresty ワーカープロセスの PID は 3373101 です。そして、通常通りこの PID に対して OpenResty XRay アナライザーを実行します。

$ orxray analyzer run lj-loaded-modules -p 3373101
Start tracing...
table: table
ngx: table
resty.core.var: table
table.new: function
resty.core.regex: table
resty.core.shdict: table
resty.core.time: table
...
table.clear: function
resty.core.worker: table
resty.lrucache: table
io: table

OpenResty XRay は、長時間実行されているプロセスを特定のタイプ(「OpenResty」、「Python」など)の「アプリケーション」として自動的に検出することも可能です。

ツールの実装方法

すべてのツールは Y 言語 で実装されています。OpenResty XRay は、OpenResty XRay の Stap+2 または eBPF3 バックエンドを使用してこれらを実行します。両者とも、Linux カーネルの uprobes および kprobes 機能に基づく 100% 非侵襲的な動的トレーシング技術を使用しています。YLua スクリプトはまず Y 言語にコンパイルされ、その後実行可能な動的トレーシングツールにコンパイルされます。

対象アプリケーションやプロセスとの連携は一切必要ありません。ログデータやメトリクスデータは使用せず、必要ともしません。実行中のプロセスのプロセス空間を厳密に読み取り専用の方法で直接分析します。また、対象プロセスにバイトコードやその他の実行可能コードを一切注入しません。100% クリーンで安全です。

ツールのオーバーヘッド

このチュートリアルで紹介する動的トレーシングツールは非常に効率的で、オンライン実行に適しています。

ツールが実行されておらず、アクティブにサンプリングしていない場合、システムおよび対象プロセスへのオーバーヘッドは厳密にゼロです。対象アプリケーションやプロセスに追加のコードやプラグインを注入することは一切ないため、固有のオーバーヘッドはありません。

サンプリング中、このチュートリアルで取り上げるツールの平均リクエストレイテンシーとスループットへのオーバーヘッドは通常測定できません。ここで紹介するツールは、いずれにせよ一度限りの検査を行うだけです。

OpenResty XRay について

OpenResty XRay動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、動作の問題、セキュリティの脆弱性を解決し、実行可能な提案を提供いたします。基盤となる実装において、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、様々な環境下で複数の異なるランタイムをサポートしております。

著者について

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

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

翻訳

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


  1. これは、GDB での ptrace() システムコールを通じたアタッチとは大きく異なることに注意してください。ここでは、より安全で高速な 動的トレーシング アタッチメントを使用しています。 ↩︎

  2. Stap+ は OpenResty Inc が大幅に強化した SystemTap のバージョンです。 ↩︎

  3. これは実際、OpenResty Inc が大幅に強化した eBPF 実装である ORBPF です。 ↩︎