ついにLambdaでVB.NETが!!AWS Lambda の Custom Runtimes を利用してVB.NETを実行してみる #reinvent
サーバーレス開発部@大阪の岩田です。 この記事は AWS Lambda Custom Runtimes芸人 Advent Calendar 2018 の 8日目 です。
はじめに
Microsoftが大好きな皆様、Lambdaで.NETは使っていますか?? 昨年の今頃、re:Invent2017にてLambdaの.NET Core対応が発表されました。 この発表を聞いて歓喜したMSファンも多かったのではないでしょうか?
しかし、この.NET Core対応には1つ大きな問題がありました。 対応している言語がC#のみなのです!! 泣く泣くLambdaの利用を諦めたVB.NET派の方が多数いらっしゃったことと想像します。 しかし、今年のre:Invent2018でCustom Runtimesの機能が発表されました。 ついにLambdaでVB.NETを動かせる日が来たのです!!
本エントリではVB.NET on .NET Coreのカスタムランタイムを作成し、LambdaでVB.NETのコードを動かしてみます!
環境
開発環境は下記の通りです。
- macOS High Sierra Darwin Kernel Version 17.7.0
- .NET Core 2.1.301
カスタムランタイム用のレイヤー作成
それでは早速ランタイムを作成していきます。 まずはプロジェクトの雛形を作成します。
dotnet new console -lang VB -o bootstrap
作成されたProgram.vbを下記の通り修正します。動作させるためだけのコードなので、もし実際に使う場合は適宜戻り値のチェックやエラーハンドリングを追加して下さい。
Imports System Imports System.Linq Imports System.Reflection Imports System.Net.Http Imports System.Text Imports System.Threading.Tasks Class Program Public Shared Sub Main(ByVal args As String()) MainAsync().GetAwaiter().GetResult() End Sub Private Shared Async Function MainAsync() As Task Dim path As String = System.IO.Path.Combine(Environment.GetEnvironmentVariable("LAMBDA_TASK_ROOT"), Environment.GetEnvironmentVariable("_HANDLER")) Dim asm As Assembly = Assembly.LoadFrom(path) Dim myType As Type = asm.[GetType]("handler.Handler") Dim handler As MethodInfo = myType.GetMethod("Main") Dim obj = Activator.CreateInstance(myType) Dim endPoint = Environment.GetEnvironmentVariable("AWS_LAMBDA_RUNTIME_API") Dim client = New HttpClient() While True Dim response = Await client.GetAsync("http://" & endPoint & "/2018-06-01/runtime/invocation/next") Dim requestId = response.Headers.GetValues("Lambda-Runtime-Aws-Request-ID").FirstOrDefault() Dim json = Await response.Content.ReadAsStringAsync() Dim res = handler.Invoke(obj, New Object() {json}) Dim content = New StringContent(res.ToString(), Encoding.UTF8) response = Await client.PostAsync("http://" & endPoint & "/2018-06-01/runtime/invocation/" & requestId & "/response", content) End While End Function End Class
ポイントは
Dim myType As Type = asm.[GetType]("handler.Handler") Dim handler As MethodInfo = myType.GetMethod("Main")
の部分です。 Lambda functionとしてデプロイされたdllから動的にアセンブリを読み込んでメソッドを呼び出します。
実装できたら自己完結型として実行ファイルを作成します。 自己完結型とすることで、.NET Coreのランタイムまで含めて実行ファイルを発行することができます。
dotnet publish -c Release -f netcoreapp2.1 -r linux-x64
作成できたらLambdaへのデプロイ用にZIPに固めます。 bootstrapに実行権限を付けるのを忘れないで下さい。
cd bin/Release/netcoreapp2.1/linux-x64/publish/ chmod +x bootstrap zip -r bootstrap.zip .
ZIPができたらLambdaのレイヤーを作成します。 「互換性のあるランタイム」を指定しないのがポイントです。
Lambda function用のdll作成
次にLambda function用のコードをVB.NETで実装し、dllを作成します。
dotnet new classlib -lang VB -o handler
生成されたClass1.vbをHandler.vbにリネームし、コードを修正します。
Public Class Handler Public Function Main(ByVal json as String) return json End Function End Class
引数で受け取った文字列をそのまま返すだけの処理です。 前述の通りbootstrapではアセンブリを動的に読み込んでメソッドを呼び出しているので、クラス名やメソッド名はbootstrap側で指定しているものと合わせる必要があります。
実装できたらビルドします。
dotnet build -c Release -r linux-x64
作成されたhandler.dllをzipに圧縮します
bin/Release/netstandard2.0/linux-x64/ zip handler.zip handler.dll
Lambda functionの作成
最後にLambda functionを作成します。 ランタイムには「独自のランタイム使用する」を選択します。
レイヤーとして先ほど作成したレイヤーを指定します。
関数コードとして作成したhandler.zipをアップロードし、ハンドラにhandler.dll
を指定して保存します。
試してみる
ここまでできたら適当なテストイベントを設定してテストを実行してみます。
ついに、、、VB.NETのコードがLambdaで動きました!!
まとめ
VB.NETで実装したコードをカスタムランタイム上で動作させてみました。 カスタムランタイムの実装ではbootstrapをシェルスクリプトで実装している例も多いのですが、bootstrapも含めてVB.NET縛りで実装できて満足です。
.NETのメリットの1つとして、複数の言語から自分の好きな言語を選択して開発できるという点がありますが、今回紹介したカスタムレイヤーの上で Lambda functionごとにC#やF#を使い分けて遊ぶのも良いかもしれません。
VB.NET派、F#派の方の参考になれば幸いです。