面白駆動人生

やっほー

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(※後述)

構成は以下の通りです。

スクリーンショット 2019-12-01 22.44.19.png

可視化したい箇所は。図中の緑の線です。 このシステムを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のリソース画面に移動し、「検索」を押します。

スクリーンショット 2019-12-01 22.52.21.png

トレースを選び、クリックすると以下のように表示されます。 スクリーンショット 2019-12-01 23.58.27.png

タイムラインを表示すると、こうなります。 右上の操作IDが、OperaitionIDになります。

スクリーンショット 2019-12-01 16.48.03.png

呼び出しの親子関係がわかり、それぞれの実行時間を可視化することができました。

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が自動で依存関係を収集してくれます。

スクリーンショット 2019-12-01 16.51.38.png

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で関連付けはできたものの、、うまく親子関係にすることができません。 スクリーンショット 2019-12-01 23.49.02.png

Application Mapも、依存関係をマッピングできていません。 スクリーンショット 2019-12-01 23.50.26.png

今後のアップデートで、うまく扱えるようになってくれると嬉しい部分ですね。

最後に

AWSならX-Ray GCPならStackdriverのように、各クラウドごとに分散トレーシングの事例も情報も多かったのですが、 Azureについては少なかったように感じていました。実際にやってみて、思う様にいかないケースもあり、大変でした。

今後、アップデートを追いかけたりして、Azureでのマイクロサービス・分散トレーシングを頑張っていきたいです!