Partager via


Tutoriel : Surveiller Azure Functions avec le suivi distribué OpenTelemetry

Cet article illustre la prise en charge d’OpenTelemetry dans Azure Function, qui permet le suivi distribué sur plusieurs appels de fonction à l’aide de la prise en charge intégrée d’Application Insights et d’OpenTelemetry. Pour vous aider à commencer, un modèle Azure Developer CLI (azd) est utilisé pour créer votre projet de code ainsi que le déploiement Azure dans lequel exécuter votre application.

Dans ce tutoriel, vous utilisez l’outil azd pour :

  • Initialisez un projet OpenTelemetry à l'aide d'un modèle.
  • Passez en revue le code qui active l’intégration d’OpenTelemetry.
  • Exécutez et vérifiez votre application OpenTelemetry localement.
  • Créez une application de fonction et des ressources associées dans Azure.
  • Déployez votre projet de code sur l’application de fonction dans Azure.
  • Vérifiez le suivi distribué dans Application Insights.

Les ressources Azure requises créées par ce modèle suivent les meilleures pratiques actuelles pour les déploiements d’applications de fonction sécurisées et évolutives dans Azure. La même azd commande déploie également votre projet de code sur votre nouvelle application de fonction dans Azure.

Par défaut, le plan Flex Consumption suit un modèle de facturation avec paiement à l’utilisation , ce qui signifie que l’exécution de ce guide de démarrage rapide entraîne un petit coût de quelques cents USD ou moins dans votre compte Azure.

Important

Cet article prend actuellement en charge uniquement C#, Python et TypeScript. Pour suivre le guide de démarrage rapide, sélectionnez l’une de ces langues prises en charge en haut de l’article.

Prerequisites

Initialiser le projet

Utilisez la azd init commande pour créer un projet de code Azure Functions local à partir d’un modèle qui inclut le suivi distribué OpenTelemetry.

  1. Dans votre terminal local ou à l’invite de commandes, exécutez cette commande azd init dans un dossier vide :

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

    Cette commande extrait les fichiers projet du référentiel de modèles et initialise le projet dans le dossier actif. L’indicateur -e définit un nom pour l’environnement actuel. Dans azd, l’environnement gère un contexte de déploiement unique pour votre application et vous pouvez en définir plusieurs. Le nom de l’environnement apparaît également dans le nom du groupe de ressources que vous créez dans Azure.

  1. Dans votre terminal local ou à l’invite de commandes, exécutez cette commande azd init dans un dossier vide :

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

    Cette commande extrait les fichiers projet du référentiel de modèles et initialise le projet dans le dossier actif. L’indicateur -e définit un nom pour l’environnement actuel. Dans azd, l’environnement gère un contexte de déploiement unique pour votre application et vous pouvez en définir plusieurs. Le nom de l’environnement apparaît également dans le nom du groupe de ressources que vous créez dans Azure.

  1. Dans votre terminal local ou à l’invite de commandes, exécutez cette commande azd init dans un dossier vide :

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

    Cette commande extrait les fichiers projet du référentiel de modèles et initialise le projet dans le dossier actif. L’indicateur -e définit un nom pour l’environnement actuel. Dans azd, l’environnement gère un contexte de déploiement unique pour votre application et vous pouvez en définir plusieurs. Le nom de l’environnement apparaît également dans le nom du groupe de ressources que vous créez dans Azure.

Vérifier le code

Le modèle crée un scénario de suivi distribué complet avec trois fonctions qui fonctionnent ensemble. Examinons les aspects clés liés à OpenTelemetry :

Configuration d’OpenTelemetry

Le src/otel-sample/host.json fichier active OpenTelemetry pour l’hôte Functions :

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

Le paramètre "telemetryMode": "OpenTelemetry" de clé active le suivi distribué entre les appels de fonction.

Le src/OTelSample/host.json fichier active OpenTelemetry pour l’hôte Functions :

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

Le paramètre "telemetryMode": "OpenTelemetry" de clé active le suivi distribué entre les appels de fonction.

Dépendances pour OpenTelemetry

Le src/otel-sample/requirements.txt fichier inclut les packages nécessaires pour l’intégration d’OpenTelemetry :

azure-functions
azure-monitor-opentelemetry
requests

Le azure-monitor-opentelemetry package fournit l’intégration d’OpenTelemetry à Application Insights.

Le src/otel-sample/package.json fichier inclut les packages nécessaires pour l’intégration d’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"
  }
}

Les packages @azure/functions-opentelemetry-instrumentation et @azure/monitor-opentelemetry-exporter fournissent l'intégration d'OpenTelemetry avec Application Insights.

Le .csproj fichier inclut les packages nécessaires pour l’intégration d’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" />

Ces packages fournissent l’intégration d’OpenTelemetry à Application Insights et à l’instrumentation HTTP pour le suivi distribué.

Implémentation de fonction

Les fonctions dans src/otel-sample/function_app.py démontrent un flux de traçage distribué :

Première fonction 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"
    )

Deuxième fonction 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
    )

Déclencheur de file d'attente du 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')

La configuration OpenTelemetry est configurée dans 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()],
});

Les fonctions sont définies dans le src/otel-sample/src/functions dossier :

Première fonction 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" }),
    };
  }
}

Deuxième fonction 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,
  };
}

Déclencheur de file d'attente du 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");
}

La configuration OpenTelemetry est configurée dans 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();

Les fonctions sont définies dans des fichiers de classe distincts :

Première fonction 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}");
    }
}

Deuxième fonction 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; }
}

Déclencheur de file d'attente du 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);
    }
}

Flux de suivi distribué

Cette architecture crée un scénario de suivi distribué complet, avec ce comportement :

  1. La première fonction HTTP reçoit une requête HTTP et appelle la deuxième fonction HTTP
  2. La deuxième fonction HTTP répond et envoie un message à Service Bus
  3. Le déclencheur Service Bus traite le message avec un délai pour simuler le travail de traitement

Aspects clés de l’implémentation d’OpenTelemetry :

  • Intégration d’OpenTelemetry : le host.json fichier active OpenTelemetry avec "telemetryMode": "OpenTelemetry"
  • Chaînage de fonctions : la première fonction appelle la seconde à l’aide de requêtes HTTP, créant des traces corrélées
  • Intégration de Service Bus : la deuxième fonction est sortie vers Service Bus, ce qui déclenche la troisième fonction
  • Authentification anonyme : les fonctions HTTP utilisent auth_level=func.AuthLevel.ANONYMOUS, donc aucune clé de fonction n’est requise

Vous pouvez consulter le projet de modèle complet ici.

  • Intégration d'OpenTelemetry : Ce fichier index.ts configure OpenTelemetry avec les exportateurs Azure Monitor pour les traces et les journaux.
  • Chaînage de fonctions : la première fonction appelle la seconde à l’aide d’axios avec propagation de trace automatique
  • Intégration de Service Bus : la deuxième fonction génère des sorties vers Service Bus à l’aide de liaisons de sortie, ce qui déclenche la troisième fonction
  • Identité managée : toutes les connexions Service Bus utilisent une identité managée au lieu de chaînes de connexion
  • Simulation de traitement : le délai de 5 secondes dans le déclencheur Service Bus simule le travail de traitement des messages

Vous pouvez consulter le projet de modèle complet ici.

  • Intégration d’OpenTelemetry : le Program.cs fichier configure OpenTelemetry avec l’exportateur Azure Monitor
  • Chaînage de fonctions : la première fonction appelle la seconde à l’aide de HttpClient avec l’instrumentation OpenTelemetry
  • Intégration de Service Bus : la deuxième fonction génère des sorties vers Service Bus à l’aide de liaisons de sortie, ce qui déclenche la troisième fonction
  • Identité managée : toutes les connexions Service Bus utilisent une identité managée au lieu de chaînes de connexion
  • .NET 8 Worker isolé : Utilise le modèle de worker isolé .NET le plus récent d'Azure Functions pour des performances et une flexibilité accrues.

Vous pouvez consulter le projet de modèle complet ici.

Après avoir vérifié vos fonctions en local, il est temps de les publier sur Azure.

Déployer sur Azure

Ce projet est configuré pour utiliser la commande azd up afin de déployer ce projet sur une nouvelle application de fonctions dans un plan de consommation Flex sur Azure avec le support d’OpenTelemetry.

Conseil / Astuce

Ce projet comprend un ensemble de fichiers Bicep que azd utilisés pour créer un déploiement sécurisé vers un plan de consommation Flex qui suit les meilleures pratiques, notamment les connexions d'identité gérées.

  1. Exécutez cette commande pour qu’azd crée les ressources Azure requises dans Azure et déployer votre projet de code dans la nouvelle application de fonction :

    azd up
    

    Le dossier racine contient le fichier de définition azure.yaml exigé par azd.

    Si vous n’êtes pas déjà connecté, vous êtes invité à vous authentifier auprès de votre compte Azure.

  2. Lorsque vous y êtes invité, fournissez ces paramètres de déploiement requis :

    Paramètre Descriptif
    Abonnement Azure Abonnement dans lequel vos ressources sont créées.
    Emplacement Azure Région Azure dans laquelle créer le groupe de ressources qui contient les nouvelles ressources Azure. Seules sont montrées les régions qui prennent actuellement en charge le plan Consommation flexible.

    La commande azd up utilise votre réponse à ces invites avec les fichiers de configuration Bicep pour effectuer ces tâches de déploiement :

    • Créez et configurez ces ressources Azure requises (équivalentes à azd provision) :

      • Plan de Consommation Flex d'Azure Functions et application fonctionnelle avec OpenTelemetry activé
      • Stockage Azure (obligatoire) et Application Insights (recommandé)
      • Espace de noms et file d’attente Service Bus pour la démonstration de suivi distribué
      • Stratégies d’accès et rôles pour votre compte
      • Connexions de service à service avec des identités managées (au lieu de chaînes de connexion stockées)
    • Empaquetez et déployez votre code sur le conteneur de déploiement (équivalent à azd deploy). L’application est ensuite démarrée et s’exécute dans le package déployé.

    Une fois la commande terminée, vous voyez des liens vers les ressources que vous avez créées.

Effectuer des tests de suivi distribué

Vous pouvez maintenant tester la fonctionnalité de suivi distribué OpenTelemetry en appelant vos fonctions déployées et en observant les données de télémétrie dans Application Insights.

Appeler la fonction sur Azure

Vous pouvez appeler vos points de terminaison de fonction dans Azure en effectuant des requêtes HTTP à leurs URL. Étant donné que les fonctions HTTP de ce modèle sont configurées avec un accès anonyme, aucune clé de fonction n’est requise.

  1. Dans votre terminal local ou invite de commandes, exécutez cette commande pour obtenir le nom de l’application de fonction et construire l’URL :

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

    La commande azd env get-value obtient le nom de votre application de fonction à partir de l’environnement local.

  2. Testez la fonction dans votre navigateur en accédant à l’URL :

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

    Remplacez your-function-app par le nom de votre application de fonction réelle à l’étape précédente. Cette requête unique crée une trace distribuée qui transite par les trois fonctions.

Afficher le suivi distribué dans Application Insights

Après avoir appelant la fonction, vous pouvez observer la trace distribuée complète dans Application Insights :

Note

Il peut s'écouler quelques minutes avant que les données de télémétrie apparaissent dans Application Insights après l'invocation de votre fonction. Si vous ne voyez pas les données immédiatement, attendez quelques minutes et actualisez la vue.

  1. Accédez à votre ressource Application Insights dans le portail Azure (vous pouvez la trouver dans le même groupe de ressources que votre application de fonction).

  2. Ouvrez la carte d’application pour afficher la trace distribuée sur les trois fonctions. Vous devez voir le flux de la requête HTTP via vos fonctions et vers Service Bus.

  3. Vérifiez la recherche de transactions pour rechercher votre demande et consultez la chronologie complète des traces. Recherchez des transactions à partir de votre application de fonction.

  4. Sélectionnez une transaction spécifique pour afficher la trace de bout en bout qui montre :

    • Requête HTTP vers first_http_function
    • Appel HTTP interne à second_http_function
    • Message de Service Bus en cours d'envoi
    • Le traitement du message servicebus_queue_trigger provenant d’Azure Service Bus
  5. Dans les détails de la trace, vous pouvez voir :

    • Informations sur le temps : durée de chaque étape
    • Dépendances : connexions entre les fonctions
    • Journaux de l'application corrélés avec la trace
    • Métriques de performances : temps de réponse et débit

Cet exemple illustre le suivi distribué de bout en bout sur plusieurs fonctions Azure avec l’intégration d’OpenTelemetry, ce qui offre une visibilité complète sur le comportement et les performances de votre application.

Redéployer votre code

Exécutez la azd up commande autant de fois que vous devez provisionner vos ressources Azure et déployer des mises à jour de code sur votre application de fonction.

Note

Le dernier package de déploiement remplace toujours les fichiers de code déployés.

Vos réponses initiales aux invites d’azd et toutes les variables d’environnement générées par azd sont stockées localement dans votre environnement nommé. Utilisez la azd env get-values commande pour passer en revue toutes les variables de votre environnement que la commande utilise lors de la création de ressources Azure.

Nettoyer les ressources

Lorsque vous avez terminé d’utiliser votre application de fonction et vos ressources associées, utilisez cette commande pour supprimer l’application de fonction et ses ressources associées d’Azure et éviter d’entraîner d’autres coûts :

azd down --no-prompt

Note

L’option --no-prompt indique à azd de supprimer votre groupe de ressources sans confirmation de votre part.

Cette commande n’affecte pas votre projet de code local.