AWS Lambda x Pythonでのローカル開発環境の再現について

AWS Lambdaを使ってなにかを作るとき(Python)についての考察です。

## ■ Overview

- AWS LambdaとPythonを使って開発するときにローカル環境をどう作っていくかについて。
- AWS Lambdaをローカルで再現する手段


## ■ 問題点
- AWS LambdaとPythonで開発する際に、モジュールなどのインポートする際に依存関係があるとLambdaのGUI上ではそのまま実行できないため、一度他の場所で依存関係をインストールしてzip化してアップロードするケースが多い。
- <img width="373" alt="スクリーンショット 2018-10-30 17.18.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/122901/17ab55cc-0618-e902-7a86-d1a82eae55b2.png">

ただ、その都度変更があるためにzip化してアップロードするのは大変。すぐに反映を確認できないと開発効率がさがってしまう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122901/e7d4645b-7110-afab-770b-8b0124df0cde.png)


## ■ 解決策
主に2つあります。

### ① EC2 or AmazonLinux for Dockerを使い開発する
Lambdaは、AmazonLinuxで動いているためローカルPCでローカル開発環境を準備して開発し、それをzip化アップロードしても失敗するケースがあります。
なので、他に環境を用意する必要があります。方法は2つあり
  - EC2インスタンスを立ててその中で開発する。
  - AmazonLinuxの公式Dockerイメージの中で開発を行う。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/122901/88485af3-17a2-07c4-5189-7a72d5c19316.png)

前者のEC2を立てるのは、あまりスマートではなくコストもかかるのでおすすめできません。
AmazonLinuxの公式Dockerについては、その中で開発を行え常にクリーンな環境を保てます。

```bash
$ docker pull amazonlinux
```
https://hub.docker.com/_/amazonlinux/


### ② docker-lambdaを利用する
個人的には、こちらが一番良いかと思います。<br>
https://github.com/lambci/docker-lambda

docker-lambdaを文字通り、AWS Lambdaの環境を再現したDockerイメージです。
これを使い、Dockerfileをビルドしてアップロードすればかなりよい環境ができあがります。AmazonLinuxのDockerイメージでもよいのですが、こちらのdocker-lambdaのほうがはじめからLambdaを再現できているので余計なことをせずに簡単です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122901/b09cdcc9-6bd8-9861-b811-2019ed468fd3.png)


## ■ docker-lambdaを触ってみる
簡単ではありますが、サンプルをbuildするまで行ってみます。

まずは、DockerfileをビルドするのではなくCLIからイメージをrunして動作確認してみます。

[公式のlambci/docker-lambdaリポジトリ](https://github.com/lambci/docker-lambda#example)を参考にしていきます。


####lambda_function.pyを用意する。
AWS Lambdaではデフォールトでlambda_function.pyのlambda_handlerを呼び出すようになっているので今回も従っていきます・

```python:lambda_function.py
import json

def lambda_handler(event, context):
    return event
```

####docker-lambdaを実行していきます。

```bash
$ docker run -v "$PWD":/var/task lambci/lambda:python3.6 lambda_function.lambda_handler '{"Hello":"World"}'
Unable to find image 'lambci/lambda:python3.6' locally
python3.6: Pulling from lambci/lambda
5be106c3813f: Pull complete 
e240967675e1: Pull complete 
4ec29390cd9b: Pull complete 
fc6ba7ddc407: Pull complete 
492c7fb9ab78: Pull complete 
Digest: sha256:6a5cddcf16942a7a3ddc209f140a8d4105a87ce95cd374ffc6d953dd0b593d01
Status: Downloaded newer image for lambci/lambda:python3.6
START RequestId: 3f39da27-a241-4fd2-ad77-e92ec63ffada Version: $LATEST
END RequestId: 3f39da27-a241-4fd2-ad77-e92ec63ffada
REPORT RequestId: 3f39da27-a241-4fd2-ad77-e92ec63ffada Duration: 12 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 19 MB

{"Hello": "World"}
```
単純にJSONも文字列を渡して出力しているだけですが、無事に『{"Hello": "World"}』がでました。



####ビルドイメージをDockerfileに書いていく。
では、ここでビルド用のDockerfileを書いていきます。
内容としては、上記のlambda_function.pyと依存関係をzip化し、デプロイパッケージを作成していきます。
[デプロイパッケージの作成](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html)

ビルド準備として、`requirements.txt`というテキストを用意しそこに依存するインポート群を書いていきます。

```bash:requirements.txt
requests
```

再度、requestsモジュールを使った`lambda_function.py`を用意します。DMM.comにアクセスしてstatus_code : 200を返すスクリプトです。

```python:lambda_function.py
import requests

def lambda_handler(event, context):
    res = requests.get('https://www.dmm.com/')
    return res.status_code 
```

では、ビルドしていきます。

```bash:Dockerfile
FROM lambci/lambda:build-python3.6
ENV AWS_DEFAULT_REGION ap-northeast-1

ADD . .

CMD pip3 install -r requirements.txt -t /var/task && \
  zip -9 deploy_package.zip lambda_function.py && \
  zip -r9 deploy_package.zip *
```

まずは、イメージを作成します。

```bash
$ docker build -t mylambda .
```

```bash:イメージ確認
$ docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mylambda            latest              14a0a97cd00a        5 minutes ago       1.8GB
lambci/lambda       build-python3.6     0f6150a9635c        7 days ago          1.8GB
```

ではZipに固めていきます。

```bash:build
$ docker run -v "$PWD":/var/task mylambda
Collecting requests (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/f1/ca/10332a30cb25b627192b4ea272c351bce3ca1091e541245cccbace6051d8/requests-2.20.0-py2.py3-none-any.whl (60kB)

~中略~
```

結果として、以下のようなディレクト構造ができます。`deploy_package.zip`がアップロード対象のフォルダーです。

```bash
ls -a
.				certifi				deploy_package.zip		requests			urllib3-1.24.dist-info
..				certifi-2018.10.15.dist-info	idna				requests-2.20.0.dist-info
Dockerfile			chardet				idna-2.7.dist-info		requirements.txt
bin				chardet-3.0.4.dist-info		lambda_function.py		urllib3
```

**結果**
Lambda上で、`deploy_package.zip`をzipアップロードすると結果として200が返ってきており、正しく動作していることがわかります。
![スクリーンショット 2018-10-30 18.25.13.png](https://qiita-image-store.s3.amazonaws.com/0/122901/1fda1ce1-fc98-26d9-3f88-4fd886b47861.png)

基本的にアップロードをローカルで検証できたらで良くて、普段はコンテナ内や引数でpythonを実行して動作確認するのがよさそうです。

以上です。