A Heavy Lift: Bringing Kestrel + YARP to Azure App Services をピックアップして訳しつつ補足する

本記事は [Microsoft Azure Tech Advent Calendar 2022](https://qiita.com/advent-calendar/2022/microsoft-azure-tech) 23 日目の投稿です。

今回は今年の App Service のフロントエンドが Kestrel + YARP に移行した英語 Blog を紹介します。

https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/
https://azure.github.io/AppService/2022/08/16/A-Heavy-Lift.html

全てではなく、いくつか部分的にピックアップして日本語訳でサマリーをしつつ、あとは少しだけ補足したい部分があれば個人的に一部補足する形とします。また、原文の方で呼んでもらった方が良さそうと思ったものは紹介だけに留めます。

そのため、公式の正しい情報として知りたい方や、全ての内容をしっかり読みたい方、そもそも元の英語の言語で読みたい方は[元 Blog](https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/) を参照ください。

# Introduction
まず冒頭では、1 日に何十億回も実行されるコードパスを持っている App Service の重要なプラットフォームコンポーネントを、サービスの影響を最小限にし Kestrel + YARP へ移行したことを紹介しています。そのチャレンジの恩恵となった以下のようなメリットをあげています。

- リクエストごとのCPU使用率の大幅な減少によって Azure データセンターの環境負荷が軽減されます
- HTTP/3 などの最新プロトコルのサポートをします
- gRPC アプリケーション、ホストごとの暗号スイート構成、カスタムエラーページなどの新しいシナリオをサポートします

App Service だけでなく .NET や Azure の複数チームを横断してこの App Service Frontend fleet を Kestrel + YARP に移行したこのプロジェクトを実施したことが書かれていました。

# Azure App Service in a nutshell
App Service は Blog 記載時点で、以下のような特徴を持っています。

- 160B / 1 Day 以上の HTTP リクエストがアプリケーションによって提供されている
- 1400 万以上のホスト名
- 1.5K 以上のマルチテナントスケールユニットと、さらに1万台以上の専用スケールユニット（App Service Environments、App Service Isolated SKU)

このシステムの重要なアーキテクチャーの 1 つとして FrontEndRole があります。その主な用途は以下の通りです。

- スケールユニットに関連付けられたパブリックな仮想 IP アドレスから HTTP/HTTPS でトラフィックを受信
- 必要な場合は SSL を Terminate させる
- どの VM セットがアプリケーションのオリジンサーバ（Workers と呼ばれる）であるかを判断し、それらにルーティングする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150728/619ccc28-d867-7a5c-5940-ecf57cf8e824.png)
※ オリジナル Blog より引用

App Service は元々は Cloud Service として構築されていて、このロールは単に FrontEndRole と呼ばれています。VM Scale Sets への移行に伴い、FrontEndRole は各スケールユニットの一部である独立したスケールセットとなりました。

Windows Server 上で動作するオリジナルの App Service FrontEndRole は以下のような構成になっていました。

- HTTP.sys/IIS/Windows Server のコンポーネント
- WinHTTP でリクエスト転送を行う [ARR (Application Request Routing)](https://www.iis.net/downloads/microsoft/application-request-routing)

## [追記] 勝手に IIS / http.sys / ARR の補足
普段 IIS / http.sys / ARR を触っているので、ここで YARP とは関係ないですが、勝手に補足入れます (笑)

#### IIS / http.sys
IIS や http.sys になじみがない方は急に上記のような話がでていますが、IIS という Windows 用の Web サーバーは、HTTP 関連の処理を OS のコンポーネント・カーネルドライバである http.sys に任せています。つまり、現在 IIS を利用するということは間接的に Windows OS 標準コンポーネントの http.sys というドライバにも依存しているということです。

#### ARR
また、ARR (Application Request Routing) というのは IIS を主にリバースプロキシとしても利用できるようにするためのモジュールです。つまり、ARR も基盤としては IIS/http.sys/Windows OS に依存しているわけです。一方で、リバプロの役割を果たすということは IIS がアプリを受け付けて何らかの処理をモジュール内部で処理するだけでなく、もちろん転送先に HTTP リクエストでリクエストを飛ばす役割が必要です。

ここで利用されるのが [WinHTTP](https://learn.microsoft.com/en-us/windows/win32/winhttp/about-winhttp) という Windows OS の http リクエストを扱えるコンポーネントです。ARR はリクエストを受け付けて処理する基盤側では先と同様に IIS/http.sys/Windows OS に依存し、リクエスト処理内の転送側の処理では WinHTTP に依存して処理しているという構成です。

これらが Kestrel and YARP ベースに移行したということです。

# Kestrel and YARP in a nutshell

## Kestrel
.NET 製の [Kestrel Web サーバー](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-7.0) は以下のような最近変更された特徴をもっています。

- many-core マシンでの大幅なスケーラビリティの向上
- 多数の同時ストリームを実行する際の HTTP/2 パフォーマンスの大幅な向上
- HTTP/3 などの新しい HTTP 標準のサポート

ちなみに、ASP.NET Core では、上記の Kestrel 以外にも Windows の場合は IIS インプロセスのホスティングや HTTP.sys によるホスティングといった方法でのホスティングなども存在します。

## YARP
[YARP (“Yet Another Reverse Proxy”)](https://github.com/microsoft/reverse-proxy) は .NET の技術を活かし出来る限り容易な extensibility を目指した高速なリバースプロキシであり、OSS として公開されています。YARP は HTTP/2 や HTTP/3 といった最新のプロトコルをサポートしています。.NET プラットフォームをベースにしていることで、.NET の進化の恩恵を間接的に受けれることも魅力です。興味がある方は例えば [Performance Improvements in .NET 6](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/#gc) を見ることをおすすめします。

### [追記] 勝手に YARP 補足
YARP は以下の YARP 用のドキュメントがあります。例えば、それぞれの用途や機能ごとに gRPC や HTTP/3、Session Affinity など細かく章が分かれて解説されています。

https://microsoft.github.io/reverse-proxy/articles/getting-started.html

API Documentation はこちらにあります。

https://microsoft.github.io/reverse-proxy/api/index.html

また、今後の YARP のロードマップは以下です。OSS の YARP 自体については一般的な OSS と同様に、以下の通り GitHub の Issues や Discussions を通じて bug report 等を報告することや Pull Requeset で修正依頼を直接提案頂くこともできます。

https://github.com/microsoft/reverse-proxy/blob/main/docs/roadmap.md

# Betting on Kestrel + YARP for App Service: Why?
IIS/HTTP.SYS 上に構築された App Service の従来の FrontEndRole アーキテクチャも貢献していた一方で、Kestrel + YARP の最新の HTTP スタックは新しい利益をもたらすことができる可能性があるとして、このプロジェクトは進められたようです。具体的に以下が挙げられています。

- リクエストごとの CPU コストと接続ごとのメモリコストの大幅な削減を含むパフォーマンスの向上
- SSL 終端パスへの柔軟な拡張ポイントにより、動的な SNI ホストの選択を容易にする
- gRPC のサポート、ホストごとの暗号スイート設定、カスタムエラーページなどの新たなシナリオを可能にする

このメリットのために、「FrontEndRole を実行している 20 万以上の専用コアを Kestrel + YARP に移行する（つまり IIS/HTTP.SYS/ARR から移行する）」 という目標を掲げたとのことです。

# Challenge: Server Framework Diversity
実は、App Serviceは Kestrel と YARP に移行する最初 の Microsoft サービスではありません。既に以下の一部の Bing、Azure Active Directory（AAD）、Dynamics 365 の.NET への移行によって重要なサービスのワークロードに対する .NET の安定性と性能が証明されていたと述べられています。

- [Bing.com runs on .NET Core 2.1](https://devblogs.microsoft.com/dotnet/bing-com-runs-on-net-core-2-1/)
- [Azure Active Directory’s gateway is on .NET 6.0](https://devblogs.microsoft.com/dotnet/azure-active-directorys-gateway-is-on-net-6-0/)
- [Dynamics 365 using YARP](https://youtu.be/67tCWKnweso?t=1803)

なお、App Service はあくまでアプリを動作させるための基盤であるプラットフォームのため、“service health” というコンセプトを考えていた時に、複雑であり、その測定が容易ではないことが記載されています。元 Blog の以下の部分に記載があり、これは興味深く、ここでは深掘りしませんが興味があれば以下を直接閲覧してみてください。

- [Challenge: Platform versus Organic Health](https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/#challenge-platform-versus-organic-health)
- [Challenge: Quick Rollback in Production](https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/#challenge-quick-rollback-in-production)
- [The Journey: 100% FrontEndRoles using Kestrel/YARP](https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/#the-journey-100-frontendroles-using-kestrel-yarp)

# The Bugs Encountered
Kestrel + YARP への移行にあたり遭遇した Bug の紹介があります。FrontEndRole における HTTP 仕様関連のエッジケースの扱いに関する問題が挙げられています。

例えば、リクエストの先頭に改行文字 (CR や LF) がある場合です。厳密に言えば、これは許されませんが、次のように始まるリクエストを送信するクライアントが存在していました。

```
\rGET / HTTP/1.1.1
...
```

これは、IIS（および他の一部のサーバー）では許可されているケースですが、Kestrel は歴史的にかなり厳しいスタンスを取っているため、パーサーがこのようなリクエストを BadHttpRequestException で拒否しているのを確認しました。ASP.NET Core チームと密接に協力して、Kestrelが 受け入れるものをもう少し寛大に緩和することができました（上記の例は現在 .NET 6.0.5 およびより新しいリリースのKestrel で機能しています）。

他のいくつかの興味深い問題は以下で見ることができます。

- https://github.com/dotnet/aspnetcore/pull/40833
- https://github.com/dotnet/aspnetcore/pull/40633
- https://github.com/microsoft/reverse-proxy/pull/1773

このような問題を調査し、対処した結果、セキュリティの基本原則を損なうことなく、Kestrel をより高性能なサーバーにすることができました。


# The Payoff: Performance and New Features, Now and in the Future
FrontEndRole を Kestrel + YARP に移行したことで、パフォーマンスの向上等の話が紹介されています。

また、FrontEndRoles を Kestrel + YARP に移行したことで、Linux ワーカー VM も Kestrel + YARP に移行することができたということが述べられています。この変更により、nginx を置き換え、コードベースを共通化し、App Service Linux SKU の [gRPC をサポート](https://azure.github.io/AppService/2022/05/23/gRPC-support-on-App-Service.html)することができました。

# A Great Partnership
最後の、本移行についての結びはぜひ原文で閲覧下さい。
https://devblogs.microsoft.com/dotnet/bringing-kestrel-and-yarp-to-azure-app-services/#a-great-partnership

# おわりに
gRPC サポートをはじめ、ある一機能の追加という側面の裏側では、実はこのような App Service の内部アーキテクチャの改善が寄与している部分があります。App Service やその一部を担う新しい YARP などにも少しでも興味をもっていただけたら幸いです。