Aspire 是一個固執己見的堆棧,可簡化雲中分佈式應用程序的開發。 Aspire 與 Azure Functions 的整合可讓您開發、偵錯及協調 Azure Functions .NET 專案,作為 Aspire 應用程式主機的一部分。
先決條件
設定您的開發環境,以搭配 Aspire 使用 Azure Functions:
-
安裝 Aspire 先決條件。
- Azure Functions 整合的完整支援需要 Aspire 13.1 或更新版本。 Aspire 13.0 還包括
Aspire.Hosting.Azure.Functions的預覽版,可作為具有上線支援的候選版本。
- Azure Functions 整合的完整支援需要 Aspire 13.1 或更新版本。 Aspire 13.0 還包括
- 安裝 Azure Functions 核心工具。
如果您使用 Visual Studio,請更新至 17.12 版或更新版本。 您也必須擁有適用於 Visual Studio 的 Azure Functions 工具最新版本。 若要檢查更新:
- 移至 [工具]>[選項]。
- 在 [項目和解決方案] 底下,選取 [Azure Functions]。
- 選取 [檢查更新],並依提示安裝更新。
解決方案結構
使用 Azure Functions 和 Aspire 的解決方案有多個專案,包括 應用程式主機專案 和一或多個 Functions 專案。
應用程式主機專案是您的應用程式進入點。 它會協調應用程式的元件設定,包括 Functions 專案。
解決方案通常也包含 服務預設 專案。 此專案提供一組預設服務和組態,可在應用程式中的項目之間使用。
應用程式主機專案
若要成功設定整合,請確定應用程式主機專案符合下列需求:
- 應用程式主機項目必須參考 Aspire.Hosting.Azure.Functions。 此套件會定義整合的必要邏輯。
- 應用程式宿主專案必須有您想要包含在編排中的每個 Functions 專案的專案參考。
- 在應用程式主機的
AppHost.cs檔案中,您必須在AddAzureFunctionsProject<TProject>()實例上呼叫IDistributedApplicationBuilder以包含專案。 您應該使用此方法,而非使用您在 Aspire 中對其他專案類型所使用的AddProject<TProject>()方法。 如果您使用AddProject<TProject>(),則 Functions 項目無法正確啟動。
下列範例顯示應用程式主機專案的最小 AppHost.cs 檔案:
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject");
builder.Build().Run();
Azure Functions 專案
若要成功設定整合,請確定 Azure Functions 專案符合下列需求:
Functions 專案必須參考 Microsoft.Azure.Functions.Worker 和 Microsoft.Azure.Functions.Worker.Sdk 的 2.x 版本。 您也必須將任何 Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore 參考更新到 2.x 版。
您的
Program.cs檔案必須使用IHostApplicationBuilder的 版本。 這項需求表示您必須使用FunctionsApplication.CreateBuilder(args)。如果您的方案包含服務預設專案,請確定您的 Functions 專案已設定為使用它:
- Functions 專案應該包含對服務預設值專案的參考。
- 在
IHostApplicationBuilder中建置Program.cs之前,請先呼叫builder.AddServiceDefaults()。
下列範例顯示 Aspire 中使用的 Functions 專案的最小 Program.cs 檔案:
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
var builder = FunctionsApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.ConfigureFunctionsWebApplication();
builder.Build().Run();
此範例不包含出現在許多其他 Program.cs 範例和 Azure Functions 範本中的預設 Application Insights 組態。 相反地,您可以呼叫 builder.AddServiceDefaults 方法,在 Aspire 中設定 OpenTelemetry 整合。
若要充分利用整合,請考慮下列指導方針:
- 請勿在 Functions 專案中包含任何直接的 Application Insights 整合。 Aspire 中的監控則會經由其 OpenTelemetry 支援來處理。 您可以將 Aspire 設定為透過服務預設專案將資料匯出至 Azure 監視器。
- 請勿在 Functions 專案的檔案中
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 Functions 需要主機儲存體連線 (AzureWebJobsStorage)以執行多項核心功能。 當您在應用程式主專案中呼叫 AddAzureFunctionsProject<TProject>() 時, AzureWebJobsStorage 預設會建立連線,並提供給 Functions 專案。 此預設連線會使用 Azure 儲存體模擬器進行本機開發時的執行,並在部署時自動布建儲存帳戶。 如需更多控制,您可以在 Functions 專案資源上呼叫 .WithHostStorage() 來取代此連線。
Aspire 為主機儲存連線設定的預設權限取決於您是否呼叫 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 服務的連線僅依賴 Aspire 預設值 Storage Blob 資料參與者,您的應用程式可能會遇到問題。
在生產情境中,包含呼叫WithHostStorage()和WithRoleAssignments()。 然後,您可以明確地設定此角色,以及您需要的任何其他角色。
繫結程序與繫結連線
您的觸發程序和繫結會依名稱參考連線。 下列 Aspire 整合會透過專案資源的WithReference()呼叫來提供這些連線:
| Aspire 整合 | 預設角色 |
|---|---|
| Azure Blob 儲存服務 |
儲存體 Blob 資料參與者、 儲存體佇列資料參與者、 儲存體資料表資料參與者 |
| Azure 佇列儲存體 |
儲存體 Blob 資料參與者、 儲存體佇列資料參與者、 儲存體資料表資料參與者 |
| Azure 事件中樞 | Azure 事件中樞資料擁有者 |
| Azure 服務總線 | Azure 服務匯流排資料擁有者 |
下列範例顯示一個應用程式主機專案的最小 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 Functions 的身分識別型連線,您可能需要提供不同的環境變數名稱。
下列範例會使用 builder.ExecutionContext.IsPublishMode 來有條件地新增必要的後綴:
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithEnvironment("MyBindingConnection" + (builder.ExecutionContext.IsPublishMode ? "__serviceUri" : ""), otherIntegration.Resource.ConnectionStringExpression);
如需每個系結所支援連接格式的詳細數據,以及這些格式所需的許可權,請參閱系結的 參考頁面。
裝載應用程式
Aspire 支援兩種不同的方式,在 Azure 中裝載 Functions 專案:
- 發佈為容器應用程式 (預設)
- 使用預覽版 App Service 整合,以函式應用程式的形式發佈
在這兩種情況下,您的專案都會部署為容器。 Aspire 會為您建置容器映像,並將它推送至 Azure 容器登錄。
發佈為容器應用程式
根據預設,當您將 Aspire 專案發佈至 Azure 時,它會部署至 Azure 容器應用程式。 系統會使用 KEDA 為您的 Functions 專案設定擴展規則。 使用 Azure Container Apps 時,功能鍵需要額外的設定。 如需詳細資訊,請參閱 Azure 容器應用程式上的存取金鑰 。
Azure 容器應用程式上的存取金鑰
數個 Azure Functions 案例會使用存取金鑰來針對不想要的存取提供基本風險降低。 例如,預設情況下,HTTP 觸發函數需要呼叫存取金鑰,但可以使用屬性停用AuthLevel此需求。 如需可能需要金鑰的案例,請參閱在 Azure Functions 中使用存取金鑰 。
當您使用 Aspire 將 Functions 專案部署至 Azure Container Apps 時,系統不會自動建立或管理 Functions 存取金鑰。 如果您需要使用存取金鑰,可以在應用程式主機設定中進行管理。 本節說明如何建立擴充功能方法,您可以從應用程式主機的 Program.cs 檔案呼叫該方法,以建立和管理存取金鑰。 此方法會使用 Azure 金鑰保存庫來儲存金鑰,並將其掛接到容器應用程式中做為秘密。
備註
這裡的行為依賴 ContainerApps 秘密提供者,該提供者僅適用於從 Functions 主機版本 4.1044.0開始。 此版本尚未在所有地區提供,在發佈 Aspire 專案之前,用於 Functions 專案的基礎映像可能不會包含必要的變更。
這些步驟需要 Bicep 版本 0.38.3 或更新版本。 您可以在命令提示字元中執行 bicep --version 來檢查 Bicep 版本。 如果您已安裝 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 App Service 整合 (目前處於預覽狀態)。
您可以使用 Aspire Azure App Service 整合,將 Aspire 設定為部署至函式應用程式。 由於 Aspire 會將 Functions 專案發佈為容器,因此函式應用程式的裝載計劃必須支援部署容器化應用程式。
若要將 Aspire Functions 專案發佈為函式應用程式,請遵循下列步驟:
- 在應用程式主機專案中新增 Aspire.Hosting.Azure.AppService NuGet 套件的參考。
- 在
AppHost.cs檔案中,呼叫AddAzureAppServiceEnvironment()執行個體上的IDistributedApplicationBuilder以建立 App Service 方案。 請注意,儘管名稱如此,但這不會佈建 App Service 環境資源。 - 在專案功能資源上,呼叫
.WithExternalHttpEndpoints()。 這是使用 Aspire Azure App Service 整合進行部署的必要條件。 - 在 Functions 專案資源上,呼叫
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux")以將該專案發佈至計劃。
這很重要
請確定您將 app.Kind 屬性設為 "functionapp,linux"。 此設定可確保將資源建立為函式應用程式,這會影響使用應用程式的體驗。
下列範例顯示應用程式主機專案的最小 AppHost.cs 檔案,該專案會將 Functions 專案發佈為函式應用程式:
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureAppServiceEnvironment("functions-env");
builder.AddAzureFunctionsProject<Projects.MyFunctionsProject>("MyFunctionsProject")
.WithExternalHttpEndpoints()
.PublishAsAzureAppServiceWebsite((infra, app) => app.Kind = "functionapp,linux");
此配置會產生 Premium V3 方案。 使用專用 App Service 方案 SKU 時,調整不是以事件為基礎。 相反地,縮放是透過 App Service 計劃設定來管理。
考量和最佳做法
當您評估 Azure Functions 與 Aspire 的整合時,請考慮下列幾點:
透過 Aspire 進行的觸發和繫結設定目前僅限於特定整合。 如需詳細資訊,請參閱本文中的 與 Aspire 的連線設定 。
函式專案的
Program.cs檔案應該使用IHostApplicationBuilder的 版本。IHostApplicationBuilder可讓您呼叫builder.AddServiceDefaults()將 Aspire 服務預設值 新增至 Functions 專案。Aspire 使用 OpenTelemetry 進行監控。 您可以將 Aspire 設定為透過服務預設專案將資料匯出至 Azure 監視器。
在許多其他 Azure Functions 的情境中,您可能會藉由註冊背景工作角色服務直接整合 Application Insights。 我們不建議在 Aspire 中進行這種整合。 這可能會導致 2.22.0 版的
Microsoft.ApplicationInsights.WorkerService運行時間錯誤,但 2.23.0 版可解決此問題。 當您使用 Aspire 時,請從 Functions 專案中移除任何直接的 Application Insights 整合。針對已登記至 Aspire 協調流程的 Functions 專案,大部分的應用程式設定都應該來自 Aspire 應用程式主機專案。 避免在
local.settings.json進行設定,除了FUNCTIONS_WORKER_RUNTIME設定以外。 在local.settings.json和 Aspire 中設定相同的環境變數時,系統會使用 Aspire 版本。請勿在
local.settings.json中為任何連線設定 Azure 儲存體模擬器。 許多 Functions 入門範本都包含模擬器做為AzureWebJobsStorage的預設值。 不過,模擬器設定可能會提示某些開發人員工具啟動可能與 Aspire 使用的版本衝突的模擬器版本。