OpenResty中的各种调试方法

API7.ai

December 16, 2022

OpenResty (NGINX + Lua)

OpenRestyのコミュニケーショングループでは、開発者からよくこのような質問が寄せられます:OpenRestyでどのようにデバッグするのか?私の知る限り、OpenRestyにはブレークポイントデバッグをサポートするツールがいくつかあります。VSCodeのプラグインもその一つですが、これまで広く使われてはいません。作者のagentzhを含め、私が知る限りのコントリビューターたちは皆、最もシンプルなngx.logngx.sayを使ってデバッグを行っています。

これは多くの初心者にとってフレンドリーではありません。OpenRestyの多くのコアメンテナーたちが、難しい問題に遭遇した時にプリミティブなログ出力しか使わないという意味なのでしょうか?

もちろんそうではありません。OpenRestyの世界では、SystemTapとフレームグラフが難しい問題やパフォーマンス問題に対処するための標準ツールです。メーリングリストやissueでこのような質問をすると、プロジェクトのメンテナーはフレームグラフをアップロードするように求め、テキストではなくグラフィカルな説明を求めるでしょう。

次の2つの記事では、デバッグについて、そしてOpenRestyがデバッグ用に特別に作成したツールセットについて話します。今日はまず、デバッグプログラムに利用できるものについて見ていきましょう。

ブレークポイントとログ出力

長い間、私の仕事ではIDE(統合開発環境)の高度なデバッグ機能に頼ってプログラムをトレースしていました。これは自然なことのように思えました。テスト環境で再現可能な問題であれば、どれだけ複雑であっても、その根本原因を見つけられる自信がありました。その理由は、バグが繰り返し再現可能であり、ブレークポイントを設定してログを出力することで原因を突き止められるからです。必要なのは忍耐力だけです。

この観点から見ると、テスト環境で安定して再現するバグを解決することは、物理的な作業です。私が仕事で解決するバグのほとんどはこのカテゴリーに属します。

ただし、ここには2つの前提条件があります:テスト環境と安定した再現性です。現実は常に理想とは異なります。もしバグが本番環境でのみ再現される場合、デバッグする方法はあるのでしょうか?

ここで一つのツールを紹介します - Mozilla RRです。これをリピーターとして使うことができ、プログラムの動作を記録し、それを繰り返し再生することができます。率直に言って、本番環境であろうとテスト環境であろうと、バグの「証拠」を記録できれば、それを「法廷証拠」としてゆっくり分析することができます。

二分探索アルゴリズムとコメントアウト

しかし、一部の大規模なプロジェクトでは、例えばバグが複数のサービスのいずれかから発生している場合や、データベースをクエリするSQL文に問題がある場合など、バグが安定して再現できたとしても、どの部分でバグが発生しているのかを特定できないことがあります。そのため、Mozilla RRのような記録ツールは役に立ちません。

この時、古典的な「二分探索アルゴリズム」を思い出すかもしれません。まずコードの論理の半分をコメントアウトし、問題が続く場合は、コメントアウトされていないコードにバグがあるので、残りの半分の論理をコメントアウトしてループを続けます。数回のうちに、問題は完全に管理可能なサイズに絞り込まれます。

このアプローチは少し愚かに聞こえるかもしれませんが、多くのシナリオで効率的です。もちろん、技術の進歩とシステムの複雑さの増加に伴い、OpenTracingのような標準を使用して分散トレーシングを行うことをお勧めします。

OpenTracingはシステムのさまざまな部分に埋め込むことができ、複数のSpanで構成される呼び出しチェーンとイベントトレースをTrace IDを通じてサーバーに報告し、分析とグラフィカルな表示を行います。これにより、開発者は多くの隠れた問題を見つけることができ、履歴データも保存されるため、いつでも比較して確認できます。

また、システムがより複雑な場合、例えばマイクロサービス環境では、ZipkinApache SkyWalkingが良い選択肢です。

動的デバッグ

これまでに説明したデバッグ方法は、ほとんどの問題を解決するのに十分です。しかし、本番環境で時々しか発生しない障害に遭遇した場合、ログやイベントトレースを追加して追跡するにはかなりの時間がかかります。

数年前、私は毎日午前1時頃にデータベースリソースが枯渇し、システム全体が雪崩を起こすシステムを担当していました。当時、私たちは昼間にコード内のスケジュールタスクをチェックし、夜にはチームが会社でバグが再現されるのを待ち、再現された時にサブモジュールの実行状態をチェックしました。バグの原因を見つけるまでに3日目の夜までかかりました。

私の経験は、Dtraceを作成したSolarisシステムエンジニアたちの背景と似ています。当時、Solarisのエンジニアたちも奇妙な本番問題のトラブルシューティングに日夜を費やし、最終的には設定が間違っていたことが原因だとわかりました。しかし、私とは異なり、Solarisのエンジニアたちはこの問題を完全に回避することを決め、Dtraceを発明しました。これは特に動的デバッグのために設計されました。

GDBのような静的デバッグツールとは異なり、動的デバッグはオンラインサービスをデバッグできます。デバッグプロセス全体は、デバッグ対象のプログラムに対して非感応的で非侵入的であり、コードを変更する必要もなく、再起動も必要ありません。例えるなら、動的デバッグはX線のようなもので、患者の体を検査するために採血や胃カメラを必要としません。

Dtraceは最初の動的トレーシングフレームワークの一つであり、その影響力により、他のシステムでも同様の動的デバッグツールが登場しました。例えば、Red HatのエンジニアたちはLinux上でSystemtapを作成しました。これについて次に話します。

Systemtap

Systemtapには独自のDSLがあり、プローブポイントを設定するために使用できます。詳細に入る前に、抽象的な話を超えるためにSystemtapをインストールしましょう。ここでは、システムのパッケージマネージャーを使ってインストールします。

sudo apt install systemtap

Systemtapで書かれたhello worldプログラムを見てみましょう:

# cat hello-world.stp
probe begin
{
  print("hello world!")
  exit()
}

簡単そうに見えませんか?sudo権限を使って実行する必要があります。

sudo stap hello-world.stp

hello world!と出力されます。ほとんどのシナリオでは、分析を行うために独自のstapスクリプトを書く必要はありません。なぜなら、OpenRestyには既に多くの既製のstapスクリプトがあり、定期的な分析を行うために使用できます。次の記事でそれらを紹介します。ですから、今日はstapスクリプトについて簡単に理解しておく必要があります。

いくつかの実践を経て、概念に戻ると、Systemtapは上記のstapスクリプトをCに変換し、システムのCコンパイラを実行してkernelモジュールを作成することで動作します。モジュールがロードされると、カーネルにフックしてすべてのプローブイベントをアクティブにします。

例えば、beginはプローブの開始時に実行され、対応するendもあります。したがって、上記のhello worldプログラムは次のようにも書けます:

probe begin
{
  print("hello ")
  exit()
}

probe end
{
print("world!")

ここでは、Systemtapについて非常に簡単に紹介しました。Systemtapの作者であるFrank Ch. Eiglerは、Systemtap tutorialという電子書籍を書いており、Systemtapについて詳細に紹介しています。もしもっと深く学び、Systemtapを理解したい場合は、この本から始めることをお勧めします。

その他の動的トレーシングフレームワーク

Systemtapは、カーネルやパフォーマンス分析エンジニアにとって十分ではありません。

  1. Systemtapはデフォルトではシステムカーネルに入りません。
  2. その動作方式により、起動が遅く、システムの正常な動作に影響を与える可能性があります。

eBPF(拡張BPF)は、近年Linuxカーネルに追加された新機能です。Systemtapと比較して、eBPFはカーネルサポートが直接行われ、クラッシュがなく、起動が速いという利点があります。同時に、DSLを使用せず、直接C構文を使用するため、始めるのがずっと簡単です。

オープンソースソリューションに加えて、IntelのVTuneも最高のツールの一つです。その直感的なインターフェース操作とデータ表示により、コードを書かずにパフォーマンスのボトルネックを分析できます。

フレームグラフ

最後に、前の記事で触れたフレームグラフを思い出しましょう。前述の通り、perfSystemtapなどのツールによって生成されたデータは、フレームグラフを使用してより視覚的に表示できます。以下の図はフレームグラフの例です。

フレームグラフ

フレームグラフでは、色ブロックの色と濃淡は意味がなく、単に異なる色ブロックを簡単に区別するためのものです。フレームグラフは、毎回サンプリングされたデータの重ね合わせであるため、ユーザーデータはブロックの幅と長さです。

CPU上のフレームグラフでは、色ブロックの幅は関数が占めるCPU時間の割合です:ブロックが広いほど、パフォーマンスの消耗が大きいことを示します。平らな頂上のピークがある場合、それがパフォーマンスのボトルネックです。一方、色ブロックの長さは関数呼び出しの深さを表し、上部のボックスは実行中の関数を示し、その下にあるすべてのボックスはその関数の呼び出し元です。したがって、下の関数は上の関数のスーパータイプです:ピークが高いほど、関数呼び出しが深いことを示します。

まとめ

動的トレーシングのような非侵入的な技術でさえ、完璧ではないことを知っておくことが重要です。特定の個々のプロセスしか検出できず、一般的にはそのデータをサンプリングするために短時間だけ有効にします。したがって、複数のサービスにわたる検出や長時間の検出が必要な場合は、opentracingのような分散トレーシング技術が必要です。

普段の仕事でどのようなデバッグツールや技術を使用していますか?コメントを残して私と議論してください。また、この記事を友人と共有して、一緒に学び、進歩しましょう。