はじめに
- 次の環境を使用して動作確認しています。
ハードウェア CPU: AMD Ryzen 5 3400G, MEM: 16GB, SSD: 130GB OS Windows 10(64ビット) IDE Microsoft Visual Studio Community 2019(16.8.5) + C#(8.0)
(.NET5を使用するためにVisual Studio 16.8.0以降か、別途.NET5 SDKのインストールが必要です。) - 完全なソースコードはこちらで公開しています。
- 次の資料を参考にしています。
.NET5への移行概要
- Azure Functionsの実行方法(モデル)が.NET isolated processに変更されました。
- 以前のAzure Functionsでは、Azure Functionsランタイムとアプリを同一プロセスとして実行するイン・プロセス・モデルが採用されていました。このモデルでは、ランタイム(SDK)が提供する豊富な機能をアプリから無駄なく使用できます。一方で、アプリとランタイムで同じ.NET環境を使用する必要があるので、アプリとランタイムで使用するライブラリの競合、アプリのプロセスを自由にカスタマイズできない、等の問題(制約)が発生します。Azure Functions開発チームでは、新しい.NETバージョンに対応するのがいつも大変のようです。
- そのため、.NET5からはランタイムとアプリを別プロセスとして実行するアウト・オブ・プロセス・モデル(.NETの用語としては.NET isolated process)が採用されました。
(.NET5は長期サポートではなく、この挑戦が成功しなくても影響が少ないと判断したようです。) - .NET5以降のロードマップが公開されています。
将来的にはアウト・オブ・プロセス・モデルに統一していくようです。.NET6ではイン・プロセスモデル/アウト・オブ・プロセスモデルのどちらも選択できますが、将来性を考えるならアウト・オブ・プロセス・モデルの使用をお薦めします。
- その他詳細はこちらをご覧ください。
- .NET Core 3.1から.NET5に移行する手順の概要を次に示します。
前述の実行モデルの変更に伴う改修が主となり、プロジェクトの形式変更、使用パッケージの.NET isolated process版への置き換えが必要になります。
なお、HTTP以外のService BusやBlobトリガーの場合は、移行前後での改修は不要のようです。No. 概要 説明 1 プロジェクト
定義の変更使用するフレームワークを.NET5、出力の種類をコンソールアプリケーション(Exe)に変更にします。 2 パッケージの変更 Azure Functionsランタイムやトリガー用のパッケージを.NET isolated process用のパッケージ(Microsoft.Azure.Functions.Worker.*)に置き換えます。 3 起動方法の変更 ASP.NET Coreアプリ等のようにホストビルダを使用してプロセスを初期化・実行します。(既存プロジェクトでスタートアップクラスを実装している場合は、DIサービス登録等の処理をこちらに移植する必要があります。) 4 メソッド定義の変更 実行対象のメソッドに付与していたFunctionName属性をFunction属性に変更します。メソッド引数にILoggerを指定している場合は、FunctionContextから取得するよう変更します。 5 HTTPトリガー
使用時の変更HTTPトリガーを使用する場合、
引数・戻り値の型をHttpRequestData, HttpResponseDataに変更します。6 ローカル実行用の
ホスト定義の変更local.settings.jsonのワーカーランタイム(FUNCTIONS_WORKER_RUNTIME)を.NET isolated process(“dotnet-isolated”)に変更します。
.NET5への移行詳細
プロジェクト定義の変更
- プロジェクトのプロパティを開きます。
「対象のフレームワーク」を「.NET 5.0」、「出力の種類」を「クラスライブラリ」から「コンソールアプリケーション」に変更します。 - プロジェクトファイル(.csproj)を直接編集する場合、次のようにTargetFramework/OutputType要素を編集します。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> <!-- 変更箇所 --> <AzureFunctionsVersion>v3</AzureFunctionsVersion> <OutputType>Exe</OutputType> <!-- 変更箇所 --> </PropertyGroup> <ItemGroup> ...
パッケージの変更
- 既存のイン・プロセス・モデル用のパッケージを、アウト・オブ・プロセス・モデル用のものに置き換える必要があります。
種類 イン・プロセス・モデル アウト・オブ・プロセス・モデル ランタイム Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdkトリガー Microsoft.Azure.WebJobs.Extensions.* Microsoft.Azure.Functions.Worker.Extensions.* - NuGetパッケージマネージャを起動してパッケージを置き換えます。
NuGetパッケージマネージャの起動方法は、[ツール]-[NuGetパッケージマネージャー]-[ソリューションのNuGetパッケージの管理]、またはプロジェクトの[依存関係]-[パッケージ]を右クリックして[NuGetパッケージの管理]を選択します。 - プロジェクトファイル(.csproj)を直接編集する方法もありますが、使用パッケージの最新安定バージョンが分からないので、前述のNuGetパッケージマネージャを使用する方法をお薦めします。
(次の例ではHTTPトリガーを使用しています。)<Project Sdk="Microsoft.NET.Sdk"> ... <ItemGroup> <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.6.0" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" /> <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.3.0" /> ... </ItemGroup> ...
起動方法の変更
- イン・プロセス・モデルの場合、Azure Functionsプロセスの起動をランタイムが行うため、プロセス初期化処理は不要でした。プロセス初期化時に必要となるDIサービスの登録は、ランタイムがスタートアップクラスを実行することで実現します。
アウト・オブ・プロセス・モデルの場合、ホストビルダ(IHostBuilder)を使用してアプリ自身でプロセスの初期化やDIサービス登録処理の呼び出しを実装する必要があります。 - アプリの実行開始場所(エントリポイント)となるメソッドMain()を定義し、ホストビルダでConfigureFunctionsWorkerDefaults()を実行します。
構成情報を追加する場合はConfigureAppConfiguration()、依存関係を注入(DI)する場合はConfigureServices()を使用します。class Program { public static Task Main() { var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() // 必須 // 構成情報を追加する場合 .ConfigureAppConfiguration((context, config) => { var env = context.HostingEnvironment; config .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); }) // 依存関係を注入する場合 .ConfigureServices((context, services) => { var configuration = context.Configuration; services.AddHttpClient(); // 例としてHttpClientをDI }) .Build(); return host.RunAsync(); } }
メソッド定義の変更
- 関数のメソッドに付与されたFunctionName属性をFunction属性に変更します。
なお、FunctionName属性の定義(FunctionNameAttribute)は、Microsoft.Azure.Functions.Worker.Extensions.Abstractionsパッケージにあります。HTTPやServiceBusトリガーを使用すると、その依存関係として自動的に参照が追加されます。public static class Function1 { [FunctionName("Function1")] public static async Task<...> Run( ...
public static class Function1 { [Function("Function1")] public static async Task<...> Run( ...
- メソッドの引数でILoggerを引き渡している場合、FunctionContext経由で取得するよう変更します。
public static class Function1 { [FunctionName("Function1")] public static async Task<...> Run(..., ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); ...
public static class Function1 { [Function("Function1")] public static async Task<...> Run(..., FunctionContext context) { var log = context.GetLogger(nameof(Function1)); log.LogInformation("C# HTTP trigger function processed a request.");
HTTPトリガー使用時の変更
- メソッドの引数型HttpRequestをHttpRequestData、戻り値型IActionResultをHttpResponseDataに変更します。
public static class Function1 { [FunctionName("Function1")] public static async Task<IActionResult> Run( [HttpTrigger(...)] HttpRequest req, ...) { ... string responseMessage = "..."; return new OkObjectResult(responseMessage); } }
public static class Function1 { [Function("Function1")] public static async Task<HttpResponseData> Run( [HttpTrigger(...)] HttpRequestData req, ...) { ... string responseMessage = "..."; var response = req.CreateResponse(System.Net.HttpStatusCode.OK); response.Body.Write(Encoding.Default.GetBytes(responseMessage)); return response; } }
ローカル実行用のホスト定義の変更
- local.settings.jsonのFUNCTIONS_WORKER_RUNTIMEを、アウト・オブ・プロセス・モデルである”dotnet-isolated”に変更します。
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" } }
トラブルシューティング
ホストが開始していない
- 事象
ローカルで実行時に次のエラー・例外が発生して中断する。- Azure Functions Core Toolsのコンソールでは次のエラーが表示される。
Microsoft.Azure.WebJobs.Script: Did not find functions with language [dotnet].
- アプリのコンソールでは、Microsoft.Azure.WebJobs.Hostで次の例外が発生する。
System.InvalidOperationException(“The host has not yet started.”)
- Azure Functions Core Toolsのコンソールでは次のエラーが表示される。
- 原因
local.settings.jsonのFUNCTIONS_WORKER_RUNTIMEが”dotnet-isolated”以外になっていることが原因。(当該値に変更する必要がある。){ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", //"FUNCTIONS_WORKER_RUNTIME": "dotnet" "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" } }
アセンブリをロードできない
- 事象
Azure Functionsアプリを起動すると、次の例外が発生して実行が中断される。System.IO.FileNotFoundException: Could not load file or assembly ‘xxx’ - 原因
- プロジェクトをビルドする際、ランタイムと共有するアセンブリ(パッケージ)は最適化のために単一バージョンのみ保持(それ以外は削除)されます。同一パッケージで異なるバージョンのパッケージを使用している場合、ビルド時にいずれかのバージョンのものが削除されるため、この例外が発生します。
- Microsoft.NET.Sdk.Functions 3.0.12より前では、プロジェクトのプロパティで”_FunctionsSkipCleanOutput”を宣言することで、この動作を抑制できます。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> <AzureFunctionsVersion>v3</AzureFunctionsVersion> <OutputType>Exe</OutputType> <_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput> </PropertyGroup> ...
- Microsoft.NET.Sdk.Functions 3.0.12以降では、保持対象とするパッケージをFunctionsPreservedDependencies要素で指定できます。
- その他詳細はこちらが参考になると思います。
gRPC公開ポイントの不正
- 事象
Azure Functionsアプリを起動すると、次の例外が発生して実行が中断される。Unhandled exception. System.InvalidOperationException: The gRPC channel URI ‘http://:0’ could not be parsed. - 原因
- gRPCの公開ポイントは構成情報(IConfiguration)から取得します。何らかの理由で公開ポイントを取得できない場合、この事象が発生します。
- 既定ではAzure Functionsホストビルダ実行時のConfigureFunctionsWorkerDefaults()で、構成情報が初期化されます。作成された構成情報に対して、意図しない操作を行い、構成情報のクリアや上書きをしている可能性があります。
- 構成情報(IConfiguration)や構成情報ビルダ(IConfigurationBuilder)に対する操作を追跡することで、原因個所を特定できると思います。
- なお、次のように構成情報ビルダのソースをクリアすると同事象を再現できます。
hostBuilder .ConfigureAppConfiguration(config => config.Sources.Clear());