この記事では、uWSGI + nginxで実行されている単純なFlaskアプリケーションを監視において完全に機能させる例を使用して、それをどのように行ったかについて説明します。
#ちょっとした歴史
Prometheusはもともと、GoogleのBorgmon由来の監視ツールです。
ネイティブ環境では、Borgmonはユビキタスで簡単なサービスディスカバリに依存しています。監視対象のサービスはBorgによって管理されるため、簡単に見つけることができるはずです。 その例としては、特定のユーザーのクラスターで実行されているすべてのジョブだったり、より複雑な展開の場合、一緒にジョブを構成するすべてのサブタスクが存在しています。
これらはそれぞれ、Prometheusの/ metrics
と同様に、Borgmonが/ varz
エンドポイントからデータを取得するための単一のターゲットになる可能性があり、それぞれは通常、C ++、Java、Go、または(あまり一般的ではありませんが)Pythonで記述されたマルチスレッドサーバーです。
Prometheusは、その環境に関するBorgmanの多くの仮定を継承しています。 特に、クライアントライブラリは、メトリックが、共有アドレススペースで実行される複数の実行スレッドのさまざまなライブラリおよびサブシステムからのものであると想定しており、サーバー側では、Prometheusは1つのターゲットが1つの(おそらく)マルチスレッドプログラムであると想定します。
#このやり方じゃダメ
これらの前提は、特にPythonの世界で、Google以外の多くのデプロイメントで破壊されています。 ここでは、(たとえば、DjangoやFlaskを使用して)リクエストを複数のワーカーに分散するWSGIアプリケーションサーバーで実行するのが一般的です(各ワーカーはスレッドではなくプロセスです)。
uWSGIの下で実行されているFlaskアプリ用のPrometheus Pythonクライアントの単純なデプロイメントでは、Prometheusサーバーから/ metrics
への各リクエストが異なるワーカープロセスにヒットし、それぞれが独自のカウンター、ヒストグラムなどをエクスポートします。よって、結果としてモニタリングデータはごみとなります。
実際、特定のカウンターの各スクレイプは、ジョブ全体ではなく、1つのワーカーの値を返します。値はあちこちにジャンプし、アプリケーション全体について何も役に立たないことを教えてくれるはずです。
#解決策
Amit Sahaは、同じ問題とさまざまな解決策を詳細な記事で話し合っています。 その中の一つの記事で説明されているのを参考にすると、PrometheusPythonクライアントには、この状況を処理することを目的としたマルチプロセスモードが含まれており、アプリケーションサーバーの動機付けの例はgunicornです。
これは、アプリケーション内のすべてのプロセス間でmmap()'d dictionariesのディレクトリを共有することで機能します。 次に、各プロセスは、Prometheusによってスクレイピングされたときに、アプリケーション全体のメトリックの共有ビューを返すために計算を行います。
これには、ドキュメントに記載されているいくつかの「見出し」の欠点があります。例として、プロセスごとの無料のPythonメトリックスがない、特定のメトリックスタイプの完全なサポートの欠如、わずかに複雑なゲージタイプなどがあります。
エンドツーエンドの構成も困難です。 ここでは、何が必要で、どのように環境で各部分を達成したかを示します。 うまくいけば、この完全な例は、将来的に同様の作業を行うすべての人に役立つはずです。
-
共有ディレクトリは、環境変数
prometheus_multiproc_dir
としてプロセスに渡す必要があります。
uWSGIのenvオプションを使用して渡します。uwsgi.iniを参照してください。 -
クライアントの共有ディレクトリは、アプリケーションの再起動後にクリアする必要があります。これは理解するのが少し難しいですが、uWSGIのハードコーディングされたフックの1つである
exec-asap
を使用して、構成ファイルを読み取った直後で、他の処理を行う前にシェルスクリプトを実行します。 uwsgi.iniを参照してください。
このスクリプトでは、Prometheusクライアントの共有データディレクトリを削除して再作成します。
適切な権限を確認するために、スーパーバイザーの下でroot
としてuwsgiを実行し、uwsgi内のprivsを削除します。 -
アプリケーションは、Pythonクライアントのマルチプロセスモードを設定する必要があります。
これは主に、Sahaの投稿を介して行ったドキュメントに従うことです。metrics.pyを参照してください。
また、これには、応答ステータスとレイテンシのPrometheusメトリックスをエクスポートするきちんとしたミドルウェアが含まれていることに注意してください。 -
uWSGIは、アプリケーションがfork()の後にロードされるようにアプリケーション環境を設定する必要があります。
デフォルトでは、uWSGIはアプリケーションをロードしてからfork()
することでメモリを節約しようとします。これには確かにコピーオンライトの利点があり、メモリを大幅に節約できます。
ただし、クライアントのマルチプロセスモードの操作に干渉しているようです。おそらく、このようにfork()の前にロックがかかっているためでしょうか。
uWSGIのlazy-appsオプションを使用すると、フォーク後にアプリケーションをロードできるため、よりクリーンな環境が得られます。
これらにより、uWSGIの下で実行されているFlaskアプリの/ metrics
エンドポイントが機能し、 pandoras_flaskデモで完全に機能することができるかと思います。
デモでは、別のポートのメトリックエンドポイントを適切なアプリに公開していることに注意してください。これにより、ユーザーがアクセスすることなく、モニタリングへのアクセスを簡単に許可できます。
デプロイメントでは、uwsgi_exporterを使用して、uWSGI自体からより多くの統計を取得できるはずです。
#特徴
Sahaのブログ投稿では、推奨されるソリューションとしてローカルのstatsdを介してメトリックをプッシュすることで、一連の代替案を説明しています。 それは実際に私たちが好む方法ではありません。
最終的には、kubernetesのようなコンテナオーケストレーションの下ですべてを実行することで、Prometheusが輝くネイティブ環境が提供されますが、これは既存のPythonアプリケーションスタックで他の利点を得るための大きなステップです。
おそらく最もプロメシアンの中間ステップは、各サブプロセスを個別にスクレイピングターゲットとして登録することです。 これはdjango-prometheusが採用したアプローチですが、提案されている「ポート範囲」アプローチは少し煩雑なものです。
私たちの環境では、次のような方法でこのアイデアを実装できます(まだ実装している可能性があります)。
- 各プロセスのスレッド内でWebサーバーを実行し、一時的なポートでリッスンし、/ metricsクエリを処理します。
- ウェブサーバーを登録し、短いTTL etcdパスでそのアドレス(例:hostname:32769)を定期的に更新します。ほとんどのサービスディスカバリニーズにすでにetcdを使用しています。
- Prometheusでfile based service discoveryを使用して、これらのターゲットを特定し、個人としてそれらをこすります。
このアプローチは、Pythonクライアントのマルチプロセスモードを使用する場合よりも複雑ではないと思いますが、独自の複雑さが伴います。
ワーカーごとに1つのターゲットがあることは、時系列の爆発の原因となることに注意してください。 たとえば、この場合、8つのワーカーにわたるPythonクライアントからの応答時間を追跡する単一のデフォルトのヒストグラムメトリックは、含める他のラベルを掛ける前に、約140の個別の時系列を生成します。 これは、Prometheusが処理する問題ではありませんが、スケーリングすると増加(または増加)する可能性があるため、注意してください。
#まとめ
とりあえず、標準のPythonウェブアプリスタックからメトリックをPrometheusにエクスポートすることは、どのような方法をとっても少し複雑です。 この投稿が、既存のnginx + uwsgi + Flaskアプリを使い始めたい人に役立つことを願っています。
コンテナーオーケストレーションの下でより多くのサービスを実行するとき(私たちがやろうとしていること)は、Prometheusモニタリングをそれらと統合することがより簡単になると期待しています。
Prometheusの定評のあるユーザーは、Hosted Prometheusのサービスをご覧になることをお勧めします。デモが必要な場合は、ご遠慮なくお申し付けください。