Share via


Azure Functions met Aspire (preview)

Aspire is een stack met een duidelijke visie die de ontwikkeling van gedistribueerde toepassingen in de cloud vereenvoudigt. Met de integratie van Aspire met Azure Functions kunt u een Azure Functions .NET-project ontwikkelen, fouten opsporen en organiseren als onderdeel van de Aspire-app-host.

Belangrijk

De integratie van Aspire met Azure Functions is momenteel in preview en kan worden gewijzigd.

Vereiste voorwaarden

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

  • Installeer de .NET 9 SDK en Aspire 9.0 of hoger. Hoewel de .NET 9 SDK is vereist, ondersteunt Aspire 9.0 de .NET 8- en .NET 9-frameworks.
  • Als u Visual Studio gebruikt, werkt u bij naar versie 17.12 of hoger. U moet ook de nieuwste versie van de Azure Functions-hulpprogramma's voor Visual Studio hebben. Controleren op 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.

Opmerking

De Integratie van Azure Functions met Aspire biedt nog geen ondersteuning voor .NET 10 Preview.

Oplossingsstructuur

Een oplossing die gebruikmaakt van Azure Functions en Aspire heeft meerdere projecten, waaronder een app-hostproject en een of meer Functions-projecten.

Het app-hostproject is het toegangspunt voor uw toepassing. 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.

App-hostproject

Als u de integratie wilt configureren, moet u ervoor zorgen dat het app-hostproject voldoet aan de volgende vereisten:

  • Het app-hostproject moet verwijzen naar Aspire.Hosting.Azure.Functions. Dit pakket definieert de benodigde logica voor de integratie.
  • Het app-hostproject moet een projectverwijzing hebben voor elk Functions-project dat u wilt opnemen in de indeling.
  • In het AppHost.cs bestand van de app-host moet u het project opnemen door AddAzureFunctionsProject<TProject>() aan te roepen op uw IDistributedApplicationBuilder exemplaar. U gebruikt deze methode in plaats van de AddProject<TProject>() methode die u gebruikt voor andere projecttypen in Aspire. Als u dit gebruikt AddProject<TProject>(), kan het Functions-project niet goed worden gestart.

In het volgende voorbeeld ziet u een minimaal AppHost.cs bestand voor een app-hostproject:

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.
  • Definieer geen aangepaste app-instellingen in het local.settings.json bestand voor het Functions-project. De enige instelling die in local.settings.json moet staan, is "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated". Stel alle andere app-configuraties in via het app-hostproject.

Verbindingsconfiguratie met Aspire

Het app-hostproject definieert resources en helpt u bij het maken van verbindingen tussen deze resources met behulp 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 u AddAzureFunctionsProject<TProject>() aanroept in uw app-host-project, wordt standaard een AzureWebJobsStorage verbinding gemaakt en beschikbaar gesteld aan het Functions-project. Deze standaardverbinding maakt gebruik van de Azure Storage-emulator voor uitvoeringen van lokale ontwikkeling en richt automatisch een opslagaccount in wanneer het wordt geïmplementeerd. Voor meer controle kunt u deze verbinding vervangen door de Functions-projectresource aan te roepen .WithHostStorage() .

De standaardmachtigingen die Aspire instelt voor de hostopslagverbinding, is afhankelijk van of u 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() Inzender voor opslagblobgegevens,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens,
Bijdrager aan opslagaccount
Roepen WithHostStorage() Inzender voor opslagblobgegevens,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens

In het volgende voorbeeld ziet u een minimaal AppHost.cs bestand voor een app-hostproject dat de hostopslag vervangt en een roltoewijzing opgeeft:

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 Inzender voor opslagblobgegevens,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens
Azure Queue Storage Inzender voor opslagblobgegevens,
Inzender voor opslagwachtrijgegevens,
Inzender voor opslagtabelgegevens
Azure Event Hubs Azure Event Hubs-gegevenseigenaar
Azure Service Bus Eigenaar van Azure Service Bus-gegevens

In het volgende voorbeeld ziet u een minimaal AppHost.cs bestand voor een app-hostproject waarmee een wachtrijtrigger wordt geconfigureerd. 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.

De toepassing hosten

Wanneer u een Azure Functions-project naar Azure publiceert, wordt het standaard geïmplementeerd in Azure Container Apps.

Tijdens de preview-periode bieden de container-app-resources geen ondersteuning voor gebeurtenisgestuurd schalen. Azure Functions-ondersteuning is niet beschikbaar voor apps die in deze modus zijn geïmplementeerd. Als u een ondersteuningsticket wilt openen, selecteert u het resourcetype Azure Container Apps.

Toegangssleutels

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 u een Functions-project implementeert met behulp van Aspire naar Azure Container Apps, maakt of beheert het systeem niet automatisch de toegangssleutels voor Functions. Als u toegangssleutels wilt gebruiken, kunt u deze beheren als onderdeel van de installatie van uw App Host. In deze sectie wordt beschreven hoe u een extensiemethode maakt die u kunt aanroepen vanuit het bestand van Program.cs de app-host om toegangssleutels 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 alleen beschikbaar is vanaf de hostversie van 4.1044.0Functions. Deze versie is nog niet beschikbaar in alle regio's, en totdat dit het geval is, kan de basisafbeelding die gebruikt wordt voor het Functions-project mogelijk niet de nodige wijzigingen bevatten wanneer u uw Aspire-project publiceert.

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 uw app-hostproject:

Maak een nieuwe klasse in uw app-hostproject 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);
    }
}

U kunt deze methode vervolgens gebruiken in het Program.cs-bestand van de app-host:

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.

Overwegingen en beste praktijken

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

  • Ondersteuning voor de integratie is momenteel beschikbaar als preview-versie.

  • 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. IHostApplicationBuilder hiermee kunt u aanroepen builder.AddServiceDefaults() om de standaardinstellingen van de Aspire-service toe te voegen aan uw 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. We raden dit soort integratie in Aspire niet aan. Dit probleem kan leiden tot runtimefouten met versie 2.22.0 van Microsoft.ApplicationInsights.WorkerService, hoewel versie 2.23.0 dit probleem verhelpt. Wanneer u Aspire gebruikt, verwijdert u alle directe Application Insights-integraties uit uw Functions-project.

  • Voor Functions-projecten die zijn opgenomen in een Aspire-indeling, moet de meeste toepassingsconfiguratie afkomstig zijn van het Hostproject van de Aspire-app. Vermijd het instellen van dingen in local.settings.json, behalve de FUNCTIONS_WORKER_RUNTIME instelling. Als u dezelfde omgevingsvariabele instelt in local.settings.json en Aspire, gebruikt het systeem de Aspire-versie.

  • Configureer de Azure Storage-emulator niet voor verbindingen in local.settings.json. Veel Functions-starterssjablonen bevatten de emulator als standaardinstelling voor AzureWebJobsStorage. De configuratie van de emulator kan echter sommige hulpprogramma's voor ontwikkelaars vragen om een versie van de emulator te starten die kan conflicteren met de versie die Aspire gebruikt.