Compartir a través de


Tutorial: Supervisión de Azure Functions con seguimiento distribuido de OpenTelemetry

En este artículo se muestra la compatibilidad con OpenTelemetry en Azure Function, que permite el seguimiento distribuido en varias llamadas de función mediante la compatibilidad integrada con Application Insights y OpenTelemetry. Para ayudarle a empezar, se usa una plantilla de la CLI para desarrolladores de Azure (azd) para crear el proyecto de código, así como la implementación de Azure en la que ejecutar la aplicación.

En este tutorial, usará la azd herramienta para:

  • Inicialice un proyecto habilitado para OpenTelemetry desde una plantilla.
  • Revise el código que habilita la integración de OpenTelemetry.
  • Ejecute y compruebe la aplicación habilitada para OpenTelemetry localmente.
  • Cree una aplicación de funciones y recursos relacionados en Azure.
  • Implemente el proyecto de código en la aplicación de funciones en Azure.
  • Compruebe el seguimiento distribuido en Application Insights.

Los recursos de Azure necesarios creados por esta plantilla siguen los procedimientos recomendados actuales para implementaciones seguras y escalables de aplicaciones de funciones en Azure. El mismo azd comando también implementa el proyecto de código en la nueva aplicación de funciones en Azure.

De forma predeterminada, el plan Flex Consumption sigue un modelo de facturación de pago por uso, lo que implica que completar esta iniciación rápida conllevará un pequeño costo de unos pocos centavos de dólar estadounidense o menos en su cuenta de Azure.

Importante

Actualmente, este artículo solo admite C#, Python y TypeScript. Para completar el inicio rápido, seleccione uno de estos idiomas admitidos en la parte superior del artículo.

Prerrequisitos

  • Node.js 18.x o posterior

Inicialización del proyecto

Use el azd init comando para crear un proyecto de código local de Azure Functions a partir de una plantilla que incluya el seguimiento distribuido de OpenTelemetry.

  1. En el terminal local o el símbolo del sistema, ejecute este comando azd init en una carpeta vacía:

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

    Este comando extrae los archivos del proyecto del repositorio de plantillas e inicializa el proyecto en la carpeta actual. La marca -e establece un nombre para el entorno actual. En azd, el entorno mantiene un contexto de implementación único para la aplicación y puede definir más de uno. El nombre del entorno también aparece en el nombre del grupo de recursos que crea en Azure.

  1. En el terminal local o el símbolo del sistema, ejecute este comando azd init en una carpeta vacía:

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

    Este comando extrae los archivos del proyecto del repositorio de plantillas e inicializa el proyecto en la carpeta actual. La marca -e establece un nombre para el entorno actual. En azd, el entorno mantiene un contexto de implementación único para la aplicación y puede definir más de uno. El nombre del entorno también aparece en el nombre del grupo de recursos que crea en Azure.

  1. En el terminal local o el símbolo del sistema, ejecute este comando azd init en una carpeta vacía:

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

    Este comando extrae los archivos del proyecto del repositorio de plantillas e inicializa el proyecto en la carpeta actual. La marca -e establece un nombre para el entorno actual. En azd, el entorno mantiene un contexto de implementación único para la aplicación y puede definir más de uno. El nombre del entorno también aparece en el nombre del grupo de recursos que crea en Azure.

Revisión del código

La plantilla crea un escenario de seguimiento distribuido completo con tres funciones que funcionan conjuntamente. Vamos a revisar los aspectos clave relacionados con OpenTelemetry:

Configuración de OpenTelemetry

El src/otel-sample/host.json archivo habilita OpenTelemetry para el host de Functions:

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

La configuración "telemetryMode": "OpenTelemetry" de clave habilita el seguimiento distribuido entre las llamadas de función.

El src/OTelSample/host.json archivo habilita OpenTelemetry para el host de Functions:

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

La configuración "telemetryMode": "OpenTelemetry" de clave habilita el seguimiento distribuido entre las llamadas de función.

Dependencias para OpenTelemetry

El src/otel-sample/requirements.txt archivo incluye los paquetes necesarios para la integración de OpenTelemetry:

azure-functions
azure-monitor-opentelemetry
requests

El azure-monitor-opentelemetry paquete proporciona la integración de OpenTelemetry con Application Insights.

El src/otel-sample/package.json archivo incluye los paquetes necesarios para la integración de 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"
  }
}

Los @azure/functions-opentelemetry-instrumentation paquetes y @azure/monitor-opentelemetry-exporter proporcionan la integración de OpenTelemetry con Application Insights.

El .csproj archivo incluye los paquetes necesarios para la integración de 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" />

Estos paquetes proporcionan la integración de OpenTelemetry con Application Insights e instrumentación HTTP para el seguimiento distribuido.

Implementación de funciones

Las funciones de src/otel-sample/function_app.py muestran un flujo de seguimiento distribuido:

Primera función 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"
    )

Segunda función 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
    )

Desencadenador de cola de 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 configuración de OpenTelemetry se configura en 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()],
});

Las funciones se definen en la src/otel-sample/src/functions carpeta :

Primera función 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" }),
    };
  }
}

Segunda función 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,
  };
}

Desencadenador de cola de 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 configuración de OpenTelemetry se configura en 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();

Las funciones se definen en archivos de clase independientes:

Primera función 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}");
    }
}

Segunda función 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; }
}

Desencadenador de cola de 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);
    }
}

Flujo de seguimiento distribuido

Esta arquitectura crea un escenario de seguimiento distribuido completo, con este comportamiento:

  1. La primera función HTTP recibe una solicitud HTTP y llama a la segunda función HTTP
  2. La segunda función HTTP responde y envía un mensaje a Service Bus
  3. El desencadenador de Service Bus procesa el mensaje con un retraso para simular el trabajo de procesamiento

Aspectos clave de la implementación de OpenTelemetry:

  • Integración de OpenTelemetry: el host.json archivo habilita OpenTelemetry con "telemetryMode": "OpenTelemetry"
  • Encadenamiento de funciones: la primera función llama a la segunda mediante solicitudes HTTP, creando seguimientos correlacionados
  • Integración de Service Bus: la segunda función se genera en Service Bus, que desencadena la tercera función.
  • Autenticación anónima: las funciones HTTP usan auth_level=func.AuthLevel.ANONYMOUS, por lo que no se requieren claves de función.

Puede revisar el proyecto de plantilla completo aquí.

  • Integración de OpenTelemetry: el index.ts archivo configura OpenTelemetry con exportadores de Azure Monitor para seguimientos y registros.
  • Encadenamiento de funciones: la primera función llama a la segunda mediante axios con propagación automática de seguimiento
  • Integración de Service Bus: la segunda función envía datos a Service Bus usando vínculos de salida, lo que desencadena la tercera función.
  • Identidad administrada: todas las conexiones de Service Bus usan identidad administrada en lugar de cadenas de conexión
  • Simulación de procesamiento: el retraso de 5 segundos en el desencadenador de Service Bus simula el trabajo de procesamiento de mensajes.

Puede revisar el proyecto de plantilla completo aquí.

  • Integración de OpenTelemetry: el Program.cs archivo configura OpenTelemetry con el exportador de Azure Monitor.
  • Encadenamiento de funciones: la primera función llama a la segunda mediante HttpClient con instrumentación de OpenTelemetry
  • Integración de Service Bus: la segunda función manda salida a Service Bus mediante vínculos de salida, lo que desencadena la tercera función.
  • Identidad administrada: todas las conexiones de Service Bus usan identidad administrada en lugar de cadenas de conexión
  • Trabajo aislado de .NET 8: usa el modelo de trabajo aislado de .NET de Azure Functions más reciente para mejorar el rendimiento y la flexibilidad.

Puede revisar el proyecto de plantilla completo aquí.

Después de comprobar las funciones localmente, es el momento de publicarlas en Azure.

Implementación en Azure

Este proyecto está configurado para usar el azd up comando para implementar este proyecto en una nueva aplicación de funciones en un plan de consumo flexible en Azure con compatibilidad con OpenTelemetry.

Sugerencia

Este proyecto incluye un conjunto de archivos de Bicep que azd usa para crear una implementación segura en un plan de consumo Flex que sigue las mejores prácticas, incluidas las conexiones de identidad administrada.

  1. Ejecute este comando para que azd cree los recursos de Azure necesarios en Azure e implemente el proyecto de código en la nueva aplicación de funciones:

    azd up
    

    La carpeta raíz contiene el azure.yamlarchivo de definición de requerido por azd.

    Si aún no ha iniciado sesión, se le pedirá que se autentique con su cuenta de Azure.

  2. Cuando se le solicite, proporcione estos parámetros de implementación necesarios:

    Parámetro Description
    Suscripción a Azure Suscripción en la que se crean los recursos.
    Ubicación de Azure Región de Azure en la que se va a crear el grupo de recursos que contiene los nuevos recursos de Azure. Solo se muestran las regiones que admiten actualmente el Plan de consumo flexible.

    El comando azd up usa la respuesta a estos mensajes con los archivos de configuración de Bicep para completar estas tareas de implementación:

    • Cree y configure estos recursos de Azure necesarios (equivalente a azd provision):

      • Plan de consumo flexible y aplicación de funciones de Azure Functions con OpenTelemetry habilitado
      • Azure Storage (obligatorio) y Application Insights (recomendado)
      • Espacio de nombres y cola de Service Bus para la demostración de rastreo distribuido
      • Directivas y roles de acceso para la cuenta
      • Conexiones de servicio a servicio mediante identidades administradas (en lugar de cadenas de conexión almacenadas)
    • Empaquete e implemente el código en el contenedor de implementación (equivalente a azd deploy). A continuación, la aplicación se inicia y se ejecuta en el paquete implementado.

    Una vez que el comando se complete correctamente, verá vínculos a los recursos que creó.

Prueba del seguimiento distribuido

Ahora puede probar la funcionalidad de seguimiento distribuido de OpenTelemetry llamando a las funciones implementadas y observando la telemetría en Application Insights.

Invocación de la función en Azure

Puede invocar los puntos de conexión de función en Azure realizando solicitudes HTTP a sus respectivas direcciones URL. Dado que las funciones HTTP de esta plantilla están configuradas con acceso anónimo, no se requieren claves de función.

  1. En su terminal local o símbolo del sistema, ejecute este comando para obtener el nombre de la función de aplicación y generar la URL:

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

    El comando azd env get-value obtiene el nombre de la aplicación de funciones del entorno local.

  2. Pruebe la función en el explorador; para ello, vaya a la dirección URL:

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

    Reemplace por your-function-app el nombre de la aplicación de funciones real del paso anterior. Esta única solicitud crea un seguimiento distribuido que fluye a través de las tres funciones.

Visualización del seguimiento distribuido en Application Insights

Después de invocar la función, puede observar el seguimiento distribuido completo en Application Insights:

Nota:

Los datos de telemetría pueden tardar unos minutos en aparecer en Application Insights después de invocar la función. Si no ve los datos inmediatamente, espere unos minutos y actualice la vista.

  1. Vaya al recurso de Application Insights en Azure Portal (puede encontrarlo en el mismo grupo de recursos que la aplicación de funciones).

  2. Abra el mapa de aplicación para ver el seguimiento distribuido en las tres funciones. Debería ver el flujo desde la solicitud HTTP a través de las funciones hasta el Service Bus.

  3. Compruebe la búsqueda de transacciones para encontrar la solicitud y vea la escala de tiempo de seguimiento completa. Busque transacciones en su aplicación de funciones.

  4. Seleccione una transacción específica para ver el seguimiento de un extremo a otro que muestra:

    • Solicitud HTTP a first_http_function
    • Llamada HTTP interna a second_http_function
    • El mensaje del Service Bus que se está enviando
    • El servicebus_queue_trigger procesando el mensaje de Service Bus
  5. En los detalles del seguimiento, puede ver:

    • Información de tiempo: cuánto tiempo se llevó a cabo cada paso
    • Dependencias: las conexiones entre funciones
    • Registros: Registros de aplicación correlacionados con la traza
    • Métricas de rendimiento: tiempos de respuesta y rendimiento

En este ejemplo se muestra el seguimiento distribuido de un extremo a otro en varias instancias de Azure Functions con la integración de OpenTelemetry, lo que proporciona visibilidad completa del comportamiento y el rendimiento de la aplicación.

Reimplementación del código

Ejecute el azd up comando tantas veces como sea necesario aprovisionar los recursos de Azure e implementar actualizaciones de código en la aplicación de funciones.

Nota:

El paquete de implementación más reciente siempre sobrescribe los archivos de código implementados.

Las respuestas iniciales a azd mensajes y las variables de entorno generadas por azd se almacenan localmente en el entorno con nombre. Use el azd env get-values comando para revisar todas las variables del entorno que usa el comando al crear recursos de Azure.

Limpieza de recursos

Cuando haya terminado de trabajar con la aplicación de funciones y los recursos relacionados, use este comando para eliminar la aplicación de funciones y sus recursos relacionados de Azure y evitar incurrir en costos adicionales:

azd down --no-prompt

Nota:

La --no-prompt opción indica azd que elimine el grupo de recursos sin una confirmación de usted.

Este comando no afecta al proyecto de código local.