Azureでサーバレス 、監視はどうする? 試して分かった、7つのこと
はじめに
Azure Advent Calendar 2019 1日目の記事です。 担当は@yktm31です。
本記事では、Azure上でサーバレスシステムを分散トレーシングで監視しようと試みた際に、 できたこと・難しかったことをまとめていきます。
AzureにはApplication Insightsという、Webアプリケーションを監視するためのサービスがあります。 パフォーマンスや例外、セッション数などを監視することができます。(公式)
今回、Application Insightsを利用し、リソース間関係・ボトルネックの可視化を目指しました。 下記の記事でやっている事をAzureでもやってみるというイメージです。 AWS X-RayでLambdaとAWSリソースをトレースする(Python)
システム構成
監視対象とするシステムは、Azure Functionsをメインに使った、サーバレスなLine Botです。 使用言語はpythonを利用しました。
Lineで写真を送ると、物体に矩形がついた写真が返ってくるというものです。 利用した技術要素としては、以下の様な感じです。
- Line Messaging API
- YOLOv3
- Flask
- Azure周り
Functinos / Storage / Container Instance / Container Registry / Service Bus / Application Insights - OpenCensus(※後述)
構成は以下の通りです。
可視化したい箇所は。図中の緑の線です。 このシステムをApplication Insightsを利用して、監視してみました。
なお。今回詳しい実装については触れませんので、ご容赦ください。
気が向いたら、別の記事で書くかもしれません。
また以下についても説明を割愛させていただきます。
・分散トレーシングの詳細な挙動
・Azure Functionをはじめとする、Azure各リソースの作成・デプロイ方法
・Line Messaging APIの実装について
わかった事
1. そもそも、Pythonはプレビュー段階で、サポートされていない部分が多い
タイトルの通りですね。公式のドキュメント読んでいると、C#や.Net、JavaScriptに比べ、Pythonはサポートしていないという機能がいくつかありました。 Python アプリケーション用に Azure Monitor をセットアップする (プレビュー)
この先に触れていくことも、Pythonだと難しいが、他の言語ならできるということはあります。 最初の言語選択として、どの程度親和性が高いかは最初にみておいた方が良さそうです。
2. Application Insightsの依存関係は、Operation Idで紐づけられる
分散トレーシングの基本概念として、SpanとTraceというものがあります。 Spanとは、1処理・1コンポーネントという単位です。 k8sで言えば1Pod、FaaSで言えば1関数という単位になるかと思います。
Traceとは、関連するSpanをつなぎ合わせた、一連のSpanの集合です。 Spanについているtrace_idとspan_idを伝播させていくことで、各処理のトレーシングを実現します。 この辺のことについては、下記の資料が分かりやすかったです。 クラウドネイティブ時代の分散トレーシング - Distributed Tracing in a Cloud Native Age
Application InsightsでのOperation Idは、Trace IDに当たります。 Azureでは、下記のドキュメントに各用語について解説されています。 Application Insights Telemetry のデータ モデル
というわけで、同じOperation Idを持つリクエストやログは一連の処理として紐付けられます。 Application Insights上で確認してみます。 Application Insightsのリソース画面に移動し、「検索」を押します。
トレースを選び、クリックすると以下のように表示されます。
タイムラインを表示すると、こうなります。 右上の操作IDが、OperaitionIDになります。
呼び出しの親子関係がわかり、それぞれの実行時間を可視化することができました。
3. Azure Functionsは関数毎ではなく、リソース毎にマップされる
これが一つ目の罠でした。 まず前提として、Azure Functionsは、一つのリソースに複数の関数を作成することができます。 しかし、一つのリソースにある関数は、Application Map上では一つのノードとして表示されてしまう様です。
次の記事は2018年のものですが、1関数ごとにリソースを作成し、関数をマッピングさせています。 Azure Functions 2.0 – Real World Use Case for Serverless Architecture
今回、serverless-ml-linebotと、serverless-yoloという二つの関数を、別のリソースとして作成しました。 確かにそれぞれAplication Mapに表示できました。 ただ、2つ3つ程度ならばまだしも、大規模になると大変そうです。
今後のアップデートで対応されて欲しい部分の一つですね。
4. Azure FunctionsのBindingsを使うと、自動で依存関係を収集してくれる
Azure Functionsで複数の関数を一連の流れとしてまとめる場合、どうなるのか。 Bindingsを利用すれば、自動で依存関係を収集してくれます。 例えば。関数AがServiceBusにキューを詰めて、関数Bがキューをトリガーにして発火するというケースであれば、 同じOperation IDを自動で付与してくれます。
今回、serverless-ml-linebotが発火すると、ServiceBusにキューを詰め込み、serverless-yoloがそれをトリガーに発火します。 この一連の処理に同一のOperation IDが付与され、Application Insightsが自動で依存関係を収集してくれます。
Applicatoin Mapでは、この様に表示されます。
5. Azure SDK for Pythonでは、依存関係の収集に対応していない?
Azure Functionsから、StorageCosmosDBにデータを入出力する別の方法として、 Azure SDK for Pythonがあります。
ただ、Azure SDK for Pythonを利用した方法だと、トレースはしてくれるものの、 依存関係を作っては入れない様です。
したがって、タイムラインには親子関係として表示されない上に、 Application Mapにもマップできないことになります。
なぜできないのかを解明するために、Azure-SDK-For-Pythonのソースコードを読んでみると、 distributed_traceというデコレータが実装されていることが分かりました。GitHub
原因の解明とはいきませんでしたが、今後対応される見込みはたかそうです(?)
6. Application Insights SDK for Pythonは、OpenCensusに統合される
Application InsightsのPython向けSDKは、今はOpencensusに統合されているようです。 Microsoft joins the OpenCensus project
OpenCensusとは?
OpenCensusは、アプリケーションにObservabilityを提供するライブラリで、 以下の3つの機能を持っています 1. リクエストのトレースと、時系列メトリクスを収集する 2. 収集した結果を可視化する 3. 収集した結果を別の分析用アプリケーションに送出する。
また、Azureとして分散トレースの標準をW3Cが出している、Trace Contextに移行している最中だそうです。 これにより、旧来Application Insightsが、分散トレースのために使っていたHTTP Headerなどは置き換わって行くことになるそうです。
今回は、serverless-yoloから、flask-yoloに対するリクエストを投げる部分を、OpenCensusを利用して監視しました。
APIを叩く側の実装は、リクエストヘッダに traceparent という値を追加するだけです。 traceparentの詳細な仕様は、こちらのドキュメント(日本語訳)にあります。
値としては、 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01 というものになります。 形式は、 00-{trace_id}-{span_id}-{flag} です。
headers = {'traceparent': traceparent} requests.get(url='https://yolo-flask/predict', headers=headers)
リクエストを受ける側のFlaskは、opencensus-ext-flaskというモジュールを利用します。 実装はほぼサンプル通りです。
import logging import json from flask import Flask, jsonify, request from opencensus.ext.azure.trace_exporter import AzureExporter from opencensus.ext.flask.flask_middleware import FlaskMiddleware from opencensus.trace.samplers import ProbabilitySampler from opencensus.trace.tracer import Tracer app = Flask(__name__) # Application Insightsの接続文字列 APPLICATION_INSIGHTS_CONNECTION_STRING = 'InstrumentationKey=XXXXXXX' exporter = AzureExporter(connection_string=APPLICATION_INSIGHTS_CONNECTION_STRING) sampler = ProbabilitySampler(rate=1.0) middleware = FlaskMiddleware(app, exporter=exporter, sampler=sampler) tracer = Tracer( exporter=AzureExporter(connection_string=APPLICATION_INSIGHTS_CONNECTION_STRING), sampler=ProbabilitySampler(1.0) ) @app.route('/yolo-flask/predict', methods=['GET']) def post(): msg = 'ok' return jsonify({'res': msg}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
7. Pythonでは、Azure FunctionsのOperation Idを取得できない
これはかなり詰まったところです。 2番でも触れた様に、アプリケーション間の親子関係を作るためには、trace_idと、span_idというものが必要になります。
Azure Functionsから、別のAPIを叩きたいようなケースの場合、 Operation Idを取得できないという問題がありました。
これはつまり、trace_idで関連付けできないことを意味します。 今回のシステムで言えば、serverless-yoloから、flask-yoloのAPIを叩きたいようなケースです。 6番のケースですね。
半ば強引に実装し、trace_idで関連付けはできたものの、、うまく親子関係にすることができません。
Application Mapも、依存関係をマッピングできていません。
今後のアップデートで、うまく扱えるようになってくれると嬉しい部分ですね。
最後に
AWSならX-Ray GCPならStackdriverのように、各クラウドごとに分散トレーシングの事例も情報も多かったのですが、 Azureについては少なかったように感じていました。実際にやってみて、思う様にいかないケースもあり、大変でした。
今後、アップデートを追いかけたりして、Azureでのマイクロサービス・分散トレーシングを頑張っていきたいです!