ApexでAmazon Lambdaにデプロイ

Amazon Lambda関数の構築、展開、管理ツールである[Apex](http://apex.run/)を使ってLambdaに関数をデプロイしてみる。

# 実行環境
macOS High Sierra 10.13.3

# 環境構築
Lambdaがサポートしている言語はいくつかありますが、今回はPython3.6系でやっていきます。
まず最初に仮想環境を作ります。（任意です）
Pythonの仮想環境を作る方法はいくつかありますが、今回はcondaを使います。
Anacondaをインストールしてcondaコマンドを実行します。

### lambda-envという名前で環境作成

```console
$ conda create -n lambda-env python=3.6
```

### 環境の切り替え

```console
$ source activate lambda-env
$ python --version
Python 3.6.3 :: Anaconda custom (64-bit)
```

# Apexの導入
公式サイトに書いている通りにやっていきます。
http://apex.run/

### インストール
```console
$ curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh
```

### AWS資格情報の設定
AWSの資格情報を環境変数か、`~/.aws`以下に設定します。
アクセスキーやシークレットキーはIAMユーザ作成時に取得できます。
とりあえず何もアクセス権限を持たない`lambda-user`というユーザを作成し、aws cliで設定を行います。

```
$ aws configure --profile lambda-user
```

```shell:~/.aws/credentials
[lambda-user]
aws_access_key_id = xxxxxxxx
aws_secret_access_key = xxxxxxx
```

```shell:~/.aws/config
[lambda-user]
region = ap-northeast-1
output = json
```

### Minimum IAM Policy
まず、以下の(公式サイト参照)IAM PolicyをIAM->ポリシー->ポリシーの作成のJSONタブを選択し、貼り付けます。

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "iam:CreateRole",
        "iam:CreatePolicy",
        "iam:AttachRolePolicy",
        "iam:PassRole",
        "lambda:GetFunction",
        "lambda:ListFunctions",
        "lambda:CreateFunction",
        "lambda:DeleteFunction",
        "lambda:InvokeFunction",
        "lambda:GetFunctionConfiguration",
        "lambda:UpdateFunctionConfiguration",
        "lambda:UpdateFunctionCode",
        "lambda:CreateAlias",
        "lambda:UpdateAlias",
        "lambda:GetAlias",
        "lambda:ListAliases",
        "lambda:ListVersionsByFunction",
        "logs:FilterLogEvents",
        "cloudwatch:GetMetricStatistics"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
```

<img width="1220" alt="スクリーンショット 2018-06-17 2.00.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/217930/06e70dcd-f691-46ed-f459-b85480337967.png">

作成したポリシーを先ほど作成したユーザにアタッチします。(ユーザグループにアタッチして、ユーザをグループに含めるのでも大丈夫です)

## プロジェクト作成
```console
$ apex init --profile lambda-user --region ap-northeast-1
```

初期状態ではnode.jsの関数が用意されています。

```
.
├── functions
│   └── hello
│       └── index.js
└── project.json
```

Pythonで関数を作りたいので、helloフォルダは削除します。

## 関数の作成
今回はPythonのライブラリpandasを使って基本統計量を求める関数を試しに作ってみます。
`functions`以下に`describe`というフォルダを作り移動します。

`function.json`という設定ファイルと、`index.py`という空のファイル作ります。

```json:function.json
{
  "description": "pandas describe",
  "runtime": "python3.6",
  "handler": "index.lambda_handler",
  "environment":{
    "PYTHONPATH": "/var/runtime:/var/task/site-packages"
  }
}
```

外部ライブラリをLambdaで使いたいときはデプロイパッケージに含める必要があります。

ライブラリの設定ファイル`requirements.txt`を作成します。

```txt:requirements.txt
pandas==0.23.1
```

次にライブラリをインストールするフォルダを作成しておきます。

```console
$ mkdir site-packages
```

フォルダの構成はこんな感じです。

```
.
├── functions
│   └── describe
│       ├── function.json
│       ├── index.py
│       ├── requirements.txt
│       └── site-packages
└── project.json
```

pandasとその依存ライブラリをsite-packagesにインストールしたいところですが、

```console
$ pip install -r requirements.txt -t site-packages
```

これだとmac環境でビルドされたライブラリになり、Amazon Linux環境のLambdaでは実行できません。

例えば、`import numpy`した関数をLambdaにdeployして実行すると、
`Unable to import module 'index': Missing required dependencies ['numpy']`
こんなエラーが出て実行することができません。
LambdaはAmazon Linux環境なので、同じ環境でビルドしたライブラリでないと実行ができないからです。

# Linux環境でpip install
dockerでLinuxのコンテナを作って`pip install`します。
https://qiita.com/hoto17296/items/a579c333a8690c96a56c
やり方はこちらの記事を参考にさせていただきました。

```console
$ docker pull python:3.6
$ docker run --rm -v $(pwd):/work -w /work python:3.6 pip install -r requirements.txt -t site-packages
```

正確にはAmazon Linux環境ではないですが、これでLambdaで実行できます。
Linux上で開発したいときは、

```console
$ docker run -it --name ${CONTAINER NAME} python:3.6 /bin/bash
```

でコンテナ上に入って開発するなどすれば良さそうです。
（たまに２回`pip install`しないとうまくインストールできないライブラリがあったりするので、そういうときはコンテナ上でインストールする必要があったりします）

# 実行する関数
今回は適当にpandasのdescribe関数を使うだけの関数を作りました。

```python:index.py
import pandas as pd

def lambda_handler(event, context):
    df = pd.DataFrame({
        'hoge': [1, 2, 3, 4, 5],
    })
    return df.describe().to_json()
```

# deploy

```console
$ apex deploy describe --profile lambda-user --region ap-northeast-1
```

# コンソールから実行
```console
$ apex invoke describe --profile lambda-user --region ap-northeast-1
"{\"hoge\":{\"count\":5.0,\"mean\":3.0,\"std\":1.5811388301,\"min\":1.0,\"25%\":2.0,\"50%\":3.0,\"75%\":4.0,\"max\":5.0}}"
```

delopyと実行ができていることがわかります。
AWSのコンソールで確認すると、確かにデプロイされていました。
<img width="1516" alt="スクリーンショット 2018-06-19 0.24.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/217930/56bd7fba-4e14-de1d-ff3a-b88817285446.png">

# まとめと課題
ApexでAmazon LambdaにPythonの関数をデプロイして実行することができました。
S3にcsvファイルアップロードしたタイミングで実行したりもできるので、Lambdaを使うことで色々できることの幅が広がりそうです。
ただし、[deploy packageのサイズの制限](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html)があり、分析用のライブラリのサイズによってはLambdaで使えなかったりしました...
そういった場合、どのように解決しているのか気になります:thinking_face: