Apexを使ってLambda Function(Python)をデプロイしてみる


[Apex](http://apex.run/)は、いくつもの言語をサポートしていますが、今回はPythonを用いたパターンでサクッとLambdaファンクションをデプロイして実行してみるところまでをやってみます。

# 1. Apexをインストールする

使用する環境は以下のとおりです。

* OS: OS X EL Capitan
* Apex  : v0.10.2
* python: v2.7.10

## インストール
以下のコマンドを実行します。

```cmd
curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh
```

動作確認を兼ねて、以下のコマンドでバージョンを確認します。

```cmd
apex version
Apex version 0.10.2
```

# 2. AWSのクレデンシャルを設定する

Apexのりようには、AWS CLIと同様にAWSのクレデンシャル情報を指定する必要があります。

```~/.aws/config
[default]
output = json
region = ap-northeast-1
```

```~/.aws/credentials
[default]
aws_access_key_id = XXXXXXXXXX
aws_secret_access_key = XXXXXXXXXX
```

# 3. Apexプロジェクトを作成する

## プロジェクトの雛形作成

Apexのプロジェクトジェネレーターを使って、プロジェクトの雛形を作成します。

```cmd
mkdir apex-sample
cd apex-samle
apex init
```

最初に、`Project name`と`Project description`を入力します。

```cmd
             _    ____  _______  __
            / \  |  _ \| ____\ \/ /
           / _ \ | |_) |  _|  \  /
          / ___ \|  __/| |___ /  \
         /_/   \_\_|   |_____/_/\_\



  Enter the name of your project. It should be machine-friendly, as this
  is used to prefix your functions in Lambda.

    Project name: apex-sample

  Enter an optional description of your project.

    Project description: apex-sample

```

処理が完了すると、デプロイコマンドが表示されます。


```cmd
  [+] creating IAM hoge-tes_lambda_function role
  [+] creating IAM hoge-tes_lambda_logs policy
  [+] attaching policy to lambda_function role.
  [+] creating ./project.json
  [+] creating ./functions

  Setup complete, deploy those functions!

    $ apex deploy
```

## プロジェクトのディレクトリ

ジェネレーターで作成されるプロジェクトの雛形は以下の通りです。
デフォルトで、node.jsのファンクション（hello）が作成されます。

```cmd
.
|-- functions
|   `-- hello
|       `-- index.js
`-- project.json
```
# 4. ファンクションを実装する

## スクリプトの作成

```cmd
mkdir function/python_lambda
vi function/python_lambda/main.py
```

今回は以下のスクリプトを用意します。

```main.py
"""
Lambda example with external dependency
"""

import logging
import requests


logger = logging.getLogger()
logger.setLevel(logging.INFO)


def handle(event, context):
    """
    Lambda handler
    """
    logger.info("%s - %s", event, context)

    url = "https://api.ipify.org?format=json"

    raw = requests.get(url)
    logger.info("%s", raw)
    result = raw.json()

    logger.info("Lambda IP: %s", result['ip'])
```

## 注記の設定

ファンクションの注記を設定します。これは、AWS マネジメントコンソール上でLambdaファンクションの一覧に表示されます。下記の`function.json`を`python_lambda`フォルダの直下に置きます。

``` function.json
{
  "description": "Example with python"
}
```

## 依存ライブラリのダウンロード

以下のコマンドでスクリプトの中で利用するライブラリをプロジェクトの中にダウンロードします。

```cmd
pip install requests -t functions/python_lambda
```

依存ライブラリのダウンロード後のプロジェクトは、以下の様になります。

```cmd
.
|-- function.json
|-- main.py
|-- requests
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- adapters.py
|   |-- adapters.pyc
|   |-- api.py
|   |-- api.pyc
|   |-- auth.py
|   |-- auth.pyc
|   |-- cacert.pem
|   |-- certs.py
|   |-- certs.pyc
|   |-- compat.py
|   |-- compat.pyc
|   |-- cookies.py
|   |-- cookies.pyc
|   |-- exceptions.py
|   |-- exceptions.pyc
|   |-- hooks.py
|   |-- hooks.pyc
|   |-- models.py
|   |-- models.pyc
|   |-- packages
|   |   |-- __init__.py
|   |   |-- __init__.pyc
|   |   |-- chardet
|   |   |   |-- __init__.py
|   |   |   |-- __init__.pyc
|   |   |   |-- big5freq.py
|   |   |   |-- big5freq.pyc
|   |   |   |-- big5prober.py
|   |   |   |-- big5prober.pyc
|   |   |   |-- chardetect.py
|   |   |   |-- chardetect.pyc
|   |   |   |-- chardistribution.py
|   |   |   |-- chardistribution.pyc
|   |   |   |-- charsetgroupprober.py
|   |   |   |-- charsetgroupprober.pyc
|   |   |   |-- charsetprober.py
|   |   |   |-- charsetprober.pyc
|   |   |   |-- codingstatemachine.py
|   |   |   |-- codingstatemachine.pyc
|   |   |   |-- compat.py
|   |   |   |-- compat.pyc
|   |   |   |-- constants.py
|   |   |   |-- constants.pyc
|   |   |   |-- cp949prober.py
|   |   |   |-- cp949prober.pyc
|   |   |   |-- escprober.py
|   |   |   |-- escprober.pyc
|   |   |   |-- escsm.py
|   |   |   |-- escsm.pyc
|   |   |   |-- eucjpprober.py
|   |   |   |-- eucjpprober.pyc
|   |   |   |-- euckrfreq.py
|   |   |   |-- euckrfreq.pyc
|   |   |   |-- euckrprober.py
|   |   |   |-- euckrprober.pyc
|   |   |   |-- euctwfreq.py
|   |   |   |-- euctwfreq.pyc
|   |   |   |-- euctwprober.py
|   |   |   |-- euctwprober.pyc
|   |   |   |-- gb2312freq.py
|   |   |   |-- gb2312freq.pyc
|   |   |   |-- gb2312prober.py
|   |   |   |-- gb2312prober.pyc
|   |   |   |-- hebrewprober.py
|   |   |   |-- hebrewprober.pyc
|   |   |   |-- jisfreq.py
|   |   |   |-- jisfreq.pyc
|   |   |   |-- jpcntx.py
|   |   |   |-- jpcntx.pyc
|   |   |   |-- langbulgarianmodel.py
|   |   |   |-- langbulgarianmodel.pyc
|   |   |   |-- langcyrillicmodel.py
|   |   |   |-- langcyrillicmodel.pyc
|   |   |   |-- langgreekmodel.py
|   |   |   |-- langgreekmodel.pyc
|   |   |   |-- langhebrewmodel.py
|   |   |   |-- langhebrewmodel.pyc
|   |   |   |-- langhungarianmodel.py
|   |   |   |-- langhungarianmodel.pyc
|   |   |   |-- langthaimodel.py
|   |   |   |-- langthaimodel.pyc
|   |   |   |-- latin1prober.py
|   |   |   |-- latin1prober.pyc
|   |   |   |-- mbcharsetprober.py
|   |   |   |-- mbcharsetprober.pyc
|   |   |   |-- mbcsgroupprober.py
|   |   |   |-- mbcsgroupprober.pyc
|   |   |   |-- mbcssm.py
|   |   |   |-- mbcssm.pyc
|   |   |   |-- sbcharsetprober.py
|   |   |   |-- sbcharsetprober.pyc
|   |   |   |-- sbcsgroupprober.py
|   |   |   |-- sbcsgroupprober.pyc
|   |   |   |-- sjisprober.py
|   |   |   |-- sjisprober.pyc
|   |   |   |-- universaldetector.py
|   |   |   |-- universaldetector.pyc
|   |   |   |-- utf8prober.py
|   |   |   `-- utf8prober.pyc
|   |   `-- urllib3
|   |       |-- __init__.py
|   |       |-- __init__.pyc
|   |       |-- _collections.py
|   |       |-- _collections.pyc
|   |       |-- connection.py
|   |       |-- connection.pyc
|   |       |-- connectionpool.py
|   |       |-- connectionpool.pyc
|   |       |-- contrib
|   |       |   |-- __init__.py
|   |       |   |-- __init__.pyc
|   |       |   |-- appengine.py
|   |       |   |-- appengine.pyc
|   |       |   |-- ntlmpool.py
|   |       |   |-- ntlmpool.pyc
|   |       |   |-- pyopenssl.py
|   |       |   |-- pyopenssl.pyc
|   |       |   |-- socks.py
|   |       |   `-- socks.pyc
|   |       |-- exceptions.py
|   |       |-- exceptions.pyc
|   |       |-- fields.py
|   |       |-- fields.pyc
|   |       |-- filepost.py
|   |       |-- filepost.pyc
|   |       |-- packages
|   |       |   |-- __init__.py
|   |       |   |-- __init__.pyc
|   |       |   |-- ordered_dict.py
|   |       |   |-- ordered_dict.pyc
|   |       |   |-- six.py
|   |       |   |-- six.pyc
|   |       |   `-- ssl_match_hostname
|   |       |       |-- __init__.py
|   |       |       |-- __init__.pyc
|   |       |       |-- _implementation.py
|   |       |       `-- _implementation.pyc
|   |       |-- poolmanager.py
|   |       |-- poolmanager.pyc
|   |       |-- request.py
|   |       |-- request.pyc
|   |       |-- response.py
|   |       |-- response.pyc
|   |       `-- util
|   |           |-- __init__.py
|   |           |-- __init__.pyc
|   |           |-- connection.py
|   |           |-- connection.pyc
|   |           |-- request.py
|   |           |-- request.pyc
|   |           |-- response.py
|   |           |-- response.pyc
|   |           |-- retry.py
|   |           |-- retry.pyc
|   |           |-- ssl_.py
|   |           |-- ssl_.pyc
|   |           |-- timeout.py
|   |           |-- timeout.pyc
|   |           |-- url.py
|   |           `-- url.pyc
|   |-- sessions.py
|   |-- sessions.pyc
|   |-- status_codes.py
|   |-- status_codes.pyc
|   |-- structures.py
|   |-- structures.pyc
|   |-- utils.py
|   `-- utils.pyc
`-- requests-2.10.0.dist-info
    |-- DESCRIPTION.rst
    |-- INSTALLER
    |-- METADATA
    |-- RECORD
    |-- WHEEL
    |-- metadata.json
    `-- top_level.txt
```

# 5. Lambdaファンクションのデプロイする

Lambdaファンクションの準備ができたら、以下のコマンドでAWSへデプロイします。`--profile <name>`でプロフィルを切り替えることが可能です（本番/開発の切り替えなど）。

```cmd
apex deploy
   • creating function         function=python_lambda
   • created alias current     function=python_lambda version=1
   • function created          function=python_lambda name=apex-sample_python_lambda version=1
```

AWSにLambdaファンクションが作成されます。

![Lambda Management Console.png](https://qiita-image-store.s3.amazonaws.com/0/118448/b77becc4-82ce-f821-44d7-8eab6bffaf2b.png "Lambda Management Console.png")


# 6. Lambdaファンクションを実行してみる

デプロイした後、以下のコマンドでLambdaファンクションを実行することができます。

```cmd
apex invoke python_lambda
```

続いて以下のコマンドで実行結果を確認すると、動作したことが確認できます。LambdaのIPが取得できていることがわかると思います。

```cmd
apex logs python_lambda
/aws/lambda/apex-sample_python_lambda START RequestId: 2e61ed55-49d5-11e6-a09e-59cea9415cc8 Version: 1
/aws/lambda/apex-sample_python_lambda [INFO]	2016-07-14T15:11:05.301Z	2e61ed55-49d5-11e6-a09e-59cea9415cc8	{} - <__main__.LambdaContext object at 0x7f3ef89d7850>
/aws/lambda/apex-sample_python_lambda [INFO]	2016-07-14T15:11:05.336Z	2e61ed55-49d5-11e6-a09e-59cea9415cc8	Starting new HTTPS connection (1): api.ipify.org
/aws/lambda/apex-sample_python_lambda [INFO]	2016-07-14T15:11:06.119Z	2e61ed55-49d5-11e6-a09e-59cea9415cc8	<Response [200]>
/aws/lambda/apex-sample_python_lambda [INFO]	2016-07-14T15:11:06.119Z	2e61ed55-49d5-11e6-a09e-59cea9415cc8	Lambda IP: 52.196.95.168
/aws/lambda/apex-sample_python_lambda END RequestId: 2e61ed55-49d5-11e6-a09e-59cea9415cc8
/aws/lambda/apex-sample_python_lambda REPORT RequestId: 2e61ed55-49d5-11e6-a09e-59cea9415cc8	Duration: 819.04 ms	Billed Duration: 900 ms 	Memory Size: 128 MB	Max Memory Used: 39 MB
```

# 7. Lambdaファンクションのバージョン確認

尚、以下のコマンドを実行するとデプロイされたLambdaファンクションのバージョンが確認できます。

```cmd
apex list

  python_lambda
    description: Example with python
    runtime: python2.7
    memory: 128mb
    timeout: 5s
    role: arn:aws:iam::XXXXXXXXXXXX:role/apex-sample_lambda_function
    handler: main.handle
    current version: 1
    
```

# 所感

この様に、サクッとできました。Apexの開発者の言う通り、Lambdaのユーザビリティーの不満はだいぶ解決されるのではないでしょうか。

Apexにはその他にも便利な機能がたくさんあるので、Lambdaの開発には大変便利なものだと思います。

# 参考
http://dev.classmethod.jp/cloud/aws/how-to-manage-aws-lambda-functions-with-apex/
https://github.com/apex/apex