Sdílet prostřednictvím


Azure Functions v kombinaci s Aspire

Aspire je názorově zaměřený stack, který zjednodušuje vývoj distribuovaných aplikací v cloudu. Integrace Aspire s Azure Functions umožňuje vyvíjet, ladit a orchestrovat projekt Azure Functions .NET jako součást hostitele aplikace Aspire.

Požadavky

Nastavení vývojového prostředí pro použití Azure Functions s Aspire:

Pokud používáte Visual Studio, aktualizujte na verzi 17.12 nebo novější. Musíte mít také nejnovější verzi nástrojů Azure Functions pro Visual Studio. Kontrola aktualizací:

  1. Přejděte naMožnosti>.
  2. V části Projekty a řešení vyberte Azure Functions.
  3. Podle výzvy vyberte Vyhledat aktualizace a nainstalovat aktualizace.

Struktura řešení

Řešení, které používá Azure Functions a Aspire, má několik projektů, včetně hostitelského projektu aplikace a jednoho nebo více projektů Functions.

Projekt hostitele aplikace je vstupním bodem vaší aplikace. Orchestruje nastavení komponent aplikace, včetně projektu Functions.

Řešení obvykle zahrnuje také výchozí projekt služby . Tento projekt poskytuje sadu výchozích služeb a konfigurací, které se mají použít napříč projekty ve vaší aplikaci.

Projekt hostingu aplikace

Pokud chcete úspěšně nakonfigurovat integraci, ujistěte se, že hostitelský projekt aplikace splňuje následující požadavky:

  • Projekt hostitele aplikace musí odkazovat na Aspire.Hosting.Azure.Functions. Tento balíček definuje potřebnou logiku pro integraci.
  • Hostitelský projekt aplikace musí mít odkaz na projekt pro každý projekt Functions, který chcete zahrnout do orchestrace.
  • V souboru AppHost.cs hostitele aplikace musíte voláním na instanci AddAzureFunctionsProject<TProject>() zahrnout projekt IDistributedApplicationBuilder. Tuto metodu AddProject<TProject>() použijete místo metody, kterou používáte pro jiné typy projektů v Aspire. Pokud používáte AddProject<TProject>(), projekt Functions se nemůže správně spustit.

Následující příklad ukazuje minimální AppHost.cs soubor pro hostitelský projekt aplikace:

var builder = DistributedApplication.CreateBuilder(args);

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

builder.Build().Run();

Projekt Azure Functions

Pokud chcete úspěšně nakonfigurovat integraci, ujistěte se, že projekt Azure Functions splňuje následující požadavky:

Následující příklad ukazuje minimální Program.cs soubor pro projekt Functions použitý v Aspire:

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

var builder = FunctionsApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.ConfigureFunctionsWebApplication();

builder.Build().Run();

Tento příklad neobsahuje výchozí konfiguraci Application Insights, která se zobrazí v mnoha dalších Program.cs příkladech a v šablonách Azure Functions. Místo toho nakonfigurujete integraci OpenTelemetry v Aspire voláním builder.AddServiceDefaults metody.

Pokud chcete využít integraci na maximum, zvažte následující pokyny:

  • Do projektu Functions nezahrnujte žádné přímé integrace Application Insights. Monitorování v Aspire se místo toho zpracovává prostřednictvím podpory OpenTelemetry. Aspire můžete nakonfigurovat tak, aby exportoval data do služby Azure Monitor prostřednictvím výchozího projektu služby.
  • Nedefinujte vlastní nastavení aplikace v local.settings.json souboru pro projekt Functions. Jediné nastavení, které by mělo být v local.settings.json je "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated". Nastavte všechny ostatní konfigurace aplikací prostřednictvím hostitelského projektu aplikace.

Konfigurace připojení pomocí Aspire

Projekt hostitele aplikace definuje prostředky a pomáhá vytvářet propojení mezi nimi pomocí kódu. Tato část ukazuje, jak nakonfigurovat a přizpůsobit připojení, která váš projekt Azure Functions používá.

Aspire obsahuje výchozí oprávnění připojení, která vám můžou pomoct začít. Tato oprávnění ale nemusí být pro vaši aplikaci vhodná nebo dostatečná.

Ve scénářích, které používají řízení přístupu na základě role v Azure (RBAC), můžete oprávnění přizpůsobit voláním WithRoleAssignments() metody pro prostředek projektu. Při volání WithRoleAssignments()se odeberou všechna výchozí přiřazení rolí a musíte explicitně definovat požadovaná přiřazení rolí úplné sady. Pokud hostujete aplikaci ve službě Azure Container Apps, je nutné použít WithRoleAssignments() a volat AddAzureContainerAppEnvironment() na DistributedApplicationBuilder.

Hostitelské úložiště Azure Functions

Azure Functions vyžaduje připojení k úložišti hostitele (AzureWebJobsStorage) pro několik základních chování. Při volání AddAzureFunctionsProject<TProject>() ve vašem hostitelském projektu aplikace se ve výchozím nastavení vytvoří připojení, které je poskytnuto projektu Functions. Toto výchozí připojení používá emulátor služby Azure Storage pro spuštění místního vývoje a automaticky zřídí účet úložiště při nasazení. Pokud chcete mít větší kontrolu, můžete toto připojení nahradit voláním .WithHostStorage() zdroje projektu Functions.

Výchozí oprávnění, která Aspire nastaví pro připojení k úložišti hostitele, závisí na tom, jestli voláte WithHostStorage() nebo ne. Přidáním WithHostStorage() se odebere přiřazení přispěvatele účtu úložiště. Následující tabulka uvádí výchozí oprávnění, která Aspire nastaví pro připojení k úložišti hostitele:

Připojení k úložišti hostitele Výchozí role
Žádný hovor na WithHostStorage() Přispěvatel dat objektů blob služby Storage
Přispěvatel dat fronty služby Storage
Přispěvatel dat tabulky služby Storage
Přispěvatel ke službě úložiště
Volání WithHostStorage() Přispěvatel dat objektů blob služby Storage
Přispěvatel dat fronty služby Storage
Přispěvatel dat tabulky úložiště

Následující příklad ukazuje minimální AppHost.cs soubor pro hostitelský projekt aplikace, který nahrazuje úložiště hostitele a určuje přiřazení role:

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

Poznámka:

Vlastník dat objektů blob služby Storage je role, kterou doporučujeme pro základní potřeby připojení hostitele k úložišti. Aplikace může narazit na problémy, pokud připojení ke službě blob má pouze výchozí roli Storage Blob Data Contributor.

V produkčních scénářích uveďte volání jak WithHostStorage(), tak WithRoleAssignments(). Tuto roli pak můžete explicitně nastavit společně s ostatními, které potřebujete.

Připojení spouštěče a vazby

Vaše spouštěče a vazby odkazují na připojení podle jména. Následující integrace Aspire poskytují tato připojení prostřednictvím volání na WithReference() zdroje projektu:

Integrace Aspire Výchozí role
Azure Blob Storage Přispěvatel dat objektů blob služby Storage
Přispěvatel dat fronty služby Storage
Přispěvatel dat tabulky úložiště
Azure Queue Storage Přispěvatel dat objektů blob služby Storage
Přispěvatel dat fronty služby Storage
Přispěvatel dat tabulky úložiště
Azure Event Hubs Vlastník dat služby Azure Event Hubs
Azure Service Bus Vlastník dat služby Azure Service Bus

Následující příklad ukazuje minimální AppHost.cs soubor pro hostitelský projekt aplikace, který konfiguruje spouštěč fronty. V tomto příkladu má odpovídající aktivační událost fronty vlastnost Connection nastavenu na MyQueueTriggerConnection, takže volání specifikuje název WithReference().

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

Při jiných integracích se volání k nastavení WithReference konfigurace provádějí odlišně. Zpřístupní konfiguraci integrací klientů Aspire, ale ne triggerům a vazbám. U těchto integrací zavolejte WithEnvironment() pro zpracování informací o připojení s cílem vyřešení triggeru nebo vazby.

Následující příklad ukazuje, jak nastavit proměnnou MyBindingConnection prostředí pro prostředek, který zveřejňuje výraz připojovacího řetězce:

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

Pokud chcete, aby integrace klienta Aspire i systém triggerů a vazeb používaly připojení, můžete nakonfigurovat obojí WithReference() i WithEnvironment().

U některých prostředků se struktura připojení může lišit mezi tím, kdy ho spouštíte místně a kdy ho publikujete do Azure. V předchozím příkladu může být otherIntegration prostředek, který běží jako emulátor, takže ConnectionStringExpression by vrátil připojovací řetězec emulátoru. Když je prostředek publikován, Aspire však může nastavit připojení založené na identitě a ConnectionStringExpression poskytnout URI služby. V tomto případě může být při nastavování připojení založených na identitě pro Azure Functions potřeba zadat jiný název proměnné prostředí.

Následující příklad používá builder.ExecutionContext.IsPublishMode k podmíněnému přidání nezbytné přípony:

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

Podrobnosti o formátech připojení, které jednotlivé vazby podporují, a oprávnění, která tyto formáty vyžadují, najdete na referenčních stránkách vazby.

Hostování aplikace

Aspire podporuje dva různé způsoby hostování projektu Functions v Azure:

V obou případech se váš projekt nasadí jako kontejner. Aspire se postará o vytvoření image kontejneru za vás a nasdílí ji do služby Azure Container Registry.

Publikování jako aplikace typu kontejner

Když publikujete projekt Aspire do Azure, ve výchozím nastavení se nasadí do Azure Container Apps. Systém nastaví pravidla škálování pro váš projekt Functions pomocí KEDA. Pokud používáte Azure Container Apps, je potřeba další nastavení pro klíče funkcí. Další informace najdete v tématu Přístupové klíče v Azure Container Apps .

Přístupové klíče v Azure Container Apps

Několik scénářů Azure Functions používá přístupové klíče k zajištění základního omezení rizik proti nežádoucímu přístupu. Například funkce triggeru HTTP ve výchozím nastavení vyžadují vyvolání přístupového klíče, i když tento požadavek může být zakázán pomocí AuthLevel vlastnosti. Scénáře, které můžou vyžadovat klíč, najdete v tématu Práce s přístupovými klíči ve službě Azure Functions .

Když nasadíte projekt Functions pomocí Aspire do Azure Container Apps, systém automaticky nevytváří ani nespravuje přístupové klíče Functions. Pokud potřebujete používat přístupové klíče, můžete je spravovat v rámci nastavení hostitele aplikací. V této části se dozvíte, jak vytvořit metodu rozšíření, kterou můžete volat ze souboru hostitele Program.cs aplikace za účelem vytvoření a správy přístupových klíčů. Tento přístup používá Azure Key Vault k ukládání klíčů a jejich připojení do aplikace kontejneru jako tajných kódů.

Poznámka:

Toto chování závisí na poskytovateli tajemství ContainerApps, který je k dispozici pouze od verze 4.1044.0 hostitele Functions. Tato verze ještě není k dispozici ve všech oblastech a dokud nebude, když publikujete projekt Aspire, nemusí základní image použitá pro projekt Functions obsahovat potřebné změny.

Tyto kroky vyžadují verzi 0.38.3 Bicep nebo novější. Verzi Bicep můžete zkontrolovat spuštěním bicep --version z příkazového řádku. Pokud máte nainstalované Rozhraní příkazového řádku Azure, můžete použít az bicep upgrade k rychlé aktualizaci Bicep na nejnovější verzi.

Do hostitelského projektu aplikace přidejte následující balíčky NuGet:

Vytvořte novou třídu v hostitelském projektu aplikace a zadejte následující kód:

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

Tuto metodu Program.cs pak můžete použít v souboru hostitele aplikace:

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

Tento příklad používá výchozí trezor klíčů vytvořený metodou rozšíření. Výsledkem je výchozí klíč a systémový klíč pro použití s rozšířením Model Context Protocol.

Pokud chcete tyto klíče používat z klientů, musíte je načíst z klíčového úložiště.

Publikovat jako aplikaci funkcí

Poznámka:

Publikování jako aplikace funkcí vyžaduje integraci služby Aspire Azure App Service, která je aktuálně ve verzi Preview.

Aspire můžete nakonfigurovat tak, aby se nasadil do aplikace funkcí pomocí integrace služby Aspire Azure App Service. Vzhledem k tomu, že Aspire publikuje projekt Functions jako kontejner, musí plán hostování vaší aplikace funkcí podporovat nasazování kontejnerizovaných aplikací.

Pokud chcete publikovat projekt Aspire Functions jako aplikaci funkcí, postupujte takto:

  1. Přidejte do hostitelského projektu aplikace odkaz na balíček NuGet Aspire.Hosting.Azure.AppService .
  2. V souboru AppHost.cs zavolejte AddAzureAppServiceEnvironment() na instanci IDistributedApplicationBuilder, abyste vytvořili plán služby App Service. Všimněte si, že bez ohledu na název to nezřizuje prostředek služby App Service Environment.
  3. Na zdroji projektu Functions volejte .WithExternalHttpEndpoints(). To se vyžaduje pro nasazení s integrací služby Aspire Azure App Service.
  4. Na zdroji projektu Functions zavolejte .PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux") , aby se projekt publikoval do plánu.

Důležité

Ujistěte se, že nastavíte vlastnost app.Kind na "functionapp,linux". Toto nastavení zajistí, že se prostředek vytvoří jako aplikace funkcí, což má vliv na prostředí pro práci s vaší aplikací.

Následující příklad ukazuje minimální AppHost.cs soubor pro hostitelský projekt aplikace, který publikuje projekt Functions jako aplikaci funkcí:

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

Tato konfigurace vytvoří plán Premium V3. Pokud používáte skladovou položku vyhrazeného plánu služby App Service, škálování není založené na událostech. Škálování se místo toho spravuje prostřednictvím nastavení plánu služby App Service.

Důležité informace a osvědčené postupy

Při vyhodnocování integrace azure Functions s Aspire zvažte následující body:

  • Konfigurace triggerů a vazeb prostřednictvím Aspire se momentálně omezuje na konkrétní integrace. Podrobnosti najdete v části Konfigurace připojení s Aspire v tomto článku.

  • Soubor ve vašem projektu funkce by měl používat verzi pro spuštění instance hostitele . IHostApplicationBuilder umožňuje volat funkci builder.AddServiceDefaults() pro přidání výchozích hodnot služby Aspire do projektu Function.

  • Aspire používá k monitorování OpenTelemetry. Aspire můžete nakonfigurovat tak, aby exportoval data do služby Azure Monitor prostřednictvím výchozího projektu služby.

    V mnoha dalších kontextech Azure Functions můžete zahrnout přímou integraci s Application Insights registrací pracovní služby. Tento druh integrace do Aspire nedoporučujeme. Může vést k chybám za běhu verze 2.22.0 z Microsoft.ApplicationInsights.WorkerService, i když verze 2.23.0 tento problém řeší. Pokud používáte Aspire, odeberte z projektu Functions všechny přímé integrace Application Insights.

  • U projektů Functions zařazených do orchestrace Aspire by většina konfigurace aplikace měla pocházet z hostitelského projektu aplikace Aspire. Vyhněte se nastavování položek v local.settings.json, kromě nastavení FUNCTIONS_WORKER_RUNTIME. Pokud nastavíte stejnou proměnnou prostředí v local.settings.json a Aspire, systém použije verzi Aspire.

  • Nenakonfigurujte emulátor služby Azure Storage pro žádná připojení v local.settings.json. Mnoho úvodních šablon Functions zahrnuje emulátor jako výchozí nastavení pro AzureWebJobsStorage. Konfigurace emulátoru ale může vyzvat některé vývojářské nástroje ke spuštění verze emulátoru, která může být v konfliktu s verzí, kterou Aspire používá.