次の方法で共有


チュートリアル: OpenTelemetry 分散トレースを使用して Azure Functions を監視する

この記事では、統合された Application Insights と OpenTelemetry のサポートを使用して、複数の関数呼び出し間で分散トレースを有効にする Azure Function での OpenTelemetry のサポートについて説明します。 作業を開始するために、Azure Developer CLI (azd) テンプレートを使用して、コード プロジェクトと、アプリを実行する Azure デプロイを作成します。

このチュートリアルでは、 azd ツールを使用して次の操作を行います。

  • テンプレートから OpenTelemetry 対応プロジェクトを初期化します。
  • OpenTelemetry 統合を有効にするコードを確認します。
  • OpenTelemetry 対応アプリをローカルで実行して確認します。
  • Azure で関数アプリと関連リソースを作成します。
  • コード プロジェクトを Azure の関数アプリにデプロイします。
  • Application Insights で分散トレースを確認します。

このテンプレートによって作成される必要な Azure リソースは、Azure でのセキュリティで保護されたスケーラブルな関数アプリのデプロイに関する最新のベスト プラクティスに従います。 同じ azd コマンドによって、コード プロジェクトも Azure の新しい関数アプリにデプロイされます。

既定では、Flex 従量課金プランは 従量課金 制の課金モデルに従います。つまり、このクイック スタートを完了すると、Azure アカウントで数 USD セント以下の小さなコストが発生します。

Important

この記事では現在、C#、Python、TypeScript のみがサポートされています。 クイック スタートを完了するには、記事の上部にあるサポートされているこれらの言語のいずれかを選択します。

[前提条件]

プロジェクトを初期化する

azd init コマンドを使用して、OpenTelemetry 分散トレースを含むテンプレートからローカル Azure Functions コード プロジェクトを作成します。

  1. ローカル ターミナルまたはコマンド プロンプトで、空のフォルダーで次の azd init コマンドを実行します。

    azd init --template functions-quickstart-python-azd-otel -e flexquickstart-otel
    

    このコマンドにより、テンプレート リポジトリからプロジェクト ファイルがプルされ、現在のフォルダー内のプロジェクトが初期化されます。 -e フラグでは、現在の環境の名前が設定されます。 azdでは、環境はアプリの一意のデプロイ コンテキストを維持し、複数を定義できます。 環境名は、Azure で作成したリソース グループの名前にも表示されます。

  1. ローカル ターミナルまたはコマンド プロンプトで、空のフォルダーで次の azd init コマンドを実行します。

    azd init --template functions-quickstart-typescript-azd-otel -e flexquickstart-otel
    

    このコマンドにより、テンプレート リポジトリからプロジェクト ファイルがプルされ、現在のフォルダー内のプロジェクトが初期化されます。 -e フラグでは、現在の環境の名前が設定されます。 azdでは、環境はアプリの一意のデプロイ コンテキストを維持し、複数を定義できます。 環境名は、Azure で作成したリソース グループの名前にも表示されます。

  1. ローカル ターミナルまたはコマンド プロンプトで、空のフォルダーで次の azd init コマンドを実行します。

    azd init --template functions-quickstart-dotnet-azd-otel -e flexquickstart-otel
    

    このコマンドにより、テンプレート リポジトリからプロジェクト ファイルがプルされ、現在のフォルダー内のプロジェクトが初期化されます。 -e フラグでは、現在の環境の名前が設定されます。 azdでは、環境はアプリの一意のデプロイ コンテキストを維持し、複数を定義できます。 環境名は、Azure で作成したリソース グループの名前にも表示されます。

コードの確認

このテンプレートは、連携して動作する 3 つの関数を使用して、完全な分散トレース シナリオを作成します。 OpenTelemetry 関連の重要な側面を確認しましょう。

OpenTelemetry の構成

src/otel-sample/host.json ファイルを使用すると、Functions ホストの OpenTelemetry が有効になります。

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "extensions": {
    "serviceBus": {
        "maxConcurrentCalls": 10
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

キー設定 "telemetryMode": "OpenTelemetry" により、関数呼び出し間での分散トレースが有効になります。

src/OTelSample/host.json ファイルを使用すると、Functions ホストの OpenTelemetry が有効になります。

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "logging": {
    "OpenTelemetry": {
      "logLevel": {
        "Host.General": "Warning"
      }
    }
  }
}

キー設定 "telemetryMode": "OpenTelemetry" により、関数呼び出し間での分散トレースが有効になります。

OpenTelemetry の依存関係

src/otel-sample/requirements.txt ファイルには、OpenTelemetry 統合に必要なパッケージが含まれています。

azure-functions
azure-monitor-opentelemetry
requests

azure-monitor-opentelemetry パッケージは、OpenTelemetry と Application Insights の統合を提供します。

src/otel-sample/package.json ファイルには、OpenTelemetry 統合に必要なパッケージが含まれています。

{
  "dependencies": {
    "@azure/functions": "^4.0.0",
    "@azure/functions-opentelemetry-instrumentation": "^0.1.0",
    "@azure/monitor-opentelemetry-exporter": "^1.0.0",
    "axios": "^1.6.0"
  }
}

@azure/functions-opentelemetry-instrumentationパッケージと@azure/monitor-opentelemetry-exporter パッケージは、Application Insights と OpenTelemetry の統合を提供します。

.csproj ファイルには、OpenTelemetry 統合に必要なパッケージが含まれています。

<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.OpenTelemetry" Version="1.4.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />

これらのパッケージは、分散トレース用の Application Insights および HTTP インストルメンテーションとの OpenTelemetry 統合を提供します。

関数の実装

src/otel-sample/function_app.pyの関数は、分散トレース フローを示しています。

最初の HTTP 関数

@app.function_name("first_http_function")
@app.route(route="first_http_function", auth_level=func.AuthLevel.ANONYMOUS)
def first_http_function(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (first) processed a request.')

    # Call the second function
    base_url = f"{req.url.split('/api/')[0]}/api"
    second_function_url = f"{base_url}/second_http_function"

    response = requests.get(second_function_url)
    second_function_result = response.text

    result = {
        "message": "Hello from the first function!",
        "second_function_response": second_function_result
    }

    return func.HttpResponse(
        json.dumps(result),
        status_code=200,
        mimetype="application/json"
    )

2 番目の HTTP 関数

@app.function_name("second_http_function")
@app.route(route="second_http_function", auth_level=func.AuthLevel.ANONYMOUS)
@app.service_bus_queue_output(arg_name="outputsbmsg", queue_name="%ServiceBusQueueName%",
                              connection="ServiceBusConnection")
def second_http_function(req: func.HttpRequest, outputsbmsg: func.Out[str]) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (second) processed a request.')

    message = "This is the second function responding."

    # Send a message to the Service Bus queue
    queue_message = "Message from second HTTP function to trigger ServiceBus queue processing"
    outputsbmsg.set(queue_message)
    logging.info('Sent message to ServiceBus queue: %s', queue_message)

    return func.HttpResponse(
        message,
        status_code=200
    )

Service Bus キューのトリガー

@app.service_bus_queue_trigger(arg_name="azservicebus", queue_name="%ServiceBusQueueName%",
                               connection="ServiceBusConnection") 
def servicebus_queue_trigger(azservicebus: func.ServiceBusMessage):
    logging.info('Python ServiceBus Queue trigger start processing a message: %s',
                azservicebus.get_body().decode('utf-8'))
    time.sleep(5)  # Simulate processing work
    logging.info('Python ServiceBus Queue trigger end processing a message')

OpenTelemetry の構成は、 src/otel-sample/index.tsで設定されます。

import { AzureFunctionsInstrumentation } from '@azure/functions-opentelemetry-instrumentation';
import { AzureMonitorTraceExporter, AzureMonitorLogExporter } from '@azure/monitor-opentelemetry-exporter';
import { getNodeAutoInstrumentations, getResourceDetectors } from '@opentelemetry/auto-instrumentations-node';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { detectResources } from '@opentelemetry/resources';
import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';

const resource = detectResources({ detectors: getResourceDetectors() });

const tracerProvider = new NodeTracerProvider({ 
  resource, 
  spanProcessors: [new SimpleSpanProcessor(new AzureMonitorTraceExporter())] 
});
tracerProvider.register();

const loggerProvider = new LoggerProvider({
  resource,
  processors: [new SimpleLogRecordProcessor(new AzureMonitorLogExporter())],
});

registerInstrumentations({
    tracerProvider,
    loggerProvider,
    instrumentations: [getNodeAutoInstrumentations(), new AzureFunctionsInstrumentation()],
});

関数は、 src/otel-sample/src/functions フォルダーに定義されています。

最初の HTTP 関数

export async function firstHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (first) processed a request.");

  try {
    // Call the second function
    const baseUrl = request.url.split("/api/")[0];
    const secondFunctionUrl = `${baseUrl}/api/second_http_function`;

    const response = await axios.get(secondFunctionUrl);
    const secondFunctionResult = response.data;

    const result = {
      message: "Hello from the first function!",
      second_function_response: secondFunctionResult,
    };

    return {
      status: 200,
      body: JSON.stringify(result),
      headers: { "Content-Type": "application/json" },
    };
  } catch (error) {
    return {
      status: 500,
      body: JSON.stringify({ error: "Failed to process request" }),
    };
  }
}

2 番目の HTTP 関数

export async function secondHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (second) processed a request.");

  const message = "This is the second function responding.";

  // Send a message to the Service Bus queue
  const queueMessage =
    "Message from second HTTP function to trigger ServiceBus queue processing";

  context.extraOutputs.set(serviceBusOutput, queueMessage);
  context.log("Sent message to ServiceBus queue:", queueMessage);

  return {
    status: 200,
    body: message,
  };
}

Service Bus キュー トリガー

export async function serviceBusQueueTrigger(
  message: unknown,
  context: InvocationContext
): Promise<void> {
  context.log("TypeScript ServiceBus Queue trigger start processing a message:", message);

  // Simulate processing time
  await new Promise((resolve) => setTimeout(resolve, 5000));

  context.log("TypeScript ServiceBus Queue trigger end processing a message");
}

OpenTelemetry の構成は、 src/OTelSample/Program.csで設定されます。

using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
using OpenTelemetry.Trace;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()    
    .WithTracing(tracing =>
    {
        tracing.AddHttpClientInstrumentation();
    });

builder.Services.AddOpenTelemetry().UseAzureMonitorExporter();
builder.Services.AddOpenTelemetry().UseFunctionsWorkerDefaults();

builder.Services.AddHttpClient();

builder.Build().Run();

関数は、個別のクラス ファイルで定義されます。

最初の HTTP 関数

public class FirstHttpTrigger
{
    private readonly ILogger<FirstHttpTrigger> _logger;
    private readonly IHttpClientFactory _httpClientFactory;

    public FirstHttpTrigger(ILogger<FirstHttpTrigger> logger, IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
    }

    [Function("first_http_function")]
    public async Task<IActionResult> Run(
         [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("first_http_function function processed a request.");

        var baseUrl = $"{req.Url.AbsoluteUri.Split("/api/")[0]}/api";
        var targetUri = $"{baseUrl}/second_http_function";

        var client = _httpClientFactory.CreateClient();
        var response = await client.GetAsync(targetUri);
        var content = await response.Content.ReadAsStringAsync();

        return new OkObjectResult($"Called second_http_function, status: {response.StatusCode}, content: {content}");
    }
}

2 番目の HTTP 関数

public class SecondHttpTrigger
{
    private readonly ILogger<SecondHttpTrigger> _logger;

    public SecondHttpTrigger(ILogger<SecondHttpTrigger> logger)
    {
        _logger = logger;
    }

    [Function("second_http_function")]
    public MultiResponse Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("second_http_function function processed a request.");

        return new MultiResponse
        {
            Messages = new string[] { "Hello" },
            HttpResponse = req.CreateResponse(System.Net.HttpStatusCode.OK)
        };
    }
}

public class MultiResponse
{
    [ServiceBusOutput("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
    public string[]? Messages { get; set; }

    [HttpResult]
    public HttpResponseData? HttpResponse { get; set; }
}

Service Bus キュー トリガー

public class ServiceBusQueueTrigger
{
    private readonly ILogger<ServiceBusQueueTrigger> _logger;

    public ServiceBusQueueTrigger(ILogger<ServiceBusQueueTrigger> logger)
    {
        _logger = logger;
    }

    [Function("servicebus_queue_trigger")]
    public async Task Run(
        [ServiceBusTrigger("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions)
    {
        _logger.LogInformation("Message ID: {id}", message.MessageId);
        _logger.LogInformation("Message Body: {body}", message.Body);

        // Complete the message
        await messageActions.CompleteMessageAsync(message);
    }
}

分散トレース フロー

このアーキテクチャでは、次の動作を使用して、完全な分散トレース シナリオが作成されます。

  1. 最初の HTTP 関数 は HTTP 要求を受け取り、2 番目の HTTP 関数を呼び出します
  2. 2 番目の HTTP 関数 が応答し、Service Bus にメッセージを送信する
  3. Service Bus トリガー は、処理作業をシミュレートするために遅延でメッセージを処理します

OpenTelemetry 実装の主な側面:

  • OpenTelemetry 統合: host.json ファイルを使って、"telemetryMode": "OpenTelemetry" で OpenTelemetry を有効にします。
  • 関数チェーン: 最初の関数は HTTP 要求を使用して 2 つ目を呼び出し、相関トレースを作成します
  • Service Bus 統合: 2 番目の関数が Service Bus に出力され、3 番目の関数がトリガーされます
  • 匿名認証: HTTP 関数は auth_level=func.AuthLevel.ANONYMOUSを使用するため、関数キーは必要ありません

完全なテンプレート プロジェクトをこちらで確認できます。

  • OpenTelemetry 統合: index.ts ファイルは、トレースとログ用に Azure Monitor エクスポーターを使用して OpenTelemetry を構成します
  • 関数チェーン: 最初の関数は、自動トレース伝達で axios を使用して 2 番目の関数を呼び出します
  • Service Bus 統合: 2 番目の関数は、出力バインドを使用して Service Bus に出力され、3 番目の関数がトリガーされます
  • マネージド ID: すべての Service Bus 接続で接続文字列の代わりにマネージド ID が使用されます
  • 処理シミュレーション: Service Bus トリガーの 5 秒の遅延により、メッセージ処理の処理がシミュレートされます

完全なテンプレート プロジェクトをこちらで確認できます。

  • OpenTelemetry 統合: Program.cs ファイルは、Azure Monitor エクスポーターを使用して OpenTelemetry を構成します
  • 関数チェーン: 最初の関数は、OpenTelemetry インストルメンテーションで HttpClient を使用して 2 つ目を呼び出します
  • Service Bus 統合: 2 番目の関数は、出力バインドを使用して Service Bus に出力され、3 番目の関数がトリガーされます
  • マネージド ID: すべての Service Bus 接続で接続文字列の代わりにマネージド ID が使用されます
  • .NET 8 分離ワーカー: パフォーマンスと柔軟性を向上させるために、最新の Azure Functions .NET Isolated Worker モデルを使用します

完全なテンプレート プロジェクトをこちらで確認できます。

関数をローカルで検証したら、Azure に発行します。

Azure にデプロイ

このプロジェクトは、 azd up コマンドを使用して、OpenTelemetry をサポートする Azure の Flex Consumption プランの新しい関数アプリにこのプロジェクトをデプロイするように構成されています。

ヒント

このプロジェクトには、Bicep ファイルのセットが含まれており、それを使用して、マネージド ID 接続を含むベスト プラクティスに従い、Flex 従量課金プランへの安全なデプロイを作成します。

  1. 次のコマンドを実行し、azd で必要な Azure リソースを Azure に作成し、コード プロジェクトを新しい関数アプリにデプロイします。

    azd up
    

    ルート フォルダーには、azd に必要な azure.yaml 定義ファイルが含まれています。

    まだサインインしていない場合は、Azure アカウントで認証するように求められます。

  2. プロンプトが表示されたら、次の必須のデプロイ パラメーターを指定します。

    パラメーター Description
    Azure サブスクリプション リソースが作成されるサブスクリプション。
    Azure の場所 新しい Azure リソースを含むリソース グループを作成する Azure リージョン。 現在、Flex 従量課金プランをサポートしているリージョンのみが表示されます。

    azd up コマンドでは、Bicep 構成ファイルでこれらのプロンプトに対する応答を使用して、次のデプロイ タスクを完了します。

    • 次の必要な Azure リソースを作成して構成します (azd provision と同等)。

      • OpenTelemetry が有効になっている Azure Functions Flex 従量課金プランと関数アプリ
      • Azure Storage (必須) と Application Insights (推奨)
      • 分散トレースのデモンストレーション用の Service Bus 名前空間とキュー
      • アカウントのアクセス ポリシーとロール
      • マネージド ID を使用したサービス間接続 (格納されている接続文字列の代わり)
    • コードをパッケージ化し、デプロイ コンテナーにデプロイします (azd deploy と同等)。 その後、アプリが起動し、デプロイされたパッケージで実行されます。

    コマンドが正常に完了した後、作成したリソースへのリンクが表示されます。

分散トレースをテストする

デプロイされた関数を呼び出し、Application Insights でテレメトリを監視することで、OpenTelemetry 分散トレース機能をテストできるようになりました。

Azure 上の関数を呼び出す

AZURE で関数エンドポイントを呼び出すには、URL に対して HTTP 要求を行います。 このテンプレートの HTTP 関数は匿名アクセスで構成されているため、関数キーは必要ありません。

  1. ローカル ターミナルまたはコマンド プロンプトで、次のコマンドを実行して関数アプリ名を取得し、URL を作成します。

    APP_NAME=$(azd env get-value AZURE_FUNCTION_NAME)
    echo "Function URL: https://$APP_NAME.azurewebsites.net/api/first_http_function"
    

    azd env get-value コマンドでは、ローカル環境から関数アプリ名が取得されます。

  2. URL に移動して、ブラウザーで関数をテストします。

    https://your-function-app.azurewebsites.net/api/first_http_function
    

    your-function-appを前の手順の実際の関数アプリ名に置き換えます。 この 1 つの要求により、3 つの関数すべてを通過する分散トレースが作成されます。

Application Insights で分散トレースを表示する

関数を呼び出した後、Application Insights で完全な分散トレースを確認できます。

関数を呼び出した後、テレメトリ データが Application Insights に表示されるまでに数分かかる場合があります。 データがすぐに表示されない場合は、数分待ってからビューを更新します。

  1. Azure portal で Application Insights リソースに移動します (関数アプリと同じリソース グループで見つけることができます)。

  2. アプリケーション マップを開き、3 つの関数すべてに分散トレースを表示します。 HTTP 要求から関数および Service Bus へのフローが表示されます。

  3. トランザクション検索を確認して要求を見つけ、完全なトレース タイムラインを確認します。 関数アプリからトランザクションを検索します。

  4. 特定のトランザクションを選択して、次を示すエンドツーエンドのトレースを表示します。

    • first_http_function へのHTTP 要求
    • second_http_function への内部 HTTP 呼び出し
    • 送信される Service Bus メッセージ
    • Service Bus からのメッセージを処理するservicebus_queue_trigger
  5. トレースの詳細には、次の情報が表示されます。

    • タイミング情報: 各ステップの所要時間
    • 依存関係: 関数間の接続
    • ログ: トレースと関連付けられたアプリケーション ログ
    • パフォーマンス メトリック: 応答時間とスループット

この例では、OpenTelemetry 統合を使用して複数の Azure Functions に対するエンドツーエンドの分散トレースを示し、アプリケーションの動作とパフォーマンスを完全に可視化します。

コードを再デプロイする

azd up コマンドを必要な回数実行して、Azure リソースをプロビジョニングし、コード更新プログラムを関数アプリにデプロイします。

最新のデプロイ パッケージは、常にデプロイされたコード ファイルを上書きします。

azd プロンプトに対する最初の応答と、azd によって生成された環境変数は、名前付き環境にローカルに格納されます。 azd env get-values コマンドを使用して、Azure リソースの作成時にコマンドが使用する環境内のすべての変数を確認します。

リソースをクリーンアップする

関数アプリと関連リソースの操作が完了したら、次のコマンドを使用して関数アプリとその関連リソースを Azure から削除し、それ以上のコストが発生しないようにします。

azd down --no-prompt

--no-prompt オプションは、確認なしでリソース グループを削除するように azd に指示します。

このコマンドは、ローカル コード プロジェクトには影響しません。