

# .NET Lambda 関数コードをネイティブランタイム形式にコンパイルする
<a name="dotnet-native-aot"></a>

.NET 8 はネイティブ ahead-of-time (AOT) コンパイルをサポートします。ネイティブ AOT を使用することで、Lambda 関数コードをネイティブなランタイム形式にコンパイルできるので、実行時に .NET コードをコンパイルする必要がなくなります。ネイティブ AOT コンパイルは、.NET で記述する Lambda 関数のコールドスタート時間を短縮できます。詳細については、AWS コンピューティングブログの「[Introducing the .NET 8 runtime for AWS Lambda](https://aws.amazon.com/blogs/compute/introducing-the-net-8-runtime-for-aws-lambda/)」を参照してください。

**Topics**
+ [Lambda ランタイム](#dotnet-native-aot-runtime)
+ [前提条件](#dotnet-native-aot-prerequisites)
+ [使用開始](#dotnet-native-aot-getting-started)
+ [シリアル化](#dotnet-native-aot-serialization)
+ [トリミング](#dotnet-native-aot-trimming)
+ [トラブルシューティング](#dotnet-native-aot-troubleshooting)

## Lambda ランタイム
<a name="dotnet-native-aot-runtime"></a>

ネイティブ AOT コンパイルで構築された Lambda 関数をデプロイするには、 マネージド .NET 8 Lambda ランタイムを使用します。このランタイムは、x86\$164 アーキテクチャと arm64 アーキテクチャの両方の使用をサポートします。

AOT を使用せずに .NET Lambda 関数をデプロイすると、アプリケーションが中間言語 (IL) コードにコンパイルされます。実行時、Lambda ランタイム just-in-time (JIT) コンパイラが、必要に応じて IL コードを取得してマシンコードにコンパイルします。ネイティブ AOT で事前にコンパイルされた Lambda 関数では、関数をデプロイするときにコードをマシンコードにコンパイルするため、Lambda ランタイムの .NET ランタイムや SDK に依存しずに、実行前にコードをコンパイルできます。

AOT の制限の 1 つは、アプリケーションコードを、.NET 8 ランタイムが使用するのと同じ Amazon Linux 2023 (AL2023) オペレーティングシステムを使用する環境でコンパイルする必要があることです。.NET Lambda CLI には、AL2023 イメージを使用して Docker コンテナでアプリケーションをコンパイルする機能があります。

アーキテクチャ間の互換性に関する潜在的な問題を避けるため、関数用に設定したのと同じプロセッサアーキテクチャの環境でコードをコンパイルすることを強くお勧めします。クロスアーキテクチャコンパイルの制限の詳細については、Microsoft .NET ドキュメントの「[クロスコンパイル](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/cross-compile)」を参照してください。

## 前提条件
<a name="dotnet-native-aot-prerequisites"></a>

**Docker**  
ネイティブ AOT を使用するには、関数コードを、 ランタイムと同じ AL2023 オペレーティングシステムの環境でコンパイルする必要があります。以下のセクションで説明する .NET CLI コマンドは、Docker を使用して AL2023 環境で Lambda 関数を開発および構築します。

**.NET 8 SDK**  
ネイティブ AOT コンパイルは .NET 8 の機能です。ビルドマシンには、ランタイムだけでなく、[.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) もインストールする必要があります。

**Amazon.Lambda.Tools**  
Lambda 関数を作成するには、[https://www.nuget.org/packages/Amazon.Lambda.Tools](https://www.nuget.org/packages/Amazon.Lambda.Tools) [.NET Global Tools 拡張機能](https://aws.amazon.com/blogs/developer/net-core-global-tools-for-aws/)を使用します。Amazon.Lambda.Tools をインストールするには、以下のコマンドを実行します。  

```
dotnet tool install -g Amazon.Lambda.Tools
```
Amazon.Lambda.Tools .NET CLI 拡張機能の詳細については、GitHub の「[AWS Extensions for .NET CLI](https://github.com/aws/aws-extensions-for-dotnet-cli)」リポジトリを参照してください。

**Amazon.Lambda.Templates**  
Lambda 関数コードを生成するには、[https://www.nuget.org/packages/Amazon.Lambda.Templates](https://www.nuget.org/packages/Amazon.Lambda.Templates) NuGet パッケージを使用します。このテンプレートパッケージをインストールするには、以下のコマンドを実行します。  

```
dotnet new install Amazon.Lambda.Templates
```

## 使用開始
<a name="dotnet-native-aot-getting-started"></a>

.NET Global CLI と AWS Serverless Application Model (AWS SAM) はどちらも、ネイティブ AOT を使用してアプリケーションを構築するための開始用テンプレートを提供します。最初のネイティブ AOT Lambda 関数を構築するには、以下の手順を実行します。

**ネイティブ AOT コンパイル済み Lambda 関数を初期化してデプロイするには**

1. ネイティブ AOT テンプレートを使用して新しいプロジェクトを初期化し、作成した `.cs` ファイルと `.csproj` ファイルが含まれるディレクトリに移動します。この例では、関数に `NativeAotSample` という名前を付けています。

   ```
   dotnet new lambda.NativeAOT -n NativeAotSample
   cd ./NativeAotSample/src/NativeAotSample
   ```

   ネイティブ AOT テンプレートによって作成された `Function.cs` ファイルには、次の関数コードが含まれています。

   ```
   using Amazon.Lambda.Core;
   using Amazon.Lambda.RuntimeSupport;
   using Amazon.Lambda.Serialization.SystemTextJson;
   using System.Text.Json.Serialization;
   
   namespace NativeAotSample;
   
   public class Function
   {
       /// <summary>
       /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase. It
       /// initializes the .NET Lambda runtime client passing in the function handler to invoke for each Lambda event and
       /// the JSON serializer to use for converting Lambda JSON format to the .NET types.
       /// </summary>
       private static async Task Main()
       {
           Func<string, ILambdaContext, string> handler = FunctionHandler;
           await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>())
               .Build()
               .RunAsync();
       }
   
       /// <summary>
       /// A simple function that takes a string and does a ToUpper.
       ///
       /// To use this handler to respond to an AWS event, reference the appropriate package from
       /// https://github.com/aws/aws-lambda-dotnet#events
       /// and change the string input parameter to the desired event type. When the event type
       /// is changed, the handler type registered in the main method needs to be updated and the LambdaFunctionJsonSerializerContext
       /// defined below will need the JsonSerializable updated. If the return type and event type are different then the
       /// LambdaFunctionJsonSerializerContext must have two JsonSerializable attributes, one for each type.
       ///
       // When using Native AOT extra testing with the deployed Lambda functions is required to ensure
       // the libraries used in the Lambda function work correctly with Native AOT. If a runtime
       // error occurs about missing types or methods the most likely solution will be to remove references to trim-unsafe
       // code or configure trimming options. This sample defaults to partial TrimMode because currently the AWS
       // SDK for .NET does not support trimming. This will result in a larger executable size, and still does not
       // guarantee runtime trimming errors won't be hit.
       /// </summary>
       /// <param name="input"></param>
       /// <param name="context"></param>
       /// <returns></returns>
       public static string FunctionHandler(string input, ILambdaContext context)
       {
           return input.ToUpper();
       }
   }
   
   /// <summary>
   /// This class is used to register the input event and return type for the FunctionHandler method with the System.Text.Json source generator.
   /// There must be a JsonSerializable attribute for each type used as the input and return type or a runtime error will occur
   /// from the JSON serializer unable to find the serialization information for unknown types.
   /// </summary>
   [JsonSerializable(typeof(string))]
   public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
   {
       // By using this partial class derived from JsonSerializerContext, we can generate reflection free JSON Serializer code at compile time
       // which can deserialize our class and properties. However, we must attribute this class to tell it what types to generate serialization code for.
       // See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-source-generation
   ```

   ネイティブ AOT は、アプリケーションを単一のネイティブバイナリにコンパイルします。このバイナリのエントリポイントは `static Main` メソッドです。`static Main` 内部では、Lambda ランタイムがブートストラップされ、`FunctionHandler` メソッドが設定されます。ランタイムブートストラップの一部として、ソース生成のシリアライザーは `new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>()` を使用して設定されます。

1. アプリケーションを Lambda にデプロイするには、Docker がローカル環境で実行されていることを確認し、次のコマンドを実行します。

   ```
   dotnet lambda deploy-function
   ```

   バックグラウンドでは、.NET Global CLI が AL2023 Docker イメージをダウンロードし、実行中のコンテナ内でアプリケーションコードをコンパイルしています。コンパイルされたバイナリは、Lambda にデプロイされる前にローカルファイルシステムに出力されます。

1. 次のコマンドを実行して関数をテストします。`<FUNCTION_NAME>` を、デプロイウィザードで関数に選択した名前に置き換えます。

   ```
   dotnet lambda invoke-function <FUNCTION_NAME> --payload "hello world"
   ```

   CLI からの応答には、コールドスタートのパフォーマンスの詳細 (初期化時間) と関数呼び出しの合計実行時間が含まれます。

1. 前の手順で作成した AWS リソースを削除するには、次のコマンドを実行します。`<FUNCTION_NAME>` を、デプロイウィザードで関数に選択した名前に置き換えます。使用しなくなった AWS リソースを削除することで、AWS アカウントに請求される料金の発生を防ぎます。

   ```
   dotnet lambda delete-function <FUNCTION_NAME>
   ```

## シリアル化
<a name="dotnet-native-aot-serialization"></a>

ネイティブ AOT を使用して Lambda に関数をデプロイするには、関数コードで[ソース生成のシリアル化](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation-modes?pivots=dotnet-8-0)を使用する必要があります。ランタイムリフレクションを使用し、シリアル化のためにオブジェクトプロパティにアクセスするのに必要なメタデータを収集する代わりに、ソースジェネレーターは、アプリケーションの構築時にコンパイルされる C\$1 ソースファイルを生成します。ソース生成のシリアライザーを正しく設定するには、関数が使用する入力オブジェクトと出力オブジェクト、およびカスタムタイプが含まれていることを確認します。例えば、API Gateway REST API からイベントを受信してカスタム `Product` タイプを返す Lambda 関数には、次のように定義されたシリアライザーが含まれます。

```
[JsonSerializable(typeof(APIGatewayProxyRequest))]
[JsonSerializable(typeof(APIGatewayProxyResponse))]
[JsonSerializable(typeof(Product))]
public partial class CustomSerializer : JsonSerializerContext
{
}
```

## トリミング
<a name="dotnet-native-aot-trimming"></a>

ネイティブ AOT は、バイナリができるだけ小さくなるように、コンパイルの一部としてアプリケーションコードをトリミングします。.NET 8 for Lambda は、以前のバージョンの .NET と比較して、トリミングサポートが改善されています。[Lambda ランタイムライブラリ](https://github.com/aws/aws-lambda-dotnet/pull/1596)、[AWS .NET SDK ](https://github.com/aws/aws-sdk-net/pulls?q=is%3Apr+trimming)、[.NET Lambda アノテーション](https://github.com/aws/aws-lambda-dotnet/pull/1610) 、.NET 8 自体にサポートが追加されました。

これらの改善により、ビルド時のトリミング警告を排除できる可能性がありますが、.NET が完全にトリムセーフになることはありません。つまり、関数が依存するライブラリの一部が、コンパイルステップの一部としてトリミングされる可能性があるということです。これを管理するには、次の例に示すように、`TrimmerRootAssemblies` を `.csproj` ファイルの一部として定義することでこれを管理できます。

```
<ItemGroup>
    <TrimmerRootAssembly Include="AWSSDK.Core" />
    <TrimmerRootAssembly Include="AWSXRayRecorder.Core" />
    <TrimmerRootAssembly Include="AWSXRayRecorder.Handlers.AwsSdk" />
    <TrimmerRootAssembly Include="Amazon.Lambda.APIGatewayEvents" />
    <TrimmerRootAssembly Include="bootstrap" />
    <TrimmerRootAssembly Include="Shared" />
</ItemGroup>
```

トリミング警告が表示された場合は、警告を生成するクラスを `TrimmerRootAssembly` に追加しても、問題は解決しない可能性があります。トリミング警告は、ランタイムまで決定できない他のクラスにクラスがアクセスしようとしていることを示します。ランタイムエラーを回避するには、この 2 番目のクラスを `TrimmerRootAssembly` に追加します。

トリミング警告の管理の詳細については、Microsoft .NET ドキュメントの「[Introduction to trim warnings](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings)」を参照してください。

## トラブルシューティング
<a name="dotnet-native-aot-troubleshooting"></a>

**Error: Cross-OS native compilation is not supported.(エラー: クロス OS のネイティブコンパイルはサポートされません。)**  
使用している Amazon.Lambda.Tools .NET Core グローバルツールのバージョンが古くなっています。最新のバージョンにアップデートしてから、再試行してください。

**Docker: image operating system "linux" cannot be used on this platform. (Docker: このプラットフォームでイメージオペレーティングシステム「linux」を使用することはできません。)**  
システム上の Docker は、Windows コンテナを使用するように設定されています。Linux コンテナに切り替えて、ネイティブ AOT 構築環境を実行してください。

一般的なエラーの詳細については、GitHub の [AWS NativeAOT for .NET](https://github.com/awslabs/dotnet-nativeaot-labs#common-errors) リポジトリを参照してください。