しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Functions の .NET 5 対応と関係する注意点

.NET 5 がリリースされて少し経ちますが、App Service は Early Access という形ですが .NET 5 への対応が行われたのに対して、Azure Functions は今のところ .NET Core 3.1 までの対応となっています。

少し前から Azure Functions の .NET 5 対応に関して GitHub では悪い意味で盛り上がっているのと、パッケージの更新を行うと動かなくなってしまうケースがあるため、注意喚起のために書きます。起こっていることは以下の Issue から辿れば分かります。

先に軽く概要を書いておくと、Azure Functions は v1 から v3 までのランタイムが存在していて、それぞれが .NET Framework / .NET Core 2.1 / .NET Core 3.1 をターゲットにしています。基本的には LTS 向けにリリースするという方針です。

しかし .NET 5 は LTS ではないため、Function チーム的にはあまり需要は無いと見ていたようです。しかし現実には .NET 5 への要望が非常に多かったため、Node.js などのようにランタイムと切り離す方向で非 LTS バージョン向けにプレビュー版がリリースされたのが最近の話です。

個人的には .NET 5 への対応は割とどうでも良いと思っていて、これまで通り .NET Standard 2.1 に対応したパッケージが使えれば問題ないと考えていましたが、実際には .NET Standard 2.1 対応のパッケージでも動作しないものが出てきたので困っています。

ここからは実際に Azure Functions と .NET 5 周りで発生したトラブルと対処法のまとめです。

.NET Standard 対応なのに使えないパッケージ

最近の .NET / .NET Core では Microsoft.Extensions.* というパッケージが同時にリリースされているのですが、このパッケージの参照するバージョンによっては .NET Standard 2.1 であっても Azure Functions では実行時エラーとなります。

具体的には Microsoft.Extensions.HttpMicrosoft.EntityFramework.Core などのパッケージで発生します。これらは .NET 5 と同時にリリースされたので、参照する Microsoft.Extensions.* のバージョンは同じく 5.0.x になっています。

これらのパッケージは .NET Standard 2.0 や 2.1 をターゲットにしているので、本来であれば .NET Core 3.1 の Azure Functions でも問題なく動作するはずです。

実際に Visual Studio で作成した HttpTrigger のみを持つ Function App に Microsoft.Extensions.Http をインストールして実行すると、以下のようなエラーが発生して起動しません。

f:id:shiba-yan:20210110215243p:plain

原因としては Azure Functions は .NET Core 3.1 なので、参照している Microsoft.Extensions.* のバージョンは大体 3.1.x となっていますが、Function App 側は 5.0.x なのでバージョンが合いません。

Function App のライブラリは実行時に AssemblyLoadContext によって読み込まれるので、本来であれば別々のバージョンが読まれても問題はないのですが、ランタイムから Function App へ DI やバインディングでインスタンスを渡すケースがあるため、上手く動かないという話です。

従ってパッケージのバージョンを 3.1.x に下げると問題なく動作します。

f:id:shiba-yan:20210113231632p:plain

NuGet からのインストール時にバージョンを明示的に指定しない場合は自動的に最新版がインストールされるため、何気なく Entity Framework Core や HttpClientFactory を使おうとするとはまります。

HttpClientFactory ぐらいであればバージョンを下げれば良いのですが、Entity Framework Core は 5.0 でかなりのアップデートが行われているため、バージョンを下げて対応というのは無理な話です。なのでフィードバックをしていますが反応は微妙です。

依存関係は問題がないのに使えないパッケージ

特に依存関係には問題がないのに、インストールすると実行時エラーになるパッケージも一部存在します。代表例は System.Text.Encoding.CodePages 辺りです。

実際に Function App にインストールして動かしてみると、以下のようにエラーが発生します。

f:id:shiba-yan:20210113230610p:plain

これの原因は単純で、最近の Azure Functions SDK はビルド時にランタイムが持っているアセンブリは自動的に削除するようになっているため、ランタイムより新しいバージョンを使う場合に問題となります。

回避にはアセンブリの自動削除を無効化すれば良いので、csproj ファイルに _FunctionsSkipCleanOutput を追加するだけで動くようになります。この辺りは以前書いたエントリを参照してください。

_FunctionsSkipCleanOutput を追加して再度実行してみると今度は問題なく動作しました。

f:id:shiba-yan:20210113230836p:plain

バージョンをチェックせずに同名なら一律で消すという処理が、まだ改善されていないことに残念さを感じますが、こっちに関してはひとまず回避方法があるので何とかなります。

.NET 5 対応 Worker はまだまだ発展途上

ランタイムの .NET 5 への対応が LTS ではないので行われない代わりに、Node.js などと同様の仕組みで .NET 5 が使える Worker が去年の末にプレビューとして公開されています。

アーキテクチャ的に Worker はかなりのオーバーヘッドがありますし、.NET 5 でパフォーマンスが改善したとはいっても確実にオーバーヘッド分の方が大きくなるので、個人的に使うメリットはないと感じています。

一応試しては見ましたが使えるトリガーはまだ僅かですし、この Worker を使っても Entity Framework Core 5.0 は使えなかったので完全に見切りました。

事故らないために出来ること

Floating Version を使って指定する

今回の内容を要約すると .NET 5 と同時にリリースされたバージョンの Microsoft.Extensions.* パッケージを参照すると動かなくなるため、既存のパッケージのアップデート時に注意する必要があります。

HttpClientFactory や Entity Framework Core はセキュリティアップデートなどで更新も行われるので、Floating Version を使うことでそもそもアップデートの必要が無くなるので多少回避できます。

バージョンの一部をアスタリスクで指定すると、その部分の最新版が自動的に解決されるので LTS の .NET Core 3.1 向けではあります。csproj を書き換えても良いですし、インストール時に指定も出来ます。

dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 3.1.*

これでインストールされた Entity Framework Core は 3.1 系の最新版が常に使われるようになります。

Version Range を明確に指定する

Floating Version だけでは Visual Studio から NuGet パッケージの一括更新が行われた場合には、メジャーバージョンを含めた最新版へアップデートされてしまうので、回避するためには利用可能なバージョンを明示的に指定する方法があります。

若干書き方が難しいですが、以下のように書けば Entity Framework Core と HttpClientFactory は 3.1.x の最新版かつ、5.0.0 未満しかインストール出来なくなるため安全です。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="[3.1.*,5.0.0)" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="[3.1.*,5.0.0)" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

Floating Version やバージョンの範囲を限定した場合でも、Visual Studio のソリューションエクスプローラーから、どのバージョンがインストールされたか確認出来ます。

f:id:shiba-yan:20210110213913p:plain

.NET Core から .NET への過渡期というのもあり、問題が複雑になっている感はありますが、パッケージのバージョンアップには気を付ける必要が当分はあるという話でした。

Azure Functions SDK の実装が雑なのも混乱を深めている感じです。直るまでフィードバックし続けます。