Azure Functions met Aspire

Aspire is een toolchain voor het bouwen, uitvoeren, debuggen en uitrollen van gedistribueerde applicaties. De Aspire Azure Functions-integratie stelt je in staat om een Azure Functions-project te ontwikkelen, te debuggen en te orkestreren als onderdeel van een Aspire AppHost. De .NET-voorbeelden in dit artikel gebruiken het model van geïsoleerde werker.

Prerequisites

Stel uw ontwikkelomgeving in voor het gebruik van Azure Functions met Aspire:

Als je Visual Studio gebruikt, installeer dan de nieuwste Visual Studio- en Azure Functions-tools-updates:

  1. Ga naar Extra-opties>.
  2. Selecteer Azure Functions onder Projecten en oplossingen.
  3. Selecteer Controleren op updates en installeer updates zoals hierom wordt gevraagd.

Voor meer informatie over het integratiepakket en de ondersteunde AppHost API's, zie Set up Azure Functions in de AppHost.

Oplossingsstructuur

Een oplossing die Azure Functions en Aspire gebruikt, heeft meerdere projecten, waaronder een AppHost en één of meer Functions-projecten.

De AppHost is het toegangspunt voor je aanvraag. Het organiseert de installatie van de onderdelen van uw toepassing, met inbegrip van het Functions-project.

De oplossing bevat doorgaans ook een standaardproject voor services . Dit project biedt een set standaardservices en configuraties die in projecten in uw toepassing moeten worden gebruikt.

AppHost-project

Om de integratie succesvol te configureren, zorg ervoor dat het AppHost-project aan de volgende eisen voldoet:

  • De AppHost verwijst naar Aspire.Hosting.Azure. Functions. Dit pakket definieert de integratie.
  • Een C# AppHost verwijst naar een Functions-project en roept AddAzureFunctionsProject<TProject>() aan, of roept AddAzureFunctionsProject(name, projectPath) aan met het pad naar het projectbestand. TypeScript AppHosts gebruiken de projectpadvorm van addAzureFunctionsProject.
  • Gebruik AddAzureFunctionsProject in plaats van AddProject. Een Functions-project dat is toegevoegd door te gebruiken AddProject , kan niet goed starten.

Het volgende voorbeeld toont een minimaal AppHost.cs bestand voor een C# AppHost-project:

var builder = DistributedApplication.CreateBuilder(args);

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject");

builder.Build().Run();

Azure Functions-project

Als u de integratie wilt configureren, moet u ervoor zorgen dat het Azure Functions-project voldoet aan de volgende vereisten:

In het volgende voorbeeld ziet u een minimaal Program.cs bestand voor een Functions-project dat wordt gebruikt in Aspire:

using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.ConfigureFunctionsWebApplication();

builder.Build().Run();

Dit voorbeeld bevat niet de standaard Application Insights-configuratie die wordt weergegeven in veel andere Program.cs voorbeelden en in de Azure Functions-sjablonen. In plaats daarvan configureert u OpenTelemetry-integratie in Aspire door de methode aan te builder.AddServiceDefaults() roepen.

Bekijk de volgende richtlijnen om optimaal gebruik te maken van de integratie:

  • Neem geen directe Application Insights-integraties op in het Functions-project. Bewaking in Aspire wordt in plaats daarvan afgehandeld via de OpenTelemetry-ondersteuning. U kunt Aspire configureren voor het exporteren van gegevens naar Azure Monitor via het standaardproject voor de service.
  • Wanneer Aspire het Functions-project uitvoert, geef je voorkeur aan instellingen die door de AppHost zijn geïnjecteerd. Je kunt equivalente instellingen in local.settings.json laten staan om het project zelfstandig uit te voeren met func start; door Aspire geïnjecteerde omgevingsvariabelen hebben daar voorrang op.

Verbindingsconfiguratie met Aspire

De AppHost definieert resources en helpt je om verbindingen tussen deze te maken door gebruik te maken van code. In deze sectie wordt beschreven hoe u verbindingen configureert en aanpast die door uw Azure Functions-project worden gebruikt.

Aspire bevat standaard verbindingsmachtigingen die u kunnen helpen aan de slag te gaan. Deze machtigingen zijn echter mogelijk niet geschikt of voldoende voor uw toepassing.

Voor scenario's die gebruikmaken van op rollen gebaseerd toegangsbeheer van Azure (RBAC), kunt u machtigingen aanpassen door de WithRoleAssignments() methode voor de projectresource aan te roepen. Wanneer u aanroept WithRoleAssignments(), worden alle standaardroltoewijzingen verwijderd en moet u expliciet de volledige set roltoewijzingen definiëren die u wilt. Als u uw toepassing op Azure Container Apps host, moet u ook WithRoleAssignments() aanroepen op AddAzureContainerAppEnvironment() met behulp van DistributedApplicationBuilder.

Azure Functions-hostopslag

Azure Functions vereist een hostopslagverbinding (AzureWebJobsStorage) voor verschillende kerngedrag. Wanneer je AddAzureFunctionsProject<TProject>() in je AppHost aanroept, maak je standaard een verbinding van het type AzureWebJobsStorage aan en geef je die door aan het Functions-project. Deze standaardverbinding gebruikt de Azure Storage-emulator voor lokale ontwikkelingsruns en stelt automatisch een opslagaccount beschikbaar wanneer je het uitrolt. Voor meer controle vervang deze verbinding door de Functions-projectresource aan te roepen .WithHostStorage() .

De standaardrechten die Aspire voor de hostopslagverbinding stelt, hangen af van of je belt WithHostStorage() of niet. Het toevoegen van WithHostStorage() verwijdert een bijdrager voor opslagaccounts-toewijzing. De volgende tabel bevat de standaardmachtigingen die Aspire instelt voor de hostopslagverbinding:

Hostopslagverbinding Standaardrollen
Geen telefoongesprek naar WithHostStorage() Bijdrager voor Blob-gegevensopslag,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens,
Bijdrager aan opslagaccount
Roepen WithHostStorage() Bijdrager voor Blob-gegevensopslag,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens

Het volgende voorbeeld toont een minimaal AppHost.cs bestand dat de hostopslag vervangt en een roltoewijzing specificeert:

using Azure.Provisioning.Storage;

var builder = DistributedApplication.CreateBuilder(args);

builder.AddAzureContainerAppEnvironment("myEnv");

var myHostStorage = builder.AddAzureStorage("myHostStorage");

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
    .WithHostStorage(myHostStorage)
    .WithRoleAssignments(myHostStorage, StorageBuiltInRole.StorageBlobDataOwner);

builder.Build().Run();

Opmerking

Eigenaar van opslagblobgegevens is de rol die wordt aanbevolen voor de basisbehoeften van de hostopslagverbinding. Uw app kan problemen ondervinden als de verbinding met de blobservice alleen de Aspire-standaard van Opslagblobgegevensbijdrager heeft.

Voor productiescenario's moet u zowel oproepen naar WithHostStorage() als WithRoleAssignments() opnemen. U kunt deze rol vervolgens expliciet instellen, samen met andere personen die u nodig hebt.

Trigger- en bindingsverbindingen

Uw triggers en bindingen verwijzen naar verbindingen op naam. De volgende Aspire-integraties bieden deze verbindingen via een aanroep naar WithReference() de projectresource:

Ambieer integratie Standaardrollen
Azure Blob-opslagruimte Bijdrager voor Blob-gegevensopslag,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens
Azure Queue Storage Bijdrager voor Blob-gegevensopslag,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens
Azure Event Hubs Azure Event Hubs-gegevenseigenaar
Azure Service Bus Eigenaar van Azure Service Bus-gegevens

Het volgende voorbeeld toont een minimaal AppHost.cs bestand dat een wachtrijtrigger configureert. In dit voorbeeld is de eigenschap van de bijbehorende wachtrijtrigger ingesteld op Connection, dus specificeert de aanroep MyQueueTriggerConnection de naam.

var builder = DistributedApplication.CreateBuilder(args);

var myAppStorage = builder.AddAzureStorage("myAppStorage").RunAsEmulator();
var queues = myAppStorage.AddQueues("queues");

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
    .WithReference(queues, "MyQueueTriggerConnection");

builder.Build().Run();

Voor andere integraties worden aanroepen om WithReference de configuratie op een andere manier in te stellen. Ze maken de configuratie beschikbaar voor Aspire-clientintegraties, maar niet voor triggers en bindingen. Voor deze integraties roept WithEnvironment() u om de verbindingsgegevens door te geven voor de trigger of binding die moet worden opgelost.

In het volgende voorbeeld ziet u hoe u de omgevingsvariabele MyBindingConnection instelt voor een resource waarmee een verbindingsreeksexpressie wordt weergegeven:

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
    .WithEnvironment("MyBindingConnection", otherIntegration.Resource.ConnectionStringExpression);

Als u wilt dat zowel Aspire-clientintegraties als het systeem van triggers en bindingen een verbinding gebruiken, moet u zowel WithReference() als WithEnvironment() configureren.

Voor sommige resources kan de structuur van een verbinding verschillen tussen wanneer u deze lokaal uitvoert en wanneer u deze publiceert naar Azure. In het vorige voorbeeld otherIntegration kan dit een resource zijn die wordt uitgevoerd als een emulator, dus ConnectionStringExpression retourneert een emulatorverbindingsreeks. Wanneer de resource wordt gepubliceerd, kan Aspire echter een op identiteit gebaseerde verbinding instellen en ConnectionStringExpression de URI van de service retourneren. Als u in dit geval op identiteit gebaseerde verbindingen voor Azure Functions wilt instellen, moet u mogelijk een andere naam voor de omgevingsvariabele opgeven.

In het volgende voorbeeld wordt builder.ExecutionContext.IsPublishMode gebruikt om het benodigde achtervoegsel voorwaardelijk toe te voegen.

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
    .WithEnvironment("MyBindingConnection" + (builder.ExecutionContext.IsPublishMode ? "__serviceUri" : ""), otherIntegration.Resource.ConnectionStringExpression);

Raadpleeg de referentiepagina's van de binding voor meer informatie over de verbindingsindelingen die elke binding ondersteunt en de machtigingen die deze indelingen vereisen.

Voor meer informatie over hoe Functions-code waarden leest die worden geïnjecteerd door WithReference, zie Azure Functions runtime configuratie.

De toepassing hosten

Aspire ondersteunt Azure Container Apps-implementatie voor Functions-projecten. Je kunt ook de aparte preview App Service-integratie gebruiken om een container-geschikte functie-app te targeten:

In beide gevallen wordt uw project geïmplementeerd als een container. Aspire zorgt ervoor dat de containerafbeeldingen voor u worden gebouwd en naar Azure Container Registry worden geüpload.

Implementeren als container-app

Wanneer je AppHost zich richt op Azure Container Apps, stelt Aspire schaalregels op voor je Functions-project met behulp van KEDA. Wanneer je Azure Container Apps gebruikt, moet je extra configuratie uitvoeren voor functiesleutels. Voor meer informatie, zie Access keys on Azure Container Apps.

Rol de geconfigureerde AppHost uit door te draaien aspire deploy. Voor meer informatie, zie Deploy to Azure Container Apps en aspire deploy.

Toegangssleutels in Azure Container Apps

In verschillende Azure Functions-scenario's worden toegangssleutels gebruikt om een basisbeperking te bieden tegen ongewenste toegang. Voor HTTP-triggerfuncties moet bijvoorbeeld standaard een toegangssleutel worden aangeroepen, hoewel deze vereiste kan worden uitgeschakeld met behulp van de AuthLevel eigenschap. Zie Werken met toegangssleutels in Azure Functions voor scenario's waarvoor mogelijk een sleutel is vereist.

Wanneer je een Functions-project uitrolt met Aspire naar Azure Container Apps, maakt of beheert het systeem niet automatisch toegangssleutels voor Functions. Als je toegangssleutels moet gebruiken, kun je ze beheren als onderdeel van je AppHost-setup. Deze sectie laat zien hoe je een extensiemethode kunt aanmaken die je vanuit het AppHost.cs bestand van je AppHost kunt aanroepen om toegangssleutels aan te maken en te beheren. Deze benadering maakt gebruik van Azure Key Vault om de sleutels op te slaan en deze als geheimen in de container-app te koppelen.

Opmerking

Het gedrag hier is afhankelijk van de ContainerApps geheime provider, die een Functions-hostversie 4.1044.0 of later vereist.

Voor deze stappen is Bicep-versie 0.38.3 of hoger vereist. U kunt uw Bicep-versie controleren door bicep --version uit te voeren vanuit een opdrachtprompt. Als u de Azure CLI hebt geïnstalleerd, kunt u met az bicep upgrade Bicep snel bijwerken naar de nieuwste versie.

Voeg de volgende NuGet-pakketten toe aan je AppHost-project:

Maak een nieuwe klasse aan in je AppHost-project en voeg de volgende code toe:

using Aspire.Hosting.Azure;
using Azure.Provisioning.AppContainers;

namespace Aspire.Hosting;

internal static class Extensions
{
    private record SecretMapping(string OriginalName, IAzureKeyVaultSecretReference Reference);

    public static IResourceBuilder<T> PublishWithContainerAppSecrets<T>(
        this IResourceBuilder<T> builder,
        IResourceBuilder<AzureKeyVaultResource>? keyVault = null,
        string[]? hostKeyNames = null,
        string[]? systemKeyExtensionNames = null)
        where T : AzureFunctionsProjectResource
    {
        if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
        {
            return builder;
        }

        keyVault ??= builder.ApplicationBuilder.AddAzureKeyVault("functions-keys");

        var hostKeysToAdd = (hostKeyNames ?? []).Append("default").Select(k => $"host-function-{k}");
        var systemKeysToAdd = systemKeyExtensionNames?.Select(k => $"host-systemKey-{k}_extension") ?? [];
        var secrets = hostKeysToAdd.Union(systemKeysToAdd)
            .Select(secretName => new SecretMapping(
                secretName,
                CreateSecretIfNotExists(builder.ApplicationBuilder, keyVault, secretName.Replace("_", "-"))
            )).ToList();

        return builder
            .WithReference(keyVault)
            .WithEnvironment("AzureWebJobsSecretStorageType", "ContainerApps")
            .PublishAsAzureContainerApp((infra, app) => ConfigureFunctionsContainerApp(infra, app, builder.Resource, secrets));
    }

    private static void ConfigureFunctionsContainerApp(
        AzureResourceInfrastructure infrastructure, 
        ContainerApp containerApp, 
        IResource resource, 
        List<SecretMapping> secrets)
    {
        const string volumeName = "functions-keys";
        const string mountPath = "/run/secrets/functions-keys";

        var appIdentityAnnotation = resource.Annotations.OfType<AppIdentityAnnotation>().Last();
        var containerAppIdentityId = appIdentityAnnotation.IdentityResource.Id.AsProvisioningParameter(infrastructure);

        var containerAppSecretsVolume = new ContainerAppVolume
        {
            Name = volumeName,
            StorageType = ContainerAppStorageType.Secret
        };

        foreach (var mapping in secrets)
        {
            var secret = mapping.Reference.AsKeyVaultSecret(infrastructure);

            containerApp.Configuration.Secrets.Add(new ContainerAppWritableSecret()
            {
                Name = mapping.Reference.SecretName.ToLowerInvariant(),
                KeyVaultUri = secret.Properties.SecretUri,
                Identity = containerAppIdentityId
            });

            containerAppSecretsVolume.Secrets.Add(new SecretVolumeItem
            {
                Path = mapping.OriginalName.Replace("-", "."),
                SecretRef = mapping.Reference.SecretName.ToLowerInvariant()
            });
        }

        containerApp.Template.Containers[0].Value!.VolumeMounts.Add(new ContainerAppVolumeMount
        {
            VolumeName = volumeName,
            MountPath = mountPath
        });
        containerApp.Template.Volumes.Add(containerAppSecretsVolume);
    }

    public static IAzureKeyVaultSecretReference CreateSecretIfNotExists(
        IDistributedApplicationBuilder builder,
        IResourceBuilder<AzureKeyVaultResource> keyVault,
        string secretName)
    {
        var secretParameter = ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"param-{secretName}", special: false);
        builder.AddBicepTemplateString($"key-vault-key-{secretName}", """
                param location string = resourceGroup().location
                param keyVaultName string
                param secretName string
                @secure()
                param secretValue string    

                // Reference the existing Key Vault
                resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
                  name: keyVaultName
                }

                // Deploy the secret only if it does not already exist
                @onlyIfNotExists()
                resource newSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
                  parent: keyVault
                  name: secretName
                  properties: {
                      value: secretValue
                  }
                }
                """)
            .WithParameter("keyVaultName", keyVault.GetOutput("name"))
            .WithParameter("secretName", secretName)
            .WithParameter("secretValue", secretParameter);

        return keyVault.GetSecret(secretName);
    }
}

Je kunt dan deze methode gebruiken in het AppHost.cs bestand van je AppHost:

builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
       .WithHostStorage(storage)
       .WithExternalHttpEndpoints()
       .PublishWithContainerAppSecrets(systemKeyExtensionNames: ["mcp"]);

In dit voorbeeld wordt een standaardsleutelkluis gebruikt die is gemaakt met de extensiemethode. Dit resulteert in een standaardsleutel en een systeemsleutel voor gebruik met de extensie Model Context Protocol.

Als u deze sleutels van clients wilt gebruiken, moet u deze ophalen uit de sleutelkluis.

Implementeren als functie-app

Opmerking

Uitrollen als functionele app vereist de integratie van Aspire Azure App Service, die momenteel in preview is.

Je kunt Aspire configureren om te deployen naar een functie-app door gebruik te maken van de Aspire Azure App Service-integratie. Omdat Aspire het Functions-project als container uitrolt, moet het hostingplan voor je Function-app het uitrollen van containergeïntegreerde applicaties ondersteunen.

Om uw Aspire Functions-project als functionele app uit te rollen, volgt u deze stappen:

  1. Voer vanuit de AppHost-map aspire add Aspire.Hosting.Azure.AppService uit om het Aspire.Hosting.Azure.AppService-NuGet-pakket toe te voegen.
  2. Roep AppHost.cs in het AddAzureAppServiceEnvironment() bestand uw IDistributedApplicationBuilder exemplaar aan om een App Service-plan te maken. Houd er rekening mee dat er, ondanks de naam, geen App Service Environment-resource wordt ingericht.
  3. Gebruik de functies-projectresource om .WithExternalHttpEndpoints() aan te roepen. Dit is vereist voor implementatie met de Integratie van Aspire Azure App Service.
  4. Op de Functions-projectresource kunt u .PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux") aanroepen om dat project als functie-app binnen het plan te configureren.

Belangrijk

Zorg ervoor dat u de app.Kind eigenschap instelt op "functionapp,linux". Deze instelling zorgt ervoor dat de resource wordt gemaakt als een functie-app, wat van invloed is op ervaringen voor het werken met uw toepassing.

Het volgende voorbeeld toont een minimaal AppHost.cs bestand dat een Functions-project als een Function-app uitrolt:

var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureAppServiceEnvironment("functions-env");
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
    .WithExternalHttpEndpoints()
    .PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux");

builder.Build().Run();

Met deze configuratie maakt u een Premium V3-abonnement. Wanneer u een toegewezen App Service-plan-SKU gebruikt, is het schalen niet gebeurtenis-gerelateerd. In plaats daarvan wordt schalen beheerd via de App Service-planinstellingen.

Overwegingen en beste praktijken

Houd rekening met de volgende punten wanneer u de integratie van Azure Functions met Aspire evalueert:

  • De trigger- en bindingsconfiguratie via Aspire is momenteel beperkt tot specifieke integraties. Zie de verbindingsconfiguratie met Aspire in dit artikel voor meer informatie.

  • Het Program.cs bestand van uw functieproject moet de IHostApplicationBuilder versie voor hostexemplaar-opstart gebruiken. Door te gebruiken IHostApplicationBuilderkun je aanroepen builder.AddServiceDefaults() om Aspire Service Defaults toe te voegen aan je Functions-project.

  • Aspire maakt gebruik van OpenTelemetry voor bewaking. U kunt Aspire configureren voor het exporteren van gegevens naar Azure Monitor via het standaardproject voor de service.

    In veel andere Azure Functions-contexten kunt u directe integratie met Application Insights opnemen door de werkrolservice te registreren. Registreer geen tweede, directe Application Insights-pijplijn wanneer je Aspire Service Defaults gebruikt.

  • Voor Functions-projecten die zijn opgenomen in een Aspire-orkestratie, zou de AppHost de meeste applicatieconfiguraties moeten verzorgen. Je kunt local.settings.json gebruiken om het Functions-project onafhankelijk uit te voeren met func start. Wanneer Aspire het project uitvoert, overschrijven door Aspire geïnjecteerde omgevingsvariabelen waarden met dezelfde namen in local.settings.json.

  • Vermijd het starten van een tweede Azure Storage-emulator voor verbindingen die door de AppHost worden beheerd. Concurrerende emulator-instanties kunnen conflicten tussen poorten en opslag veroorzaken.

Voor meer informatie, zie Azure Functions runtime configuration en Aspire telemetry.