ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

Azure Functions を In-Process モデルから Isolated Worker Process モデルにマイグレーションする

この記事は、Microsoft Azure Advent Calendar 2024 の 15 日目 の記事です。
qiita.com

Azure Functions の In-Process モデルは、.NET 8 までの対応となっており、2026 年 11 月 10 日よりサポート終了となります。
.NET 9 が GA しましたので、In-Process モデルから Isolated Worker Process モデルにマイグレーションしました。移行したリポジトリは、こちらです。
github.com

Application

Isolated Worker Process モデルにマイグレーションする手順は、こちらのドキュメントの通りです。今回は Deployment Slots を使わずダイレクトに移行します。
azure.github.io

csproj ファイル

PropertyGroup に OutputType を追加し、Target Framework も合わせて更新します。

<PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <UserSecretsId>2586a5fb-4b07-4712-be4b-75721384d340</UserSecretsId>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

Nuget Library は、In-Process モデルのパッケージを Microsoft.Azure.Functions.Worker に置き換えます。

<!--<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.5.0" />-->
<!--<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.1" />-->
<!--<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />-->
<!--<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />-->
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />

Queue Trigger の Function があるので、こちらのパッケージも置き換えます。他のトリガーやバインディングがある場合は、個別に置き換えが必要です。

<!--<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.3.0" />-->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues" Version="5.5.0" />

シークレット関連は、バージョン更新するだけで OK です。

<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.2" />

Application Insights のパッケージも追加しておきます。

<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" /> 
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.0.0" />

最後に、 新しい ItemGroup を追加します。

<ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>

Program.cs ファイル

このアプリケーションでは、Azure Functions のプロジェクトに拡張の NuGet パッケージをインストールして Startup クラスを実装していました。

  • appsettings.json の追加
  • Azure Key Vault の参照(Azure 環境のみ)
  • secrets.json の利用(ローカル開発環境のみ)
[assembly: FunctionsStartup(typeof(FunctionApp.Startup))]

namespace FunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var context = builder.GetContext();
            builder.Services.Configure<MySettings>(context.Configuration.GetSection("Function"));
        }

        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            var context = builder.GetContext();

            builder.ConfigurationBuilder
                .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
                .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
                .AddEnvironmentVariables();

            if (context.EnvironmentName != "Development")
            {
                var config = builder.ConfigurationBuilder.Build();
                builder.ConfigurationBuilder
                    .AddAzureKeyVault(new Uri(config["Function:KeyVaultUrl"]), new DefaultAzureCredential());
            }
        }
    }
}

Startup クラスの実装を Program.cs ファイルに置き換えます。IHostApplicationBuilder を使うことで、ASP.NET Core のプログラミングモデルに寄せて実装できる ASP.NET Core integration を有効化できます。

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Configuration.AddUserSecrets(Assembly.GetExecutingAssembly());

builder.Services
    .Configure<MySettings>(builder.Configuration.GetSection("Function"))
    .AddApplicationInsightsTelemetryWorkerService()
    .ConfigureFunctionsApplicationInsights();

if (builder.Configuration["AzureWebJobsStorage"] != "UseDevelopmentStorage=true")
{
    if (!string.IsNullOrEmpty(keyVaultUrl))
    {
        builder.Configuration.AddAzureKeyVault(new Uri(keyVaultUrl), new DefaultAzureCredential());
    }
}

builder.Build().Run();

local.settings.json ファイル

FUNCTIONS_WORKER_RUNTIME プロパティの値を dotnet-isolated へ変更します。

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "StorageBindingConnection": "UseDevelopmentStorage=true"
  }

Http Trigger Function

Isolated Worker Process モデルでは、Function の引数で ILogger を受け取れないため、コンストラクタの引数で受け取るように変更します。

private readonly ILogger<HttpFunction> _logger;    // 追加
private readonly MySettings _settings;

//public HttpFunction(IOptions<MySettings> optionsAccessor)
public HttpFunction(ILogger<HttpFunction> logger, IOptions<MySettings> optionsAccessor)
{
    _logger = logger;    // 追加
    _settings = optionsAccessor.Value;
}

Function を定義する属性は、FunctionName から Function に変更します。

//[FunctionName(nameof(HttpFunction))]
//public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
[Function(nameof(HttpFunction))]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)

Queue Trigger Function

ILogger をコンストラクタの引数で受け取る変更は Http Trigger と同様です。Queue Trigger ではメッセージを QueueMessage 型で受け取れるようになります。

//[FunctionName(nameof(QueueFunction))]
// public void Run([QueueTrigger("myqueue-items", Connection = "StorageBindingConnection")] string myQueueItem, ILogger log)
[Function(nameof(QueueFunction))]
public void Run([QueueTrigger("myqueue-items", Connection = "StorageBindingConnection")] QueueMessage message)

ローカル開発環境で Visual Studio からデバッグ実行すると、エラーが発生しました。

<Error>
  <Code>InvalidHeaderValue</Code>
  <Message>The value for one of the HTTP headers is not in the correct format.</Message>
  <HeaderName>x-ms-version</HeaderName>
  <HeaderValue>2024-08-04</HeaderValue>
</Error>

どうやら Nuget Package を更新したことで、ライブラリとエミュレータで Azure Storage の API バージョンが一致していないことが原因のようです。
Azurite のバージョンが 3.12 だったので、最新の 3.33 に更新することで解決しました。


Azure Functions

Azure Functions 側は、環境変数の FUNCTIONS_WORKER_RUNTIME の値を dotnet-isolated へ変更します。

.NET Version は、環境変数の変更を Save した後に変更しました。

GitHub Actions を組んでいるので、こちらの DOTNET_VERSION も忘れずに更新します。

env:
  AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root
  DOTNET_VERSION: '9.0.x' # set this to the dotnet version to use

以上で、無事に Isolated Worker Process モデルへのマイグレーションが完了しました。