Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Aspire es una cadena de herramientas para construir, ejecutar, depurar y desplegar aplicaciones distribuidas. La integración de Aspire Azure Functions te permite desarrollar, depurar y orquestar un proyecto de Azure Functions como parte de un Aspire AppHost. Los ejemplos de .NET en este artículo utilizan el modelo de trabajador aislado.
Prerequisites
Configure el entorno de desarrollo para usar Azure Functions con Aspire:
Instala los requisitos previos de Aspire, incluido el SDK .NET que requiere tu AppHost.
Instala la integración de alojamiento de Aspire Azure Functions desde el directorio de AppHost.
aspire add Aspire.Hosting.Azure.FunctionsInstale Azure Functions Core Tools.
Si usas Visual Studio, instala las últimas actualizaciones de herramientas de Visual Studio y Azure Functions:
- Vaya a Herramientas>.
- En Proyectos y soluciones, seleccione Azure Functions.
- Seleccione Buscar actualizaciones e instale las actualizaciones según se le solicite.
Para más información sobre el paquete de integración y las APIs de AppHost compatibles, consulte Configurar Azure Functions en el AppHost.
Estructura de la solución
Una solución que utiliza Azure Functions y Aspire tiene varios proyectos, incluyendo un AppHost y uno o más proyectos de Functions.
El AppHost es el punto de entrada para tu solicitud. Organiza la configuración de los componentes de la aplicación, incluido el proyecto de Functions.
Normalmente, la solución también incluye un proyecto configuración predeterminada del servicio. Este proyecto proporciona un conjunto de servicios y configuraciones predeterminados que se usarán en los proyectos de la aplicación.
Proyecto appHost
Para configurar correctamente la integración, asegúrese de que el proyecto AppHost cumpla con los siguientes requisitos:
- El AppHost hace referencia a Aspire.Hosting.Azure. Functions. Este paquete define la integración.
- Un AppHost de C# hace referencia a un proyecto de Functions y llama a
AddAzureFunctionsProject<TProject>(), o llama aAddAzureFunctionsProject(name, projectPath)con la ruta de acceso al archivo del proyecto. Los AppHosts de TypeScript utilizan el formato de ruta de proyecto deaddAzureFunctionsProject. - Use
AddAzureFunctionsProjecten lugar deAddProject. Un proyecto de Funciones añadido usandoAddProjectno puede arrancar correctamente.
El siguiente ejemplo muestra un archivo mínimo AppHost.cs para un proyecto AppHost en C#:
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject");
builder.Build().Run();
Proyecto de Azure Functions
Para configurar correctamente la integración, asegúrese de que el proyecto de Azure Functions cumple los siguientes requisitos:
Objetivo .NET 8 o posterior, utiliza el SDK .NET 9 o posterior, y utiliza el modelo de trabajador aislado.
Consulta Microsoft.Azure. Functions.Worker, Microsoft.Azure. Functions.Worker.Sdk y, para disparadores HTTP, Microsoft.Azure. Functions.Worker.Extensions.Http.AspNetCore.
El archivo
Program.csdebe usar la versiónIHostApplicationBuilderdel inicio de la instancia de host. Este requisito significa que debe usarFunctionsApplication.CreateBuilder(args).Si la solución incluye un proyecto predeterminado de servicio, asegúrese de que el proyecto de Functions esté configurado para usarlo:
- El proyecto de Functions debe incluir una referencia de proyecto al proyecto predeterminado del servicio.
- Antes de compilar
IHostApplicationBuilderenProgram.cs, incluya una llamada abuilder.AddServiceDefaults().
En el ejemplo siguiente se muestra un archivo mínimo Program.cs para un proyecto de Functions usado en Aspire:
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
var builder = FunctionsApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.ConfigureFunctionsWebApplication();
builder.Build().Run();
En este ejemplo no se incluye la configuración predeterminada de Application Insights que aparece en muchos otros Program.cs ejemplos y en las plantillas de Azure Functions. En su lugar, configurará la integración de OpenTelemetry en Aspire llamando al método builder.AddServiceDefaults().
Para sacar el máximo partido de la integración, tenga en cuenta las siguientes directrices:
- No incluya ninguna integración directa de Application Insights en el proyecto de Functions. La supervisión en Aspire, en lugar de eso, se controla a través de su compatibilidad con OpenTelemetry. Puede configurar Aspire para exportar datos a Azure Monitor a través del proyecto predeterminado del servicio.
- Cuando Aspire ejecuta el proyecto Functions, prefiero configuraciones inyectadas por el AppHost. Puedes mantener ajustes equivalentes en
local.settings.jsonpara ejecutar el proyecto de forma independiente confunc start; las variables de entorno inyectadas por Aspire prevalecen sobre ellos.
Configuración de conexión con Aspire
El AppHost define los recursos y te ayuda a crear conexiones entre ellos usando código. En esta sección se muestra cómo configurar y personalizar las conexiones que usa el proyecto de Azure Functions.
Aspire incluye permisos de conexión predeterminados que pueden ayudarle a empezar. Sin embargo, es posible que estos permisos no sean adecuados o suficientes para la aplicación.
En escenarios que usan el control de acceso basado en rol (RBAC) de Azure, puede personalizar los permisos llamando al WithRoleAssignments() método en el recurso del proyecto. Al llamar a WithRoleAssignments(), se eliminan todas las asignaciones de roles predeterminadas y debe definir explícitamente el conjunto completo de asignaciones de roles que desee. Si hospeda la aplicación en Azure Container Apps, el uso de WithRoleAssignments() también requiere que llame a AddAzureContainerAppEnvironment() en DistributedApplicationBuilder.
Almacenamiento de host de Azure Functions
Azure Functions requiere una conexión de almacenamiento de host (AzureWebJobsStorage) para varios de sus comportamientos principales. Cuando llamas AddAzureFunctionsProject<TProject>() a tu AppHost, creas una AzureWebJobsStorage conexión por defecto y la proporcionas al proyecto Functions. Esta conexión predeterminada utiliza el emulador de Azure Storage para las ejecuciones de desarrollo local y provisiona automáticamente una cuenta de almacenamiento cuando la despliegas. Para tener más control, sustituye esta conexión llamando .WithHostStorage() al recurso del proyecto Functions.
Los permisos predeterminados que Aspire establece para la conexión de almacenamiento del host dependen de si llamas WithHostStorage() o no. Agregar WithHostStorage() quita una asignación de colaborador de la cuenta de almacenamiento. En la tabla siguiente se enumeran los permisos predeterminados que Aspire establece para la conexión de almacenamiento del host:
| Conexión de almacenamiento del servidor | Roles predeterminados |
|---|---|
Sin llamada a WithHostStorage() |
Colaborador de datos de Storage Blob, Colaborador de datos de la cola de Storage, Colaborador de datos de tabla de Storage, Colaborador de la cuenta de almacenamiento |
Llamadas a WithHostStorage() |
Colaborador de datos de Storage Blob, Colaborador de datos de la cola de Storage, Contribuidor de datos de la tabla de almacenamiento |
El siguiente ejemplo muestra un archivo mínimo AppHost.cs que reemplaza el almacenamiento del anfitrión y especifica una asignación de roles:
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();
Nota:
El propietario de datos de blobs de almacenamiento es el rol que se recomienda para las necesidades básicas de la conexión de almacenamiento de host. Es posible que la aplicación encuentre problemas si la conexión con Blob service solo tiene el valor predeterminado de Aspire de colaborador de datos de blobs de almacenamiento.
En escenarios de producción, incluya llamadas a WithHostStorage() y WithRoleAssignments(). A continuación, puede establecer este rol explícitamente, junto con cualquier otro que necesite.
Conexiones de desencadenador y enlace
Los desencadenadores y enlaces hacen referencia a las conexiones por nombre. Las siguientes integraciones de Aspire proporcionan estas conexiones a través de una llamada a WithReference() en el recurso del proyecto:
El siguiente ejemplo muestra un archivo AppHost.cs mínimo que configura un desencadenador de cola. En este ejemplo, el desencadenador de cola correspondiente tiene su propiedad Connection establecida en MyQueueTriggerConnection, por lo que la llamada a WithReference() especifica el nombre.
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();
Para otras integraciones, las llamadas a WithReference establecen la configuración de otra manera. Hacen que la configuración esté disponible para las integraciones de cliente Aspire, pero no para desencadenadores y vinculaciones. Para estas integraciones, llame a WithEnvironment() para pasar la información de conexión para que el desencadenador o el enlace se resuelvan.
En el ejemplo siguiente se muestra cómo establecer la variable MyBindingConnection de entorno para un recurso que expone una expresión de cadena de conexión:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithEnvironment("MyBindingConnection", otherIntegration.Resource.ConnectionStringExpression);
Si desea que tanto las integraciones de cliente de Aspire como el sistema de desencadenadores y enlaces usen una conexión, puede configurar ambos WithReference() y WithEnvironment().
Para algunos recursos, la estructura de una conexión puede ser diferente entre la ejecución local y la publicación en Azure. En el ejemplo anterior, otherIntegration podría ser un recurso que se ejecuta como un emulador, por lo que ConnectionStringExpression devolvería una cadena de conexión del emulador. Sin embargo, cuando se publica el recurso, Aspire podría configurar una conexión basada en identidades y ConnectionStringExpression devolvería el URI del servicio. En este caso, para configurar conexiones basadas en identidades para Azure Functions, es posible que tenga que proporcionar un nombre de variable de entorno diferente.
En el ejemplo siguiente se usa builder.ExecutionContext.IsPublishMode para agregar condicionalmente el sufijo necesario:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithEnvironment("MyBindingConnection" + (builder.ExecutionContext.IsPublishMode ? "__serviceUri" : ""), otherIntegration.Resource.ConnectionStringExpression);
Para obtener más información sobre los formatos de conexión que admite cada enlace y los permisos que requieren esos formatos, consulte las páginas de referencia del enlace.
Para más información sobre cómo el código de Functions lee los valores inyectados por WithReference, véase configuración en tiempo de ejecución de Azure Functions.
Hospedaje de la aplicación
Aspire soporta el despliegue de Azure Container Apps para proyectos de Functions. También puede usar la integración independiente de la versión preliminar del App Service para dirigirle a una aplicación de función compatible con contenedores:
- Desplegar como una aplicación de contenedores
- Implementar como una aplicación de funciones mediante la integración en versión preliminar de App Service
En ambos casos, el proyecto se implementa como un contenedor. Aspire se encarga de compilar la imagen de contenedor para usted e insertarla en Azure Container Registry.
Desplegar como una aplicación en contenedor
Cuando tu AppHost se dirige a Azure Container Apps, Aspire establece reglas de escalado para tu proyecto de Funciones usando KEDA. Al usar Azure Container Apps, necesitas realizar una configuración extra para las teclas de función. Para más información, consulta Claves de acceso en Azure Container Apps.
Despliega el AppHost configurado ejecutando aspire deploy. Para más información, consulta Despliegue en Azure Container Apps y aspire deploy.
Claves de acceso en Azure Container Apps
Varios escenarios de Azure Functions usan claves de acceso para proporcionar una mitigación básica contra el acceso no deseado. Por ejemplo, las funciones de desencadenador HTTP de forma predeterminada requieren que se invoque una clave de acceso, aunque este requisito se puede deshabilitar mediante la AuthLevel propiedad . Consulte Trabajar con claves de acceso en Azure Functions para ver escenarios que pueden requerir una clave.
Cuando despliegas un proyecto de Functions usando Aspire en Azure Container Apps, el sistema no crea ni gestiona automáticamente las claves de acceso a Functions. Si necesitas usar claves de acceso, puedes gestionarlas como parte de tu configuración de AppHost. Esta sección te muestra cómo crear un método de extensión que puedas llamar desde el archivo de AppHost.cs tu AppHost para crear y gestionar claves de acceso. Este enfoque usa Azure Key Vault para almacenar las claves y montarlas en la aplicación contenedora como secretos.
Nota:
El comportamiento aquí depende del proveedor de secretos ContainerApps, que requiere la versión 4.1044.0 del host de Functions o posterior.
Estos pasos requieren la versión 0.38.3 de Bicep o posterior. Para comprobar la versión de Bicep, ejecute bicep --version desde una ventana de comandos. Si tiene instalada la CLI de Azure, puede usar az bicep upgrade para actualizar rápidamente Bicep a la versión más reciente.
Añade los siguientes paquetes NuGet a tu proyecto AppHost:
Crea una nueva clase en tu proyecto AppHost e incluye el siguiente código:
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);
}
}
Luego puedes usar este método en el archivo de AppHost.cs tu AppHost:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithHostStorage(storage)
.WithExternalHttpEndpoints()
.PublishWithContainerAppSecrets(systemKeyExtensionNames: ["mcp"]);
En este ejemplo se usa un almacén de claves predeterminado creado por el método de extensión. Da como resultado una clave predeterminada y una clave del sistema para su uso con la extensión Model Context Protocol.
Para usar estas claves que provienen de los clientes, debe obtenerlas del almacén de claves.
Desplegar como aplicación de funciones
Nota:
Desplegar como aplicación de funciones requiere la integración de Aspire Azure App Service, que actualmente está en vista previa.
Puedes configurar Aspire para realizar implementaciones en una aplicación de funciones mediante la integración de Azure App Service de Aspire. Como Aspire despliega el proyecto Functions como un contenedor, el plan de alojamiento de tu aplicación de funciones debe soportar el despliegue de aplicaciones contenedorizadas.
Para desplegar tu proyecto Aspire Functions como una aplicación funcional, sigue estos pasos:
- Desde el directorio AppHost, ejecuta
aspire add Aspire.Hosting.Azure.AppServicepara añadir el paquete NuGet de Aspire.Hosting.Azure. AppService. - En el archivo
AppHost.cs, llame aAddAzureAppServiceEnvironment()en la instanciaIDistributedApplicationBuilderpara crear un plan de App Service. Tenga en cuenta que, a pesar del nombre, esto no aprovisiona un recurso de App Service Environment. - En el recurso del proyecto Functions, llame a
.WithExternalHttpEndpoints(). Esto es necesario para la implementación con la integración de Aspire Azure App Service. - En el recurso del proyecto Functions, llama a
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux")para configurar ese proyecto como una aplicación de funciones en el plan.
Importante
Asegúrese de establecer la propiedad app.Kind a "functionapp,linux". Esta configuración garantiza que el recurso se crea como una aplicación de funciones, lo que afecta a las experiencias para trabajar con la aplicación.
El siguiente ejemplo muestra un archivo mínimo AppHost.cs que despliega un proyecto de Funciones como una aplicación de funciones:
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();
Esta configuración crea un plan Premium V3. Cuando se usa una SKU de plan de App Service dedicado, el escalado no se basa en eventos. En su lugar, el escalado se administra mediante la configuración del plan de App Service.
Consideraciones y procedimientos recomendados
Tenga en cuenta los siguientes puntos al evaluar la integración de Azure Functions con Aspire:
La configuración de activación y vinculación mediante Aspire está actualmente limitada a integraciones específicas. Para obtener más información, consulte Configuración de conexión con Aspire en este artículo.
El archivo
Program.csdebe utilizar la versiónIHostApplicationBuilderde inicio de instancia de host. UsandoIHostApplicationBuilder, puedes llamarbuilder.AddServiceDefaults()para añadir los valores predeterminados de Aspire Service a tu proyecto de Funciones.Aspire usa OpenTelemetry para la supervisión. Puede configurar Aspire para exportar datos a Azure Monitor a través del proyecto predeterminado del servicio.
En muchos otros contextos de Azure Functions, puede incluir la integración directa con Application Insights mediante el registro del servicio de trabajo. No registres una segunda pipeline directa de Application Insights cuando uses Aspire Service Defaults.
Para proyectos de Functions integrados en una orquestación de Aspire, AppHost debe proporcionar la mayor parte de la configuración de la aplicación. Puedes usar
local.settings.jsonpara ejecutar el proyecto Functions de forma independiente confunc start. Cuando Aspire ejecuta el proyecto, las variables de entorno inyectadas por Aspire sobrescriben valores con los mismos nombres enlocal.settings.json.Evita iniciar un segundo emulador de Azure Storage para conexiones que gestiona el AppHost. Las instancias de emuladores competidoras pueden causar conflictos entre puertos y almacenamiento.
Para más información, consulta configuración en tiempo de ejecución de Azure Functions y telemetría de Aspire.