AWS LambdaでPythonをSchedule実行させる

## はじめに

2015/10のAmazon re:Invent 2015で、Lambdaに関して[幾つかの機能追加](https://aws.amazon.com/jp/blogs/aws/aws-lambda-update-python-vpc-increased-function-duration-scheduling-and-more/)が発表されました。
個人的に大きいのは以下の3つです。

* Lambda functionをPythonで記述できる
* Lambda functionをSchedule実行させる事ができる
* Lambda functionの最大実行時間が5分に拡張された (従来は1分)

[AWS LambdaをData Pipelineを使ってSchedule Drivenで起動させる](http://qiita.com/taka4sato/items/e98690742d0092dd8779)記事を以前書いたのですが、Schedule実行の機能はLambdaの標準機能としてサポートされました。この記事ではLambda functionをPythonを使って記述し、Schedule実行させる手順をチラ裏しておきます。

## 制限事項
今回発表/追加されたLambda + Python + Scheduleの実行環境ですが、気を付けて置くべき制限があります。大きいのは以下の項目です。

* Lambda functionの処理は5分以内に終了させないとErrorになる(なので時間がかかる処理には使えない)
* Schedule実行の最短周期は5分 (なので1分に1回Lambda functionを実行する、とかはできない)
* PythonはVer 2.7のみ対応 (3.xは現時点で対応されていない)
* PythonでPython2.7の標準library以外をimportして使用するには、library毎zip圧縮してUploadする必要がある(手順は後述) 

## Case1：5分毎にPythonのPrintで出力をしてみる
一番最初のCase studyとして、単にPrintするだけのPython Scriptを、5分毎のSchedule実行させる、という一番ベタなケースです。Schedule実行の結果が正しく出力されているかLogを見てみます。

AWS ConsoleのLambdaから、`Create a Lambda function`を選択します。

### Step1 雛形(blueprint=青写真)の選択
Lambda functionの雛形が沢山ありますが`lambda-canary`を検索して選びます。Lambda function上で、Python scriptをSchedule実行させる雛形です。

![select_blueprint.png](https://qiita-image-store.s3.amazonaws.com/0/67592/6d786648-60c9-91d5-a33e-7e53a01edd66.png)

### Step2 Event sourceの設定
今回はSchedule実行なので、Event sourceには`Scheduled Event`を選択します(defaultで選ばれていると思います)。
`Name`と`Description`は適当な説明文を入れればOKです。
`Schedule expression`はdefaultで`rate(5 minutes)`になっているはずなのでそのままで良いです。

![Configure_event_sources.png](https://qiita-image-store.s3.amazonaws.com/0/67592/f4462a1d-6455-66ac-cde5-71d6ae92c1f7.png)

### Step3 Python scriptの設定
Python scriptと、IAM roleの設定を行います。

Pythonのcodeは以下を使います。ひたすらprintするだけのCodeです。

```py
import json, datetime, commands

def lambda_handler(event, context):
    print commands.getoutput('cat /proc/cpuinfo | grep -e "processor" -e "model name"')
    print commands.getoutput('cat /proc/meminfo | grep MemTotal')
    print commands.getoutput('cat /proc/meminfo | grep MemFree')
    print datetime.datetime.now().strftime('%Y/%m/%d %H:%M')  
    print '-------------------------------'
    
    print event
    print event['account']
    print context.__dict__
    print context.memory_limit_in_mb
```

Python script以外は、以下の設定が必要です。他はDefaultのママで良いです。

* `Name`はLambda functionの名前です。(他のLambda functionと被らなければ)適当な文字列で良いです。
* `Role`はLambda functionに付与するIAM roleです。今回は他のAWS Resourceにアクセスしないので、`lambda_basic_execution`を選択すれば良いです(`lambda_basic_execution`と言うIAM roleが無い場合には、この名前のroleを作成する事になります)。

![Configure_function.png](https://qiita-image-store.s3.amazonaws.com/0/67592/33ff1d5d-dbf2-21d2-339c-8cedb6f422a4.png)

### Step4 設定確認

最終確認です。`Enable now`を選択すると、この設定でSchedule実行がEnableされます。`Enable now`を選択し`Create function`を押せば完成です。

![Review.png](https://qiita-image-store.s3.amazonaws.com/0/67592/43664ad1-bf95-b3df-6679-037623a12211.png)


### Step5 Print出力された結果を確認

Lambda functionの出力結果は、CloudWatch logsに保存されます。
AWS ConsoleのCloudWatchのLogsから、`Lambda_Test`を選択します。すると、5分毎に実行Logが追加され、printした内容がLogに残っていると思います。

![CloudWatchLogs.png](https://qiita-image-store.s3.amazonaws.com/0/67592/91f7055e-4991-6133-a1b9-d6bbb8a9b707.png)


### Python scriptの補足
* Python2.7に標準で入って居るLibraryは、`import json, datetime, commands`の様に書けばOKです
* `main`というmethodは無く、`lambda_handler`というMethodがEntry pointとして(`event`, `context`という引数で)呼ばれます。
  Entry pointの関数は`Configuration`の`Handler`で指定を変える事が可能です
![event_hander.png](https://qiita-image-store.s3.amazonaws.com/0/67592/f8c257a3-3577-383f-79b0-88c15d48cba2.png)
* Entry pointの引数である`event`, `context`の中身は、何をEvent sourceとするかで違ってきています。printで`event`, `context`の中身を吐き出しているので、チェックしてみてください。


## Case2：Schedule実行の周期を5分毎から10分毎に変えてみる

### Step1 古いSchedule実行設定の削除
AWS ConsoleのLambdaから、先ほど作った`Lambda_Test`のfunctionの`Event sources`のタブに行きます。
`rate(5 minutes)`で設定したSchedule実行を`x`で一旦削除し、`Add event source`を選択します
![EditEventSource.png](https://qiita-image-store.s3.amazonaws.com/0/67592/8211620c-ee0a-34fb-1067-5f6714b442d3.png)

### Step2 10分毎のSchedule実行の設定
`Add event source`のDialogで、`Event sources type`に`Scheduled Event`を選択し、以下の様にDialogを埋めます。

* `Name`と`Description`は適当な説明文を入れる
* `schedule expression`で`cron`を選択し、`cron(0/10 * * * ? *)`と書く


ここで注意なのは、Lambdaのcronの文法が、いわゆるLinux一般のcronの文法と微妙に違う点です。
[この記事を参考に](http://qiita.com/pyr_revs/items/abd29d9472f4cba9032c)`cron(0/10 * * * ? *)`と書いてください。

![EditEventSource2.png](https://qiita-image-store.s3.amazonaws.com/0/67592/7e680742-1294-6198-d2ed-e44b3bd16bfb.png)

`Enable now`を選択すると、この設定でSchedule実行がされます。10分毎に実行LogがCloudWatch logsにあるか確認して見て下さい。


## Case3：標準ではないLibraryを入れてみる
`numpy`や`pandas`や`requests`と言った、Python2.7標準では無いlibraryをLambda + Pythonで使いたい場合は、Script fileそのものと合わせてLibraryも一緒にzipしてuploadして上げる必要があります。詳細は、AWS公式サイトの[Creating a Deployment Package (Python)](http://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html)を参照して下さい。

### Step1 Python scriptをfileとして保存

以下のPython scriptをLocalに保存します (ここでは`LambdaTest.py`と言うfile名で保存する想定で進めます)。
Python Scriptの中身は、`requests`libraryを使って`google.co.jp`をHTTP GETして、Status CodeとResponse BodyをPrintするものです。

```py:LambdaTest.py
import requests, datetime

def lambda_handler(event, context):
  target_URL = 'https://www.google.co.jp'

  r = requests.get(target_URL)
  print datetime.datetime.now().strftime('%Y/%m/%d %H:%M') 
  print '-------------------------------'
  print r.status_code
  print '-------------------------------'
  print r.text
```

`requests`と言う、Python2.7標準に無いlibraryが必要なので、このCodeをこのままLambdaに登録するとErrorになります。そのため、`requests`のlibrary fileを同じフォルダに置き、zip圧縮して、zipをLambdaに登録します。


### Step2 Libraryを含んだzip fileを作成

`pip install`を`-t /PathTo/LambdaTest.py` optionで実行すると、指定したフォルダにlibrary fileを配置してくれます。例えば、LambdaTest.pyが`/home/hoge`に有るとしたら、`pip install -t /home/hoge`と実行すればOKです。

以下みたいなファイル構成になるはずです。これら(フォルダが2個、ファイルが1個)をzip圧縮して、適当な名前を付けます(`LambdaLibraryZip.zip`としたとします)

![folder.png](https://qiita-image-store.s3.amazonaws.com/0/67592/adb7a3e7-8321-6774-4719-d2fb014f9b3b.png)

### Step3 Entry Pointの関数名を変更

Lambda functionのCodeをzipでUploadする場合、Entry Pointの関数名(Pythonのmain関数)が`*.pyのファイル名` + `.` + `Scriptの中のEntry Pointの関数`となります。今回の例で言うと、`LambdaTest.lambda_handler`と設定する必要があります。`Configuration`タブの`Handler`を`LambdaTest.lambda_handler`と設定します

![HanderName.png](https://qiita-image-store.s3.amazonaws.com/0/67592/e11ce591-92ee-37e9-bf5a-24c81b30aae8.png)

### Step4 zip fileをUploadして実行

`Code`タブから、`Upload a .ZIP file`で、先ほど作成した`LambdaLibraryZip.zip`をUploadして、`Save and test`をクリックして、Python Scriptが正しく実行されているか確認してみてください。

![uploadzip.png](https://qiita-image-store.s3.amazonaws.com/0/67592/d4195f23-27c3-db6c-097a-bff3a1478297.png)


## 最後に
LambdaのPython対応、Schedule実行対応は、Python Loveな私的にはとても良いNewsです。Amazonの掲げる[2 tier Architecture](http://www.slideshare.net/AmazonWebServicesJapan/aws-lambda-2tier)、もしくは[サーバレスアーキテクチャ](http://qiita.com/zerobase/items/3bc0d15980b472af841d)への大きな布石になると思います(そしてAmazonにどんどんLockinされていく...)。

ただ、まだLambda + Python + Schedule実行でも置き換えられない領域が幾つかあると思います。

### 短い周期での実行が必要なケース

Lambdaの最短実行周期は5分です。これで多くのニーズはカバーされると思いますが、『1分毎にStatus Checkや死活監視したい』などの要件もやはりあります。Server-lessで短い周期のSchedule実行を行うAWS serviceを自分は知らないです(知っていたら教えてください)。

Amazonが出してきた提案は、[EC2 t2.nano Instance](http://aws.typepad.com/aws_japan/2015/10/ec2-instance-update-x1-sap-hana-t2-nano-websites.html)なのでは、と思っています。現行のt2.microよりも更に安いInstanceを用意してヤッから、これ使ってチラシの裏にでも書いてな、って事です。

### 処理に時間かかかるケース

Lambdaの最大処理時間は今回5分に拡張されました(従来は1分でした)。1分だった時は、`S3のLogをLocalに持って来て、中身をParseして、Errorが見つかったらAlarmを発動しつつ結果をRDSに書き込んでおく`と言った処理をLambda functionでする時に、Log sizeが数100MBを超えるとTimeoutするようなケースがありましたが、今回の5分への拡張で多くのニーズはカバーされると思います。が、『大量のDataのDailyのETL Batch処理』などの処理の時間が必要な要件もやはりあります。

自分の知る限り、この手の演算やData転送で処理に時間が掛かる要件には、

* 演算能力が必要無い場合は、[Data Pipeline + Pythonを使う](http://qiita.com/taka4sato/items/6ef180508c6b46e44fbf)
* 演算能力が必要な場合は、[Lambda Schedule実行で必要なSpecを持つEC2 (Spot) Instanceを立てる](http://qiita.com/pyr_revs/items/bf483b96cca6b01a9110)

が良いんじゃないかなぁ、と思っています。