Partilhar via


Tutorial: Monitorizar Funções Azure com rastreamento distribuído do OpenTelemetry

Este artigo demonstra o suporte ao OpenTelemetry nas Azure Functions, que permite o rastreamento distribuído entre múltiplas chamadas de função utilizando o suporte integrado do Application Insights e OpenTelemetry. Para o ajudar a começar, utiliza-se um modelo Azure Developer CLI (azd) para criar o seu projeto de código, bem como a implementação do Azure para executar a sua aplicação.

Neste tutorial, utilizas a azd ferramenta para:

  • Inicialize um projeto com OpenTelemetry a partir de um template.
  • Revise o código que permite a integração com o OpenTelemetry.
  • Execute e verifique localmente a sua aplicação compatível com OpenTelemetry.
  • Crie uma aplicação de funções e recursos relacionados no Azure.
  • Implementa o teu projeto de código na aplicação de funções no Azure.
  • Verifique o rastreio distribuído no Application Insights.

Os recursos Azure necessários criados por este modelo seguem as melhores práticas atuais para implementações de aplicações funcionais seguras e escaláveis no Azure. O mesmo azd comando também implementa o seu projeto de código na nova aplicação de funções no Azure.

Por padrão, o plano Flex Consumption segue um modelo de faturação 'pague pelo que usar', o que significa que completar este início rápido implica um pequeno custo de alguns cêntimos de dólar ou menos na sua Conta Azure.

Importante

Este artigo atualmente suporta apenas C#, Python e TypeScript. Para concluir o início rápido, selecione um desses idiomas suportados na parte superior do artigo.

Pré-requisitos

Inicializar o projeto

Use o azd init comando para criar um projeto de código Azure Functions local a partir de um template que inclua rastreamento distribuído OpenTelemetry.

  1. No terminal local ou prompt de comando, execute este azd init comando em uma pasta vazia:

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

    Este comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O -e sinalizador define um nome para o ambiente atual. No azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que crias no Azure.

  1. No terminal local ou prompt de comando, execute este azd init comando em uma pasta vazia:

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

    Este comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O -e sinalizador define um nome para o ambiente atual. No azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que crias no Azure.

  1. No terminal local ou prompt de comando, execute este azd init comando em uma pasta vazia:

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

    Este comando extrai os arquivos de projeto do repositório de modelos e inicializa o projeto na pasta atual. O -e sinalizador define um nome para o ambiente atual. No azd, o ambiente mantém um contexto de implantação exclusivo para seu aplicativo e você pode definir mais de um. O nome do ambiente também aparece no nome do grupo de recursos que crias no Azure.

Rever o código

O modelo cria um cenário completo de rastreamento distribuído com três funções que funcionam em conjunto. Vamos rever os principais aspetos relacionados com a OpenTelemetry:

Configuração do OpenTelemetry

O ficheiro src/otel-sample/host.json ativa o OpenTelemetry no anfitrião do Functions.

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

A definição chave "telemetryMode": "OpenTelemetry" permite o rastreamento distribuído nas chamadas de funções.

O ficheiro src/OTelSample/host.json ativa o OpenTelemetry do anfitrião Functions.

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

A definição de chave "telemetryMode": "OpenTelemetry" permite o rastreamento distribuído entre chamadas de funções.

Dependências para OpenTelemetry

O src/otel-sample/requirements.txt ficheiro inclui os pacotes necessários para a integração do OpenTelemetry:

azure-functions
azure-monitor-opentelemetry
requests

O azure-monitor-opentelemetry pacote fornece a integração OpenTelemetry com o Application Insights.

O src/otel-sample/package.json ficheiro inclui os pacotes necessários para a integração do 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"
  }
}

Os @azure/functions-opentelemetry-instrumentation pacotes e @azure/monitor-opentelemetry-exporter fornecem a integração OpenTelemetry com o Application Insights.

O .csproj ficheiro inclui os pacotes necessários para a integração do 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" />

Estes pacotes fornecem a integração OpenTelemetry com Application Insights e instrumentação HTTP para rastreamento distribuído.

Implementação da função

As funções em src/otel-sample/function_app.py demonstram um fluxo distribuído de rastreamento:

Primeira Função 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 Função 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
    )

Trigger da Fila do Barramento de Serviço

@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')

A configuração do OpenTelemetry está configurada em 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()],
});

As funções são definidas na src/otel-sample/src/functions pasta:

Primeira Função 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 Função 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,
  };
}

Trigger da Fila do Barramento de Serviço

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");
}

A configuração do OpenTelemetry está configurada em 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();

As funções são definidas em ficheiros de classes separados:

Primeira Função 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 Função 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; }
}

Trigger da Fila do Barramento de Serviço

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);
    }
}

Fluxo de rastreamento distribuído

Esta arquitetura cria um cenário completo de rastreamento distribuído, com este comportamento:

  1. A primeira função HTTP recebe um pedido HTTP e chama a segunda função HTTP
  2. A segunda função HTTP responde e envia uma mensagem para o Service Bus
  3. O disparador do Service Bus processa a mensagem com um atraso para simular o processamento do trabalho

Aspetos-chave da implementação do OpenTelemetry:

  • Integração com OpenTelemetry: O ficheiro host.json ativa o OpenTelemetry com "telemetryMode": "OpenTelemetry"
  • Encadeamento de funções: A primeira função chama a segunda usando pedidos HTTP, criando trilhos correlacionados
  • Integração com Service Bus: A segunda função é enviada para Service Bus, que desencadeia a terceira função
  • Autenticação anónima: As funções HTTP usam auth_level=func.AuthLevel.ANONYMOUS, pelo que não são necessárias chaves de função

Você pode revisar o projeto de modelo completo aqui.

  • Integração com OpenTelemetry: O index.ts ficheiro configura o OpenTelemetry com os exportadores Azure Monitor para rastreios e registos
  • Encadeamento de funções: A primeira função chama a segunda por meio do axios com propagação automática de rastreamento
  • Integração com Service Bus: A segunda função envia para Service Bus usando ligações de saída, que ativam a terceira função
  • Identidade gerida: Todas as ligações do Barramento de Serviço usam identidade gerida em vez de cadeias de ligação
  • Simulação de processamento: O atraso de 5 segundos no disparo do Service Bus simula o processamento de mensagens

Você pode revisar o projeto de modelo completo aqui.

  • Integração com OpenTelemetry: O Program.cs ficheiro configura o OpenTelemetry com o exportador Azure Monitor
  • Encadeamento de funções: A primeira função chama a segunda usando HttpClient com instrumentação OpenTelemetry
  • Integração com Service Bus: A segunda função envia para Service Bus usando ligações de saída, que ativam a terceira função
  • Identidade gerida: Todas as ligações do Barramento de Serviço usam identidade gerida em vez de cadeias de ligação
  • .NET 8 Isolated Worker: Utiliza o modelo mais recente Azure Functions .NET Isolated Worker para melhor desempenho e flexibilidade

Você pode revisar o projeto de modelo completo aqui.

Depois de verificar suas funções localmente, é hora de publicá-las no Azure.

Publicar no Azure

Este projeto está configurado para usar o azd up comando para implantar este projeto numa nova aplicação de funções num plano Flex Consumption no Azure com suporte OpenTelemetry.

Sugestão

Este projeto inclui um conjunto de ficheiros Bicep que azd utiliza para criar uma implementação segura num plano de consumo Flex que segue as melhores práticas, incluindo ligações de identidade geridas.

  1. Execute este comando para criar azd os recursos necessários do Azure no Azure e implantar seu projeto de código no novo aplicativo de função:

    azd up
    

    A pasta raiz contém o arquivo de azure.yaml definição exigido pelo azd.

    Se já ainda não tiver iniciado sessão, ser-lhe-á solicitado que se autentique com a sua conta Azure.

  2. Quando solicitado, forneça estes parâmetros de implantação necessários:

    Parâmetro Description
    Subscrição do Azure Subscrição na qual os seus recursos são criados.
    localização do Azure Região do Azure na qual criar o grupo de recursos que contém os novos recursos do Azure. Apenas as regiões que atualmente suportam o plano Flex Consumption são mostradas.

    O azd up comando usa sua resposta a esses prompts com os arquivos de configuração do Bicep para concluir estas tarefas de implantação:

    • Crie e configure estes recursos necessários do Azure (equivalentes a azd provision):

      • Plano de consumo e aplicação de funções Azure Functions Flex com OpenTelemetry ativada
      • Armazenamento do Azure (obrigatório) e Application Insights (recomendado)
      • ** Namespace e fila do Service Bus para demonstração de rastreamento distribuído
      • Políticas e funções de acesso para a sua conta
      • Conexões de serviço a serviço usando identidades gerenciadas (em vez de cadeias de conexão armazenadas)
    • Empacote e implante seu código no contêiner de implantação (equivalente a azd deploy). O aplicativo é então iniciado e executado no pacote implantado.

    Depois que o comando for concluído com êxito, você verá links para os recursos criados.

Testar rastreamento distribuído

Agora pode testar a funcionalidade de rastreamento distribuído do OpenTelemetry chamando as suas funções implementadas e observando a telemetria no Application Insights.

Invoque a função no Azure

Podes invocar os endpoints das tuas funções no Azure fazendo requisições HTTP para os seus URLs. Como as funções HTTP neste modelo estão configuradas com acesso anónimo, não são necessárias chaves de função.

  1. No seu terminal local ou no prompt de comandos, execute este comando para obter o nome da aplicação de função e construa o URL:

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

    O azd env get-value comando obtém o nome do aplicativo de função do ambiente local.

  2. Teste a função no seu navegador navegando até à URL:

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

    Substitui your-function-app pelo nome real da tua app de função do passo anterior. Este único pedido cria um traço distribuído que atravessa as três funções.

Veja o rastreamento distribuído no Application Insights

Após invocar a função, pode observar o traço distribuído completo em Application Insights:

Observação

Pode demorar alguns minutos até que os dados de telemetria apareçam no Application Insights após invocar a sua função. Se não vir dados imediatamente, espere alguns minutos e atualize a visualização.

  1. Vai ao teu recurso Application Insights no portal do Azure (podes encontrá-lo no mesmo grupo de recursos que a tua aplicação de funções).

  2. Abra o mapa da Aplicação para ver o traço distribuído entre as três funções. Deves ver o fluxo do pedido HTTP através das tuas funções até ao Service Bus.

  3. Consulte a pesquisa de Transações para encontrar o seu pedido e ver a linha temporal completa do rastreio. Procure transações na sua aplicação de funções.

  4. Selecione uma transação específica para ver o rastreio de ponta a ponta que mostra:

    • O pedido HTTP para first_http_function
    • A chamada HTTP interna para second_http_function
    • A mensagem do Service Bus a ser enviada
    • O servicebus_queue_trigger processamento da mensagem a partir do Barramento de Serviço
  5. Nos detalhes do traço, pode ver:

    • Informação de tempo: Quanto tempo demorava cada passo
    • Dependências: As ligações entre funções
    • Registos: Registos de aplicação correlacionados com o rastreio
    • Métricas de desempenho: tempos de resposta e throughput

Este exemplo demonstra rastreamento distribuído de ponta a ponta através de múltiplas Azure Functions com integração OpenTelemetry, proporcionando visibilidade completa sobre o comportamento e desempenho da sua aplicação.

Reimplantar seu código

Executa o azd up comando tantas vezes quanto precisares tanto para provisionar os teus recursos Azure como para implementar atualizações de código na tua aplicação de funções.

Observação

O pacote de implantação mais recente sobrescreve sempre ficheiros de código implementados.

Suas respostas iniciais a azd prompts e quaisquer variáveis de ambiente geradas por azd são armazenadas localmente em seu ambiente nomeado. Use o azd env get-values comando para rever todas as variáveis no seu ambiente que o comando utiliza ao criar recursos Azure.

Limpeza de recursos

Quando terminar de trabalhar com a sua function app e recursos relacionados, use este comando para eliminar a function app e os seus recursos relacionados do Azure e evitar custos adicionais:

azd down --no-prompt

Observação

A --no-prompt opção instrui azd a excluir seu grupo de recursos sem uma confirmação sua.

Este comando não afeta seu projeto de código local.