こんにちは。seiです。桜の花の開花がこんなにも遅くなってるのは久々な気がする今日この頃。これが普通の状態なのか異常なのか、もうよくわかんなくなってきますね。
さて、今日は2024年11月にサポート終了が迫っている.NET6のAzure Functionsアプリケーションを、その次のLTS(長期サポート)リリースである「.NET8」へアップグレードすべく、事前に検証してみましたので、その手順を投稿していこうと思います。
ちなみに、以前は.NET8ではインプロセスモデルはサポートされず、分離ワーカーモデルのみになると思っていましたが、ここにきてインプロセスモデルも利用可能と理解しました。しかしながら、結局はインプロセスモデルは廃止の方向のようなので、もう.NET8への移行と同時にインプロセスモデルから分離ワーカーモデルへの移行も併せて行ってしまおうというのが今回の趣旨です。
はじめに
基本的には、MSさんのLearnにあるこの情報を元ネタとして実施しています。が、この手順ですと、ASP.NETCore統合としてAzure Functionsを構成しますが、今回は「組み込みの Functions HTTP 型」っていうの?つまりASP.NETCore統合不使用(HttpRequestDataとかHttpResponseData使うヤツ)での移行を行っていきます。
Azure Functions .NET8 分離ワーカーモデルへの移行手順
移行対象とするのは、下記の.NET6インプロセスモデルのAzure Functionsコードになります。
このコードでは、HTTPトリガーでクライアントからのHTTPリクエストを受け取り、Queue出力バインドで複数の値をQueue出力を行うというものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; namespace DotNet8MigrationFunctionApp { public static class Function1 { [FunctionName("Function1")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; for (int i = 0; i < 3; i++) { outputQueues.Add($"{i}:{name}"); } return new OkObjectResult($"Hello,{name}!"); } } } |
実行するとこんな感じ。
HTTPリクエストを受け取り、URLパラメーターで受け取った内容を出力する。
そして、QueueにURLパラメーターで受け取った内容を3回出力する。
それでは早速移行していきますが、おおまかな手順としては以下の感じになっています。
- csprojファイルの編集
- パッケージ参照の変更
- Program.csファイルの追加
- 関数アプリコードの変更
それではひとつづつ順番に行っていきましょ~。
csprojファイルの編集
まずはC#のプロジェクトファイルのcsprojファイルを変更します。
以下が変更前のcsprojファイル。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <AzureFunctionsVersion>v4</AzureFunctionsVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.2.2" /> <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage.Queues" Version="5.2.1" /> <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.3.0" /> </ItemGroup> <ItemGroup> <None Update="host.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Update="local.settings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>Never</CopyToPublishDirectory> </None> </ItemGroup> </Project> |
変更内容は以下のとおり。
①<TargetFramework>の値を「net6.0」→「net8.0」に変更します。
②<OutputType>Exe</OutputType>をPropertyGroup内に追加します。
③ItemGroupの「Microsoft.NET.SDK.Functions」へのパッケージ参照を以下の内容に書き換えます。
1 2 3 4 5 6 |
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" /> <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" /> |
④「Microsoft.Azure.WebJobs」や「Microsoft.Azure.Functions.Extensions」名前空間のパッケージがある場合は削除します。
1 2 3 |
※削除する!! <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.2.2" /> <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage.Queues" Version="5.2.1" /> |
⑤以下のItemGroupを追加します。
1 2 3 |
<ItemGroup> <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/> </ItemGroup> |
最終的に変更されたcsprojファイルは以下のような感じになると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <AzureFunctionsVersion>v4</AzureFunctionsVersion> <OutputType>Exe</OutputType> </PropertyGroup> <ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" /> <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" /> </ItemGroup> <ItemGroup> <None Update="host.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Update="local.settings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>Never</CopyToPublishDirectory> </None> </ItemGroup> <ItemGroup> <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/> </ItemGroup> </Project> |
パッケージ参照の変更
続いて、パッケージ参照を変更していきます。基本的には「Microsoft.Azure.WebJobs」や「Microsoft.Azure.Functions.Extensions」名前空間のパッケージで利用していた拡張機能を「Microsoft.Azure.Functions.Worker.Extentions」名前空間のパッケージとしてインストールしなおす感じです。
①NuGetパッケージマネージャーを開きます。コンソールがお好みの方はよしなに。
②利用するバインドなどのパッケージをインストールします。ここではQueue出力バインドを利用していますので、「Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues」パッケージをインストールします。
詳しくは以下のリンクにて。
Program.csファイルの追加
Program.csファイルを追加して以下のコードを記述していきます。すでにDIなど利用している場合はFunctionsStartup属性のついたStartup.csをProgram.csにリネームしていく感じでしょうか。必要であればDIも記述します。また、JSONシリアル化設定なども必要であれば追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Unicode; var host = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); // JSONシリアル化オプションを設定する services.Configure<JsonSerializerOptions>(options => { options.AllowTrailingCommas = true; // 末尾のカンマを無視する options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; // nullのフィールドはシリアル化時に含めない options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; // camelケースで出力 options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); // 漢字可 options.PropertyNameCaseInsensitive = true; // 逆シリアル化時に大文字小文字を区別しない options.WriteIndented = true; // JSONを整形 }); }) .Build(); host.Run(); |
関数アプリコードの変更
あとは実際に関数アプリコードを書き換えていくだけです。変更前のAzure Functions .NET6インプロセスモデルのコードを再掲します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; namespace DotNet8MigrationFunctionApp { public static class Function1 { [FunctionName("Function1")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; for (int i = 0; i < 3; i++) { outputQueues.Add($"{i}:{name}"); } return new OkObjectResult($"Hello,{name}!"); } } } |
変更ポイントは以下の通り。
- [FunctionName]属性を[Function]属性に変更する
- DIでIloggerを受け取り、コード内のlogインスタンスと置換する
- usingの書き換え
- staticクラス・メソッドをインスタンスクラス・メソッドに変更
- トリガーやバインディングの変更
- local.settings.jsonファイルの「FUNCTIONS_WORKER_RUNTIME」値を「dotnet-isolated」に変更
それでは一つ一つ見ていきます。
[FunctionName]属性を[Function]属性に変更する
1 2 3 4 5 6 7 |
public static class Function1 { [FunctionName("Function1")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues, ILogger log) |
となっているのを
1 2 3 4 5 6 7 |
public static class Function1 { [Function("Function1")] // ←ここ public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues, ILogger log) |
と[Function(関数クラス名)]に書き換えるだけです。
ILoggerのDI
①まずは以下のコードを関数クラスのメンバーとして宣言します。
1 |
private readonly ILogger<Function1> _logger; |
②関数クラスのコンストラクターを宣言し、ILoggerオブジェクトを受け取って①で宣言した変数に代入します。
1 2 3 |
public Function1(ILogger<Function1> logger) { _logger = logger; } |
③次に関数メソッドが受け取っているILoggerインスタンスを削除します。
1 2 3 4 5 |
[Function("Function1")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues, ILogger log) // ←削除 |
こうなります。
1 2 3 4 |
[Function("Function1")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues) |
④そして、コード内のlogオブジェクトを_loggerオブジェクトに変更します。
1 |
log.LogInformation("C# HTTP trigger function processed a request."); |
を
1 |
_logger.LogInformation("C# HTTP trigger function processed a request."); |
って感じに直します。
usingの書き換え
不要な「using Microsoft.Azure.WebJobs;」をすべて削除し、「using Microsoft.Azure.Functions.Worker;」を追加します。
1 2 3 4 5 6 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; // ←削除 using Microsoft.Azure.WebJobs.Extensions.Http; // ←削除 using Microsoft.Azure.Functions.Worker; // ←追加 using Microsoft.Extensions.Logging; |
こうなります。
1 2 3 4 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; |
staticクラス・メソッドをインスタンスクラス・メソッドに変更
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static class Function1 // ←staticを削除 { private readonly ILogger<Function1> _logger; public Function1(ILogger<Function1> logger) { _logger = logger; } [Function("Function1")] public static IActionResult Run( // ←staticを削除 [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues) |
を
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Function1 { private readonly ILogger<Function1> _logger; public Function1(ILogger<Function1> logger) { _logger = logger; } [Function("Function1")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues) |
に変更します。
トリガーやバインディングの変更
この手順はちょっと癖あります。基本的にはトリガーはそのまま、入力バインドはインプロセスモデル時の属性値に「Input」を付けましょうとあります。例えば、[Blob(“sample/fileName”, FileAccess.Read, Connection = “MyStorageConnection”)]だったとしたら、[BlobInput(“sample/fileName”, Connection = “MyStorageConnection”)]に変わります。Accessプロパティは不要です。
そして、出力バインドですが、入力バインドと同様に通常は「Output」を付与するとあります。ですが、今回の例では、Httpトリガーとして、クライアントにHttpレスポンスを返しつつ、Queueストレージにエンキューを行う必要があります。要するに複数の出力バインドがあるというシチュエーションを実装してみます。
元ネタはここです。
①新しいクラスを追加します。今回はCustomResponseというクラス名で追加します。そして、そこにHttpResponseを返すプロパティとQueue出力バインド用のプロパティを追加しておきます。Queue出力バインドを返すプロパティには[QueueOutput(“my-queue-items”, Connection=”AzureWebJobsStorage”)]といった感じで属性を設定しておきます。また、Queue出力バインドは1度に複数の値をエンキューしているので、Listオブジェクトといったコレクションや配列で用意する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class CustomResponse { public CustomResponse() { OutputQueue = new List<string>(); } public IActionResult Response { get; set; } [QueueOutput("my-queue-items", Connection = "AzureWebJobsStorage")] public List<string> OutputQueue { get; set; } } |
②次に、メソッド宣言を変更し、CustomResponseを返すようにし、Queueのバインドを削除します。
1 2 3 4 |
[Function("Function1")] public IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, [Queue("my-queue-items"), StorageAccount("AzureWebJobsStorage")] ICollector<string> outputQueues) |
これを
1 2 3 |
[Function("Function1")] public CustomResponse Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req) |
このように変更します。
③最後に、メソッド内でIActionResultを返しているコードを①で追加したCustomResponseオブジェクトで返すように変更します。
1 2 3 4 5 6 7 8 9 10 |
var response = new CustomResponse(); response.Response = new OkObjectResult($"Hello,{name}!"); for (int i = 0; i < 3; i++) { response.OutputQueue.Add($"{i}:{name}"); } return response; |
local.settings.jsonファイルの「FUNCTIONS_WORKER_RUNTIME」値を「dotnet-isolated」に変更
最後にlocal.settings.jsonに分離ワーカーモデルを利用することを明示していきます。
FUNCTIONS_WORKER_RUNTIMEの値を「dotnet」から「dotnet-isolated」に変更します。
local.settings.jsonですので、言うまでもなく、ローカルデバッグでの設定です。この値が有効になるのはデバッグ時ですので、Azure実行環境でも同様の変更が必要ですが、後述します。
1 2 3 4 5 6 7 |
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" // ← dotnetからdotnet-isolatedに変更 } } |
.NET8分離ワーカーモデル(ASP.NET Core統合)移行後のコード全体像
Azure Functionsの.NET6インプロセスモデルから、.NET8分離ワーカーモデル(ASP.NETCore統合)移行後のコード全体像は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using System.Collections.Generic; namespace DotNet8MigrationFunctionApp { public class CustomResponse { public CustomResponse() { OutputQueue = new List<string>(); } public IActionResult Response { get; set; } [QueueOutput("my-queue-items", Connection = "AzureWebJobsStorage")] public List<string> OutputQueue { get; set; } } public class Function1 { private readonly ILogger<Function1> _logger; public Function1(ILogger<Function1> logger) { _logger = logger; } [Function("Function1")] public CustomResponse Run( [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req) { _logger.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; var response = new CustomResponse(); response.Response = new OkObjectResult($"Hello,{name}!"); for (int i = 0; i < 3; i++) { response.OutputQueue.Add($"{i}:{name}"); } return response; } } } |
1 2 3 4 5 6 7 |
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" } } |
それではデバッグ実行してみます。無事Fucntionsホストが立ち上がりました。が、分離ワーカーかどうかははっきりわかりませんが、インプロセスの時と表示が若干違うのはわかります。
それではHTTPトリガーにリクエストを投げてみます。
あれ?レスポンスの文字列が返ってこない・・・orz
でも、Queueにはちゃんとエンキューされてる。
いろいろ調べたがどうしてもわからなかった。けど、単体のHTTPトリガーなんかはうまくいくのでもしかして、マルチレスポンスでHTTPとQueueを出力してるのが悪いのかなぁと憶測。
2024/06/11追記
Microsoft.Azure.Functions.Worker.Sdkのバージョン1.17.3-preview2以降かつ、Microsoft.Azure.Functions.Worker.Extensions.Http拡張機能のバージョン3.2.0以降かつ、Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCoreのバージョン1.3.0以降をインストールして、IActionResultに[HttpResult]属性をつけることで解決しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class CustomResponse { public CustomResponse() { OutputQueue = new List<string>(); } [HttpResult] public IActionResult Response { get; set; } [QueueOutput("my-queue-items", Connection = "AzureWebJobsStorage")] public List<string> OutputQueue { get; set; } } |
ちゃんとマルチレスポンスで返ってくるのを確認。いぇいv
ってことでASP.NETCore統合でいい方はここまででコードの移行は完了ですかね。
試しに、ASP.NET Core統合をやめて実装しなおしてみます。
ASP.NETCore統合ではなく、組み込み型モデルに書き換える方は引き続き次の手順をどうぞ。
Azure Functions .NET 8 IsolatedをASP.NET Core統合なしに書き換え
ということで、うまくレスポンスが返ってきませんでしたので、ASP.NET Core統合をやめ、非ASP.NET Core統合(組み込みFunctionsHTTP型)に書き換えていきます。
①NuGetパッケージマネージャーから「Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore」パッケージをアンインストールします。
②NuGetパッケージマネージャーから「Microsoft.Azure.Functions.Worker.Extensions.Http」パッケージをインストールします。
③Program.csのコードを変更し、ASP.NET Core統合を削除する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Unicode; var host = new HostBuilder() .ConfigureFunctionsWebApplication() // ←このコードを変更 .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); // JSONシリアル化オプションを設定する services.Configure<JsonSerializerOptions>(options => { options.AllowTrailingCommas = true; // 末尾のカンマを無視する options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; // nullのフィールドはシリアル化時に含めない options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; // camelケースで出力 options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); // 漢字可 options.PropertyNameCaseInsensitive = true; // 逆シリアル化時に大文字小文字を区別しない options.WriteIndented = true; // JSONを整形 }); }) .Build(); host.Run(); |
これを、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Unicode; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() // ←このように変更 .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); // JSONシリアル化オプションを設定する services.Configure<JsonSerializerOptions>(options => { options.AllowTrailingCommas = true; // 末尾のカンマを無視する options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; // nullのフィールドはシリアル化時に含めない options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; // camelケースで出力 options.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); // 漢字可 options.PropertyNameCaseInsensitive = true; // 逆シリアル化時に大文字小文字を区別しない options.WriteIndented = true; // JSONを整形 }); }) .Build(); host.Run(); |
④以下のようにコードを変更します。(変更箇所はコメントにて記述)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
using Microsoft.AspNetCore.Http; // ←不要になるので削除 using Microsoft.AspNetCore.Mvc; // ←不要になるので削除 using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; // ←追加 using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Threading.Tasks; // ←非同期なら追加 namespace DotNet8MigrationFunctionApp { public class CustomResponse { public CustomResponse() { OutputQueue = new List<string>(); } public HttpResponseData Response { get; set; } // ←IActionResultからHttpResponseData型に変更 [QueueOutput("my-queue-items", Connection = "AzureWebJobsStorage")] public List<string> OutputQueue { get; set; } } public class Function1 { private readonly ILogger<Function1> _logger; public Function1(ILogger<Function1> logger) { _logger = logger; } [Function("Function1")] public async Task<CustomResponse> Run( // ←今回はAsyncにした。よしなに。 [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequestData req) // ←HttpRequestからHttpRequestData型に変更 { _logger.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; var response = new CustomResponse(); response.Response = req.CreateResponse(); // ←HttpRequestDataオブジェクトのCreateResponseメソッドからHttpResponseDataを取得する response.Response.StatusCode = System.Net.HttpStatusCode.OK; // ←Httpステータスコードをセットする response.Response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); // ←レスポンスヘッダーをセットする await response.Response.WriteStringAsync($"Hello,{name}!"); // ←レスポンスボディに書き込む for (int i = 0; i < 3; i++) { response.OutputQueue.Add($"{i}:{name}"); } return response; } } } |
それでは、実行してみます。
今度は、ちゃんとHTTPレスポンスも帰ってきました。
Queueへの書き込みもばっちりです。ひとまずコードの移行は問題なさそうです。
Azure実行環境への反映
最後に、.NET8分離ワーカーモデルへと移行したAzure FunctionsコードをAzure実行環境にデプロイしていきましょう。シチュエーションとしてはAzure実行環境は.NET6インプロセスモデルで稼働しているという前提でデプロイを行ってみます。
ランタイムスタックが.NET6のAzure Functions本番系リソースを用意します。そして移行前のAzure Functionsコードがデプロイ済みです。
また、上記Azure Functionsのデプロイスロットを用意し、ステージング環境とします。もちろんこちらも.NET6インプロセスモデルのAzure Functionsです。
そして、local.settings.jsonで行ったのと同じことをAzure実行環境でも行います。[設定]-[環境変数]から、[アプリ設定]にアクセスし、ステージング環境のスロットの「FUNCTIONS_WORKER_RUNTIME」の値を「dotnet」から「dotnet-isolated」に変更し、保存を行います。
環境変数のUI、変わったんですねぇ・・・。というかいろんなところ変わってますが。
それではAzure Functionsリソースにコードをデプロイしていきます。パイプラインを組んでいる方がほとんどだと思いますが、パイプライン経由でAzure Functionsのバージョンアップまで行ってくれるか検証してないので、今回はLearnにある通り手動デプロイ時の自動アップグレードで行ってみます。
Azure Functionsアプリの公開プロファイルを作成します。
プロファイルができたら「発行」ボタンをクリックします。
以下のランタイムバージョン更新ダイアログが表示されるので、「はい」ボタンを押下しましょう。
デプロイが完了したら、Azure Functionsステージングスロットのリソースを確認してみます。ランタイムスタックが「.NET 8」に更新されているのが確認できました!
全般設定を見れば「.NET 8 Isolated」となっているので、分離ワーカーモデルとして動いていることも確認できました。
さいごに
ステージングにデプロイを行って稼働確認がOKになったら、後は本番スロットにスワップを行えば、晴れて.NET 8 分離ワーカーモデルへの移行が完了になります。
スワップしてステージング環境に移った旧本番系に再度手動デプロイを行って.NET 8へのアップグレードを行わないと次回おかしなことになっちゃいますかね?そこまでは試してないですが。「dotnet-isolated」の変更を忘れずに。
実際移行してみて、今回のHTTP&Queue出力といったような複数の出力を行うのにわざわざクラス定義しなきゃとか、ぶっちゃけめんどいですね。さらに、分離ワーカーというか、HttpRequestData、HttpResponseDataがモックしづらいので、UTコードがちょっとやばいです。というか実装してません。なんとかしてくれることを期待するのみです・・・。その辺も考慮に入れたうえでインプロセスのまま、まずは.NET8への移行だけしていくというが選択肢でもいいのかなぁと思います。今回は移行しちゃいましたが。
コード量多いとさすがに対応負担大きいので計画的に移行していきましょう。
以上、Azure Functionsを.NET8分離ワーカーモデルに移行してみました。
コメント