Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Aspire — это стек с готовыми решениями, упрощающий разработку распределенных приложений в облаке. Интеграция Aspire с функциями Azure позволяет разрабатывать, отлаживать и оркестрировать проект Функций Azure .NET в составе узла приложения Aspire.
Предпосылки
Настройте среду разработки для использования Функций Azure с Aspire:
-
Установите необходимые условия для работы с приложением Aspire.
- Для полной поддержки интеграции в Azure Functions требуется Aspire версии 13.1 или более поздняя. Aspire 13.0 также содержит предварительную версию
Aspire.Hosting.Azure.Functions, которая служит релиз-кандидатом с поддержкой для ввода в эксплуатацию.
- Для полной поддержки интеграции в Azure Functions требуется Aspire версии 13.1 или более поздняя. Aspire 13.0 также содержит предварительную версию
- Установите основные средства Функций Azure.
Если вы используете Visual Studio, обновите до версии 17.12 или более поздней. Кроме того, у вас должна быть последняя версия средств Функций Azure для Visual Studio. Чтобы проверить наличие обновлений, выполните следующие действия.
- Выберите Tools>Options (Средства > Параметры).
- В разделе "Проекты и решения" выберите "Функции Azure".
- Выберите "Проверить наличие обновлений " и установить обновления по запросу.
Структура решения
Решение, использующее Функции Azure и Aspire, имеет несколько проектов, включая проект узла приложения и один или несколько проектов Функций.
Проект хоста приложения является точкой входа для вашего приложения. Он управляет настройкой компонентов приложения, включая проект "Функции".
Решение обычно также включает проект стандартных настроек службы. Этот проект предоставляет набор служб и конфигураций по умолчанию, используемых в проектах в приложении.
Проект хоста приложения
Чтобы успешно настроить интеграцию, убедитесь, что проект узла приложения соответствует следующим требованиям:
- Проект узла приложения должен ссылаться на Aspire.Hosting.Azure.Functions. Этот пакет определяет необходимую логику для интеграции.
- Проект узла приложения должен иметь ссылку на проект для каждого проекта Функций, который требуется включить в оркестрацию.
- В файле
AppHost.csузла приложения необходимо добавить проект, вызвав методAddAzureFunctionsProject<TProject>()на экземпляреIDistributedApplicationBuilder. Вы используете этот метод вместо методаAddProject<TProject>(), который вы применяете для других типов проектов в Aspire. При использованииAddProject<TProject>()проект "Функции" не может запуститься должным образом.
В следующем примере показан наименьший AppHost.cs файл для проекта хоста приложения.
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject");
builder.Build().Run();
Проект Функций Azure
Чтобы успешно настроить интеграцию, убедитесь, что проект Функций Azure соответствует следующим требованиям:
Проект "Функции" должен ссылаться на версии 2.xMicrosoft.Azure.Functions.Worker и Microsoft.Azure.Functions.Worker.Sdk. Кроме того, необходимо обновить все ссылки Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore на версию 2.x.
Файл
Program.csдолжен использоватьIHostApplicationBuilderверсию запуска экземпляра хоста. Это требование означает, что необходимо использоватьFunctionsApplication.CreateBuilder(args).Если решение включает проект службы по умолчанию, убедитесь, что проект Функций настроен для его использования:
- Проект "Функции" должен содержать ссылку на проект службы по умолчанию.
- Перед сборкой
IHostApplicationBuilderвProgram.csвключите вызовbuilder.AddServiceDefaults().
В следующем примере показан минимальный Program.cs файл для проекта Функций, используемого в Aspire:
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
var builder = FunctionsApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.ConfigureFunctionsWebApplication();
builder.Build().Run();
Этот пример не включает конфигурацию Application Insights по умолчанию, которая отображается во многих других Program.cs примерах и в шаблонах Функций Azure. Вместо этого вы настраиваете интеграцию OpenTelemetry в Aspire, вызывая метод builder.AddServiceDefaults.
Чтобы получить большую часть интеграции, ознакомьтесь со следующими рекомендациями.
- Не включайте в проект "Функции" прямые интеграции Application Insights. Мониторинг в Aspire вместо этого обрабатывается с помощью поддержки OpenTelemetry. Вы можете настроить Aspire для экспорта данных в Azure Monitor с помощью проекта службы по умолчанию.
- Не определяйте пользовательские параметры приложения в
local.settings.jsonфайле проекта "Функции". Единственный параметр, который должен находиться вlocal.settings.json, это"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated". Задайте все остальные конфигурации приложений через проект узла приложения.
Конфигурация подключения с помощью Aspire
Проект узла приложения определяет ресурсы и помогает создавать подключения между ними с помощью кода. В этом разделе показано, как настроить и кастомизировать подключения, используемые для Azure Functions в вашем проекте.
Aspire включает разрешения подключения по умолчанию, которые помогут вам приступить к работе. Однако эти разрешения могут быть не подходящими или достаточными для вашего приложения.
В сценариях, использующих управление доступом на основе ролей Azure (RBAC), можно настроить разрешения, вызвав WithRoleAssignments() метод в ресурсе проекта. При вызове WithRoleAssignments() удаляются все стандартные назначения ролей, и необходимо явно указать все назначения ролей, которые необходимы. Если вы размещаете приложение в Azure Container Apps, использование WithRoleAssignments() требует также вызова AddAzureContainerAppEnvironment() на DistributedApplicationBuilder.
Хранилище хоста Azure Functions
Функции Azure нуждаются в подключении к хранилищу хоста (AzureWebJobsStorage) для нескольких основных действий. При вызове AddAzureFunctionsProject<TProject>() в проекте AzureWebJobsStorage узла приложения подключение создается по умолчанию и предоставляется проекту "Функции". Это подключение по умолчанию использует эмулятор службы хранилища Azure для локальных запусков разработки и автоматически подготавливает учетную запись хранения при развертывании. Для большего контроля вы можете заменить это подключение, вызвав .WithHostStorage() в ресурсе проекта Функций.
Разрешения по умолчанию, заданные для подключения к хранилищу на хосте, зависят от того, вызываете ли вы WithHostStorage() отменяет назначение контрибутор учетной записи хранения. В следующей таблице перечислены разрешения, заданные Aspire по умолчанию для подключения к хранилищу узла.
| Подключение к хранилищу хоста | Роли по умолчанию |
|---|---|
Нет вызова WithHostStorage() |
Участник для данных BLOB-объектов хранилища, Участник для данных очереди хранилища, Участник данных таблицы хранилища, Соавтор учетной записи хранения |
Призвание WithHostStorage() |
Участник для данных BLOB-объектов хранилища, Участник для данных очереди хранилища, Сотрудник по работе с данными в таблицах хранилища |
В следующем примере показан минимальный AppHost.cs файл для проекта узла приложения, который заменяет хранилище узлов и указывает назначение роли:
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();
Замечание
Владелец данных объекта BLOB — это рекомендуемая роль для основных потребностей узла в подключении к хранилищу. Ваше приложение может столкнуться с проблемами, если подключение к службе BLOB-объектов имеет только вкладчика данных BLOB хранилища по умолчанию Aspire.
Для производственных сценариев включите вызовы обоих WithHostStorage() и WithRoleAssignments(). Затем эту роль можно задать явным образом, а также все остальные, которые вам нужны.
Подключения триггеров и биндинги
Триггеры и привязки ссылаются на подключения по имени. Следующие интеграции Aspire предоставляют эти подключения через вызов WithReference() ресурса проекта:
В следующем примере показан минимальный AppHost.cs файл для проекта узла приложения, который настраивает триггер очереди. В этом примере у соответствующего триггера очереди свойство Connection установлено в MyQueueTriggerConnection, поэтому вызов 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();
Для других интеграций вызовы к WithReference устанавливают конфигурацию по-другому. Они делают конфигурацию доступной для интеграции клиентов Aspire, но не для триггеров и привязок. Для этих интеграций вызовите WithEnvironment(), чтобы передать сведения о подключении, необходимые для разрешения триггера или привязки.
В следующем примере показано, как задать переменную MyBindingConnection среды для ресурса, предоставляющего выражение строки подключения:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithEnvironment("MyBindingConnection", otherIntegration.Resource.ConnectionStringExpression);
Если вы хотите, чтобы интеграции клиентов Aspire и система триггеров и привязок использовали подключение, можно настроить как WithReference(), так и WithEnvironment().
Для некоторых ресурсов структура подключения может различаться между запуском локально и публикацией в Azure. В предыдущем примере otherIntegration может быть ресурс, который работает как эмулятор, поэтому ConnectionStringExpression возвращает эмуляторную строку подключения. Однако, когда ресурс публикуется, Aspire может установить подключение на основе идентификации, и ConnectionStringExpression вернёт URI службы. В этом случае для настройки подключений на основе удостоверений для Функций Azure может потребоваться указать другое имя переменной среды.
В следующем примере builder.ExecutionContext.IsPublishMode условно добавляется необходимый суффикс:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithEnvironment("MyBindingConnection" + (builder.ExecutionContext.IsPublishMode ? "__serviceUri" : ""), otherIntegration.Resource.ConnectionStringExpression);
Дополнительные сведения о форматах соединений, поддерживаемых каждой привязкой, и разрешениях, необходимых этим форматам, см. на справочных страницах привязки.
Размещение приложения
Aspire поддерживает два разных способа размещения проекта Функций в Azure:
- Публикация в качестве приложения-контейнера (по умолчанию)
- Публикация в качестве приложения-функции с помощью интеграции службы приложений App Service в предварительной версии
В обоих случаях ваш проект развертывается в виде контейнера. Aspire заботится о создании образа контейнера для вас и отправке его в реестр контейнеров Azure.
Публикация в качестве приложения-контейнера
По умолчанию при публикации проекта Aspire в Azure он развертывается в приложениях контейнеров Azure. Система настраивает правила масштабирования для проекта "Функции" с помощью KEDA. При использовании приложений контейнеров Azure дополнительная настройка необходима для ключей функций. Дополнительные сведения см. в разделе "Ключи доступа" в приложениях контейнеров Azure .
Ключи доступа в приложениях контейнеров Azure
В нескольких сценариях Функций Azure используются ключи доступа, чтобы обеспечить базовое устранение нежелательных рисков. Например, функции триггера HTTP по умолчанию требуют вызова ключа доступа, хотя это требование можно отключить с помощью AuthLevel свойства. См. раздел "Работа с ключами доступа" в Функциях Azure для сценариев, для которых может потребоваться ключ.
При развертывании проекта Functions с использованием инструмента Aspire для Приложений контейнеров Azure, система не создает автоматически ключи доступа к функциям и не управляет ими. Если вам нужно использовать ключи доступа, вы можете управлять ими в рамках настройки узла приложений. В этом разделе показано, как создать метод расширения, который можно вызвать из файла узла Program.cs приложения для создания ключей доступа и управления ими. Этот подход использует Azure Key Vault для хранения ключей и интеграции их в контейнерное приложение в качестве секретов.
Замечание
В этом случае используется ContainerApps поставщик секретов, который доступен только начиная с версии 4.1044.0 узла Функций. Эта версия еще не доступна во всех регионах, и до тех пор, пока она не будет опубликована при публикации проекта Aspire, базовый образ, используемый для проекта Функций, может не включать необходимые изменения.
Для этих действий требуется версия 0.38.3 Bicep или более поздняя. Вы можете проверить версию Bicep, выполнив команду bicep --version из командной строки. Если у вас установлен Azure CLI, вы можете с помощью az bicep upgrade быстро обновить Bicep до последней версии.
Добавьте следующие пакеты NuGet в проект узла приложения:
Создайте класс в проекте узла приложения и добавьте следующий код:
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);
}
}
Затем вы можете использовать этот метод в файле вашего хост-приложения Program.cs.
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithHostStorage(storage)
.WithExternalHttpEndpoints()
.PublishWithContainerAppSecrets(systemKeyExtensionNames: ["mcp"]);
В этом примере используется хранилище ключей по умолчанию, созданное методом расширения. Он приводит к использованию ключа по умолчанию и системного ключа для использования с расширением протокола контекста модели.
Чтобы использовать эти ключи от клиентов, их необходимо извлечь из хранилища ключей.
Публикация в качестве функционального приложения
Замечание
Для публикации в качестве приложения-функции требуется интеграция Службы приложений Aspire Azure, которая в настоящее время находится в предварительной версии.
Вы можете настроить Aspire для развертывания в функции приложения, используя интеграцию Службы приложений Azure. Так как Aspire публикует проект Functions в качестве контейнера, план размещения для приложения-функции должен поддерживать развертывание контейнерных приложений.
Чтобы опубликовать проект Aspire Functions в качестве приложения-функции, выполните следующие действия.
- Добавьте ссылку на пакет Aspire.Hosting.Azure.AppService NuGet в проекте узла вашего приложения.
- В файле
AppHost.csвызовитеAddAzureAppServiceEnvironment()на экземпляреIDistributedApplicationBuilder, чтобы создать план приложения App Service. Обратите внимание, что, несмотря на имя, это не подготавливает ресурс среды службы приложений. - В ресурсе проекта "Функции" вызовите
.WithExternalHttpEndpoints(). Это необходимо для развертывания с помощью интеграции Службы приложений Azure Aspire. - На ресурсе проекта "Функции" вызовите
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux"), чтобы опубликовать этот проект в плане.
Это важно
Убедитесь, что вы установили свойство app.Kind в "functionapp,linux". Этот параметр гарантирует, что ресурс создается как функциональное приложение, что влияет на взаимодействие с вашим приложением.
В следующем примере показан минимальный AppHost.cs файл для проекта узла приложения, публикующего проект "Функции" в качестве приложения-функции:
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureAppServiceEnvironment("functions-env");
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithExternalHttpEndpoints()
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux");
По умолчанию эта конфигурация создает план Premium V3. При использовании SKU выделенного плана службы приложений масштабирование не основано на событиях. Вместо этого масштабирование осуществляется с помощью параметров плана службы приложений. Если вы хотите изменить SKU, это можно сделать с помощью метода ConfigureInfrastructure в ресурсе среды. В следующем примере показано, как настроить план Elastic Premium:
using Azure.Provisioning.AppService;
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureAppServiceEnvironment("functions-env");
.ConfigureInfrastructure(infra =>
{
var plan = infra.GetProvisionableResources().OfType<AppServicePlan>().First();
plan.Sku = new AppServiceSkuDescription
{
Name = "EP1",
Tier = "ElasticPremium"
};
});
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithExternalHttpEndpoints()
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux");
При настройке плана использования SKU Elastic Premium можно публиковать только проекты Функций в этом плане. Если проект узла приложения включает другие веб-приложения, следует использовать номер SKU выделенного плана службы приложений.
Соображения и передовой опыт
Рассмотрим следующие моменты при оценке интеграции функций Azure с Aspire:
Настройка триггеров и связывания через Aspire в настоящее время ограничена определенными интеграциями. Дополнительные сведения см. в разделе "Конфигурация подключения" с помощью Aspire в этой статье.
Файл проекта
Program.csфункции должен использоватьIHostApplicationBuilderверсию запуска экземпляра хоста.IHostApplicationBuilderпозволяет вам вызватьbuilder.AddServiceDefaults(), чтобы добавить службу Aspire по умолчанию в ваш проект функции.Aspire использует OpenTelemetry для мониторинга. Вы можете настроить Aspire для экспорта данных в Azure Monitor с помощью проекта службы по умолчанию.
Во многих других контекстах Функций Azure можно включить прямую интеграцию с Application Insights, зарегистрируя рабочую службу. Мы не рекомендуем эту интеграцию в Aspire. Это может привести к ошибкам среды выполнения с версией 2.22.0
Microsoft.ApplicationInsights.WorkerService, хотя версия 2.23.0 устраняет эту проблему. При использовании Aspire удалите все прямые интеграции Application Insights из проекта "Функции".Для проектов "Функции", включенных в оркестрацию Aspire, основная часть конфигурации приложения должна поступать из проекта хоста приложения Aspire. Избегайте настройки элементов в
local.settings.json, кроме настройкиFUNCTIONS_WORKER_RUNTIME. Если задать ту же переменную среды вlocal.settings.jsonи Aspire, система использует версию Aspire.Не настраивайте эмулятор службы хранилища Azure для каких-либо подключений в
local.settings.json. Многие шаблоны начальных функций включают эмулятор в качестве значения по умолчаниюAzureWebJobsStorage. Однако конфигурация эмулятора может привести к тому, что некоторые инструменты разработчика запустят версию эмулятора, конфликтующую с версией, которую использует Aspire.