Serverless FrameworkでPython版Lambda関数(+ Lambda Layer)を作成

Python言語で書いたLambda関数を[Serverless Framework](https://serverless.com/)を用いて作成・デプロイした際の設定の記録です。

# 環境

- macOS Catalina
- serverless framework 1.65.0
- python 3.7.4
- node v12.15.0

# プロジェクトのセットアップ

事前にServerless Frameworkのアカウントを作成しておく必要があります。
https://serverless.com/

serverlessコマンドのインストール

```sh
$ npm install -g serverless
```

Serverlessプロジェクトの作成

```sh
$ serverless
```

プロジェクト名の入力やアカウントへのログイン等を行います。
開発言語の選択肢では「AWS Python」を選びましょう。

入力が完了すると新しくServelessプロジェクト用のディレクトリが作成されます。

# 初期デプロイ

作成されたディレクトリに移動します。
初期状態では`serverless.yml`と`handler.py`というファイルが作成されています。

それぞれ下記のように少し変更を加えます。

```yaml:serverless.yml
# serviceとappは各自の環境に合わせて設定してください。
service: python-test
app: python-test

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
```

```python:handler.py
def hello(event, context):
    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event
    }
```

`event`の中身をオブジェクトにそのまま格納しreturnするだけの単純な関数です。

下記コマンドでデプロイ（Lambda関数の作成）を行います

```sh
$ serverless deploy
```

AWSコンソールからLambdaの画面を見ると関数が新しく作成されていることが確認できると思います。

# 関数の動作確認

次のコマンドで作成した関数を実行することができます。

```sh
$ serverless invoke yarn serverless invoke --function hello
{
    "message": "Go Serverless v1.0! Your function executed successfully!",
    "event": {}
}
```

実行時にイベントを渡すこともできます。
まず下記の内容で`event.json`ファイルを作成します。

```json:event.json
{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}
```

 そして関数実行時にpオプションでイベントを記述したファイルを指定します。

```sh
$ serverless invoke --function hello -p event.json
{
    "message": "Go Serverless v1.0! Your function executed successfully!",
    "event": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    }
}
```

# Pipenvを用いて外部ライブラリを導入する

Pythonの外部ライブラリを使用する場合の設定です。
今回はパッケージマネージャとして[Pipenv](https://github.com/pypa/pipenv)を利用します。

## Pipenvのセットアップ

Pipenvをインストールしていない場合はpipを使ってインストールします。

```sh

$ pip3 install pipenv
```

プロジェクトにおけるPipenvの設定初期化を行います。
今回はPython3系を使うため以下のようにバージョンを指定します。

```sh
$ pipenv --python 3
```

コマンド実行後ディレクトリ内に`Pipfile`が作成されています。

```:Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]

[requires]
python_version = "3.7"
```

## Pythonの外部ライブラリをインストール

今回は日付処理を便利に行うことが可能となるpython-dateutilというライブラリを使いたいと思います。

- https://pypi.org/project/python-dateutil/

まずはPipenvでライブラリをインストールします。

```sh
$ pipenv install python-dateutil
```

次にpython-dateutilを使うようにプログラムを変更します。

```python:handler.py
import datetime
from dateutil.relativedelta import relativedelta

def hello(event, context):
    today = datetime.date.today()
    yesterday = today + relativedelta(days=-1)

    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event,
        "dates": {
            "today": f'{today: %Y-%m-%d}',
            "yesterday": f'{yesterday: %Y-%m-%d}'
        }
    }
```

標準ライブラリのdatetimeと併用して、プログラム実行時の日付と1日前の日付を取得し、returnするオブジェクト内に追加しています。

## serverless-python-requirementsの導入

PipenvでインストールしたライブラリをServerless Frameworkで扱うためには、serverless-python-requirementsというnpmパッケージを利用します。

- https://github.com/UnitedIncome/serverless-python-requirements

serverless-python-requirementsは元々は`requirements.txt`で管理するライブラリを扱えるようにするものですが、Pipenvにも対応しています。

まず、npmを使ってserverless-python-requirementをインストールします。

```sh
# npmプロジェクトの初期化を行っていない場合
$ npm init

# serverless-python-requirementsのインストール
$ npm install --save-dev serverless-python-requirements
```

次にserverless-python-requirementを利用するための設定を`serverless.yml`に記述します。
pluginとcustomの部分を追記します。

```yaml:serverless.yml
service: python-test
app: python-test

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true

functions:
  hello:
    handler: handler.hello
```

## デプロイ、動作確認

`serverless deploy`コマンドでデプロイします。
その後、更新された関数を実行してみましょう。

```sh
$ serverless invoke --function hello -p event.json
{
    "message": "Go Serverless v1.0! Your function executed successfully!",
    "event": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    },
    "dates": {
        "today": " 2020-03-07",
        "yesterday": " 2020-03-06"
    }
}
```

# Lambda Layerを利用する

[Lambda Layer](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html)を利用すると、Lambda関数のパッケージに利用ライブラリのファイルを含む必要がなくなり、パッケージ容量を少なくすることができます。

serverless-python-requirementを利用している場合、設定を少し加えるだけで依存ライブラリを簡単にLayer化することが可能です。
具体的には`serverless.yml`に次のように設定を追加します。

```diff:serverless.yml
custom:
  pythonRequirements:
    dockerizePip: true
+   layer: true
```

設定変更後、`serverless deploy`コマンドでデプロイします。

AWSコンソールのLambdaの画面から、新しくLayersが作成されていることを確認してみてください。

# まとめ

Serverless Frameworkを用いてPython言語でLambda関数を作成する方法を説明しました。
serverless-python-requirementを利用すれば、Pipenvとの統合やLambda Layerの作成など外部ライブラリ周りの導入を簡単に行うことができます。

# 参考資料

- [Serverless Frameworkの使い方まとめ](https://qiita.com/horike37/items/b295a91908fcfd4033a2)
- [Pipenvことはじめ](https://qiita.com/shinshin86/items/e11c1124e3e2e74556b8)
- [Python 日付、時刻の処理](https://qiita.com/xza/items/9618e25a8cb08c44cdb0)
- [Lambda Layerを使ってデプロイを高速化する](https://blog.ikedaosushi.com/entry/2019/04/07/012612)