F# で AWS Lambda を実行する


本記事は F# Advent Calendar 2016 に参加しています。

先日の AWS re:Invent 2016 で、 AWS Lambda で C# のサポートが発表されました。 C# で動くなら F# で動くはずだ、ということで、やってみました。

免責: 私の環境は macOS Sierra です。 Windows の方は適当に読み替えてください。

Lambda の C# ランタイムは、 .NET Core 1.0 runtime で動いています。つまり F# のコードも .NET Core 1.0 runtime で動かす設定にすれば動くはずです。ということで、 .NET Core 1.0 を公式サンプルのアナウンスのサンプルにしたがってやっていきます。

環境準備

.NET Core 1.0 SDK

.NET Core 1.0 SDK をインストールします。 Google で .NET Core を検索すると最新版の 1.1 がヒットしますが、これをインストールしても後で 1.0 をインストールしろと怒られますので、 .NET Core Downloads から 1.0 をダウンロードしてインストールします。

macOS 環境で .NET Core を動かしたい場合は、 Advent calendar 5 日目の『MacOSX+.NET Core SDK+VS CodeでF#開発』が参考になります。ただし、 .NET Core 1.0 SDK である点だけ注意します。

IAM Role

Lambda 関数に与えるロールを作成します。詳細は割愛します。後述しますが、デプロイ時に作成することもできます。

プロジェクト作成

project.json

dotnet でプロジェクトテンプレートを作成します。

1
2
3
mkdir fsharp-advent-calendar-2016
cd fsharp-advent-calendar-2016/
dotnet init --lang F#

ディレクトリー内には Program.fsproject.json が作成されます。

AWS Lambda に必要な依存ライブラリーを追加するために project.json をいじり、以下のように変更します[A]

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
  "version": "1.0.0-*",
  "buildOptions": {
    "outputName": "ASSEMBLY_NAME",
    "compilerName": "fsc",
    "compile": [
      "Program.fs"
    ]
  },
   
  "dependencies": {
    "Amazon.Lambda.Core": "1.0.0",
    "Amazon.Lambda.Serialization.Json": "1.0.0",
    "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-*",
 
    "Amazon.Lambda.Tools": {
      "type": "build",
      "version": "1.0.0-preview1"
    },
    "dotnet-compile-fsc": {
      "type": "build",
      "version": "1.0.0-preview2-*"
    }
  },
 
  "tools": {
    "Amazon.Lambda.Tools": "1.0.0-preview1",
    "dotnet-compile-fsc": "1.0.0-preview2-*"
  },
 
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
 
      "imports": "dnxcore50"
    }
  }
}

依存関係を修正したら、依存ライブラリーをインストールします[B]

1
dotnet restore

aws-lambda-tools-defaults.json

aws-lambda-tools-defaults.json は、 dotnet lambda の設定値を定義します。作成しなくても対話的に入力できますが、入力項目が多く面倒なので、作成しておくと良いでしょう。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
{
  "Information" : [],
 
  "profile":"",
  "region" : "ap-northeast-1",
  "configuration" : "Release",
  "framework" : "netcoreapp1.0",
  "function-runtime":"dotnetcore1.0",
  "function-name": "FUNCTION_NAME",
  "function-memory-size" : 128,
  "function-timeout" : 300,
  "function-handler" : "ASSEMBLY_NAME::CLASS_NAME::FUNCTION_NAME",
  "function-role": "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
}

自分の環境に応じて適宜設定値を変更してください。

ここで微妙にハマったのですが、 Lambda 関数は初回実行時のみ時間が非常にかかるため、 function-timeout に最小の 5(秒)を設定すると、タイムアウトしてしまいました。 2 回目以降の実行は高速に動きますし、 Lambda は実行時間で課金されるので、大きな値を設定していても特に問題ないと思います。

function-handler はアセンブリー名、クラス名、関数名を :: で区切ったものです。アセンブリー名は、 project.jsonbuildOptions.outputName を指定していればその名前になり、指定しなければディレクトリー名(本記事の例では fsharp-advent-calendar-2016)となります。

関数の作成

dotnet init で作成された Program.fs を以下のような感じに書き換えます。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module FSharpAdventCalendar2016.Day10.LambdaFunction
 
open Amazon.Lambda.Core
open Amazon.Lambda.Serialization.Json
open Newtonsoft.Json
 
[<assembly: LambdaSerializer(typeof<JsonSerializer>)>]
 
type InputValue = {
   [<JsonProperty("question")>]
   Question : string
}
 
type ResultValue = {
   [<JsonProperty("answer")>]
   Answer : int
}
 
[<CompiledName("FunctionHandler")>]
let handle (input:InputValue) (context:ILambdaContext) =
   match input.Question.ToLower () with
   | "the answer to the ultimate question of life the universe and everything"
   | "the answer to life the universe and everything" -> { Answer = 42 }
   | _ -> { Answer = 0 }

いちばん重要なのは aws-lambda-tools-defaults.jsonfunction-handler で指定する関数名で、この例では handle 関数です。この例では FunctionHandler を指定しているので、 function-handler は "FSharpAdventCalendar2016.Day10::FSharpAdventCalendar2016.Day10.LambdaFunction::FunctionHandler" のようになります。

handle 関数は最初の引数が任意の型で、 LambdaSerializer で指定されたシリアライザーによりシリアライズ・デシリアライズされます。この例では JSON 形式で。入力が {"question":""} のような形式、出力が {"answer":0} のような形式になります。

2 つめの引数が ILambdaContext で、 Lambda に関する情報が含まれます。ちなみに Lambda 関数がリクエストにパラメーターを必要としない場合は、 1 つめの引数を省略して ILambdaContext だけでも動きます。

デプロイ

最後に関数のデプロイを行います。以下のコマンドで、ビルドとデプロイが実行されます。

1
dotnet lambda deploy-function

function-role を指定しないと、既存のロールから選択するか、新規に作成するかを選択されます。

Lambda 関数の実行

以下のコマンドで Lambda 関数リストが取得できます。正しくデプロイされていると、作成した関数が表示されます。

1
dotnet lambda list-functions

以下のように Lambda 関数を実行することができます。

1
2
3
dotnet lambda invoke-function \
   --function-name <FUNCTION_NAME> \
   --payload '{"question":"the answer to life the universe and everything"}'

関数の削除

以下のコマンドで Lambda 関数を削除できます。

1
dotnet lambda list-functions <FUNCTION_NAME>

まとめ

AWS Lambda で F# が使えるようになったよ!

補足

dotnet lambda の使い方は、 help サブコマンドを利用します。

1
dotnet lambda help [subcommand]

おわりに

コードは GitHub に公開しています

変更履歴

  • [2016-12-10 09:45] .NET Core のインストール方法の参考文献を追記。
  • [2016-12-10 09:45] パラメーターがない場合のハンドラーのパラメーターについて追記。
  • [2016-12-10 09:45] 実行方法を追記。
  • [2016-12-11 15:10] コードの誤りを修正。

脚注

  1. バージョン周りは適当に読み替えてください []
  2. Visual Studio Code を利用している場合は保存時に []