C# で AWS Lambda を使う

AWS Lambda が公式に対応している言語は、Node.js、Java、Python で、C# では使えなかったので残念に思っていました。しかし、.NET Core 1.0 が公開され、Self-contained Apps だと実行ファイルが生成されて単独で実行できるので、Amazon Lamda で動作するのではないかということで、.NET Core コンソールアプリケーションをAmazon Lamda で動作させてみました。実際にやってみたらあまり問題なく動作するようなので紹介します。
※2016/7/5 アプリの発行は必ずしも Amazon Linux のマシンを使う必要はなく、Windows、Mac 等どのOSのマシンを使ってもできるということに気づいたので修正

# Amazon Linux に .NET Core をインストール

Lambda の実行環境は、以下のとおりです。（[AWS LAMBDA ドキュメント参照](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/current-supported-versions.html)）

* パブリック Amazon Linux AMI バージョン (AMI 名: amzn-ami-hvm-2015.09.1.x86_64-gp2):
 * AMI ID: 米国西部（オレゴン） リージョンの ami-f0091d91。
 * AMI ID: 米国東部（バージニア北部） リージョンの ami-60b6c60a。
 * AMI ID: 欧州（アイルランド） リージョンの ami-bff32ccc。
 * AMI ID: アジアパシフィック (東京) リージョンの ami-383c1956。
* Linux カーネルバージョン : 4.1.13-19.31.amzn1.x86_64

それと同じ Amazon Linux の環境を用意して、.NET Core アプリを作成してみます。本当のところは、libunwind の CentOS 用のライブラリーが必要なだけなので最新の Amazon Linux AMI 2016.03.3 でも CentOS 7.1 でも構いません。ただ、Lambda の環境がどうなっているかを知っておくことは大事です。

また、.NET Core のインストールは、Amazon Linux が、CentOS の派生ということなので、 CentOS のものを利用しす。
[.NET Core の CentOS 7.1 & Oracle Linux 7.1 のインストールのページ](https://www.microsoft.com/net/core#centos) のとおりでインストール可能です。

1 .NET Core SDK のインストール

``` 
sudo yum install libunwind libicu
curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=809131
sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet
sudo ln -s /opt/dotnet/dotnet /usr/local/bin
```

2 サンプルアプリケーションを作成

```
mkdir hwapp
cd hwapp
dotnet new
```
3 アプリケーションの実行

```
dotnet restore
dotnet run
```

Amazon Linux でも .NET Core が動作することが確認できたと思います。

最初の sudo yum install libunwind libicu の結果をみていると分かるのですが、Amazon Linux では、libicu はインストールされていますが、libunwind はインストールされていません。それで、Lambda の実行環境では libunwind インストールされていないので、libunwind のライブラリーを追加する必要があります。もし、CentOS 系のマシンを普段使っていない場合は、libunwind のライブラリーを保存しておきます。libunwind のライブラリーは、/usr/lib32 にあるので、次のようなコマンドで zip ファイルにして保存しておくと便利です。

```
cd /usr/lib32
zip -y ~/lambda.zip libunwind*
```

# Self-contained Apps の作成
以上の状態だと Portable Apps になっていて、.NET Core のランタイムはパブリックのものを使用するようになりますが、Lambda には、.NET Core のランタイムはインストールされていないので、そのままでは動作しません。それでランタイムも含まれていて単独で動作する Self-contained Apps が発行されるように設定を変更します。

初期状態だと project.json ファイルは以下のようになっています。

``` project.json
{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {},
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  }
}
```
それを編集して、"type": "platform" を削除し、runtimes の項目を追加して、以下のようにします。

``` project.json
{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {},
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "version": "1.0.0"
        }
      },
      "imports": "dnxcore50"
    }
  },
  "runtimes": {
    "centos.7-x64": {}
  }
}
```

この変更をすると、dotnet run をしても dotnet publish をしても、以下のエラーになってしまいます。

Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible with one of the target runtimes: 'amzn.2015.09-x64'. 

しかし、ランタイムに centos.7-x64 と指定すると発行ができるようになります。

```
dotnet publish --configuration Release --runtime centos.7-x64
```

発行先（出力先を指定していなければ bin/Release/netcoreapp1.0/centos.7-x64/publish/）をみると、hwapp.dll だけでは hwapp という名前の実行ファイルが作成されています。以下のコマンドで動作すれば、Self-contained Apps ができたということになります。

```
./hwapp
```

.NET Core は、マルチプラットフォームに対応しています。Portable Apps の場合は、すべてのプラットーフォーム共通で動作します。Self-contained Apps の場合は、プラットフォーム毎に違ってきますが、ランタイムを指定することで他のプラットフォームのものを作成することができます。だから、Windows でも Mac でも Ubuntu でも、--runtime centos.7-x64 をつければ、Lambda で使うバイナリを作成することができます。

なお、Visual Studio でも発行の時に設定で、ターゲットランタイムに centos.7-x64 を選択すれば作成できるはずなのですが、バグがあってエラーになってしまいます。最初だけ、コマンドラインで発行すると次回から発行できるようになります。ただ、Windows では、ファイルシステムが違うので Lambda 用のパーミッションの設定ができないので、Mac か Linux のマシンが必要になります。近くUbuntu on Windows が正式公開されるので。そうなれば Windows PC だけでも作成できるようになります。

# Lambda Function の作成

Lambda Function の作成は、AWS CLI コマンドを使って作成することもできますが、ここでは管理コンソールを使ってWebブラウザから操作する方法で説明します。

管理コンソールで、サービス「Lambda」を選択します。Lambda の管理画面が表示されたら「Create a Lambda function」 というボタンをクリックします。すりと「青写真」を選択する画面が表示されるので、node-exec を選択します。

![lambda02.png](https://qiita-image-store.s3.amazonaws.com/0/103055/5ed6806d-6fd8-f94d-f8ec-d0a210e7f6db.png)

.NET Core のモジュールも一緒にアップロードする必要があるので、表示されてるコードはコピーして、index.js という名前で保存します。それから、、Lambda function code の Code entry type は、Upload a .ZIP file か Upload a file from Amazon S3 を選択します。それから、index.js を以下のように修正して、JapaScript から hwapp を呼ぶようにします。

``` index.js
'use strict';
let exec = require('child_process').exec;

exports.handler = (event, context, callback) => {
    const child = exec("./hwapp", (error) => {
        // Resolve with result of process
        callback(error, 'Process complete!');
    });
      
    // Log process stdout and stderr
    child.stdout.on('data', console.log);
    child.stderr.on('data', console.error);
};
```

始めて、Lambda を使う場合は Lambda function 用のロールを作成する必要がありますが、ドキュメントに解説があるのでそちらをみてください。


# Lambda 用の zip ファイルの作成
index.js と コンソールアプリが作成できたら、次にそれを一つの zip ファイルにまとめる必要があります。その作業には結構注意が必要です。

### (1) libunwind のライブラリーを追加する
インストールの最初で libunwind のパッケージを追加しているので Lambda の実行環境にはインストールできていません。それで、/usr/lib64/ にある libunwind 関連のライブラリーをコピーして追加する必要があります。

``` 
cp /usr/lib64/libunwind* .
```
追加しないと次のようなエラーがでます。

Failed to load /var/task/libcoreclr.so, error: libunwind.so.8: cannot open shared object file: No such file or directory

CentOS 系以外のマシンを使う場合は、最初の所で作成した libunwind のライブラリーの zip ファイルに、zip コマンドで必要なファイルを追加していく形で作業すると楽に作成できると思います。

```
zip lambda.zip index.js
```

なお、.NET Core アプリの実行ファイルを index.js と同じディレクトリ内に置くのではなく、ディレクおりを作成してそこに置いた場合には、カレントディレクトリをそこに変更するかlibunwind のライブラリーを index.js のあるディレクトリに置くかする必要があります。

### (2) パーミッションの設定を行う
.NET CLI が作成したままだと、ファイルに「その他ユーザー」の実行許可のパーミションがついていません。それで Lambda を実行すると「Permission denied」というエラーになります。すべてのファイルに「その他ユーザー」の読み出し権限、すべての実行ファイルに実行許可のパーミションをつける必要があります。

パーミションの一括変更には、以下のようなコマンドを使うと便利です。大文字の X は、ディレクトリと実行可能ファイルとにだけ実行許可（ディレクトリの場合はディレクトリに移動）のパーミションをつけます。

```
chmod -R a=rX,u+w path
```

### (3) zip ファイルを作成するときには、index.js が最上位にくるようにする

次のようなコマンドで zip ファイルを作成すると最上位にディレクトリーが作成されるので Lambda では動きません。

```
zip -r hwapp.zip publish/
```

下の図のように zip ファイルの直下に index.js がくるように、index.js のあるディレクトリで zip コマンドを実行します。

```
zip -r hwapp.zip .
```
![lambda01.png](https://qiita-image-store.s3.amazonaws.com/0/103055/358529d0-0a60-2adc-681c-d9c9cc889900.png)


zip ファイルができたら、それをアップロードして、Save と Test をおこないます。

# Lambda の制限

AWS Lambda の制限は、[ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html)によると以下のようになっています。

AWS Lambda リソース制限

| リソース | 	デフォルトの制限 
|:-----------|------------:|
| 一時ディスク容量（"/tmp" スペース） |512 MB |     
| ファイル記述子の数	| 1,024 |
| プロセスとスレッドの数（両者の合計）	| 1,024 |
| リクエストあたりの最大実行時間	| 300 秒 |
| Invoke リクエスト本文のペイロードサイズ	| 6 MB |
| Invoke レスポンス本文のペイロードサイズ	| 6 MB |

AWS Lambda デプロイメントの制限

| 項目	| デフォルトの制限|
|:-----------|------------:|
| Lambda 関数デプロイパッケージのサイズ（.zip/.jar ファイル）	| 50 MB |
| デプロイパッケージ（非圧縮 zip/jar サイズ）に圧縮できるコード/依存関係のサイズ	| 250 MB |

hwapp のファイル容量は約50 MB、zip ファイルにすると約20 MB なのでサイズには余裕があります。リソース制限についてもそれほど問題になることはないと思います。

# Lambda の利用について
Lambda の利用については、AWS サービスをイベントソースとした Lambda 関数呼び出し、オンデマンドでの HTTPS 経由の Lambda 関数呼び出し、オンデマンドでの Lambda 関数呼び出しに加えて、スケジューラーによる呼び出しが可能で、また、VPC でも使用できるので、かなり広範囲な利用が可能と思います。

料金は、メモリ 1GB のものを 1時間使うと $0.06 なので、t2.medium よりやや高いという感じですが、EC2 が立ち上がっている時間は課金されるのに対して、Lambda はアプリケーションが動作している時間しか料金がかからないので、特定の時間しか動作しないようなケースではかなりのコスト削減になると思います。また、無料利用枠もり、それだけでもかなりの処理に使えます。

.NET Core の場合、hwapp は、殆ど何もしていないのですが、処理にメモリ125MBの場合だと、1秒以上の時間がかかります。毎回ランタイム等のモジュールをロードする必要があるので時間がかかっていると思われます。軽い処理が多数回あるようなケースには向いていないので、その場合は Node.js を使った方がいいと思います。

hwapp でのテストに成功したので、次に、[TKGMap](https://github.com/awoni/TKGMap) を使って運用のテストをしています。徳島県が公開している道路の通行規制情報のデータを定期的に取得して、Amazon S3 にアップして[徳島の交通状況 Google Maps API 版](http://tk.ecitizen.jp/)として公開しているものです。

今のところ問題なく動作しています。ログは以下の図のように CloudWatch の方に保存されます。
![lambda03.png](https://qiita-image-store.s3.amazonaws.com/0/103055/277af9ce-6e50-8dfc-aceb-f9b45d222675.png)

メモリ256 MB で 15分毎に処理をしていて、1回の処理時間は約 10 秒です。1ヶ月では、使用するコンピューティング時間は約 7,500 GB・秒になり、料金は12円ぐらいですが、無料利用枠のコンピューティング時間が 400,000 GB・秒あるので、このようなものであれば 50個ぐらいまでは無料で使えます。バックグラウンドでこういう処理が走るとその時間はサーバーが重くなってしまうので、Lambdaはそれを低料金で解消できる便利な機能だと思います。