投稿日:
2019年1月1日
最終更新日:
【サーバーレスアークテクチャ開発】ローカル環境でAWSのLambdaスクリプトをPythonで実装する際に「python-lambda-local」で動作確認する際のインストール方法とやり方【Bot開発にオススメ】
YouTubeも見てね♪
Anker PowerCor
旅行には必須の大容量モバイルバッテリー!
【最新機種】GoPro hero11 Black
最新機種でVlogの思い出を撮影しよう!
[ノースフェイス] THE NORTH FACE メンズ アウター マウンテンライトジャケット
防水暴風で耐久性抜群なので旅行で大活躍です!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
レッドブル エナジードリンク 250ml×24本
翼を授けよう!
ドラゴンクエスト メタリックモンスターズギャラリー メタルキング
みんな大好き経験値の塊をデスクに常備しておこう!
MOFT X 【新型 ミニマム版】 iPhone対応 スマホスタンド
Amazon一番人気のスマホスタンド!カード類も収納出来てかさ張らないのでオススメです!
ローカルでのLambdaスクリプト開発の課題
サーバーレスアーキテクチャの代名詞
皆さんはAWSのLambdaを使っているでしょうか?
Lambdaはサーバーレスアーキテクチャを実現する上で、欠かせない存在となっており、サーバーレスの流行りのおかげでとてもメジャーなAWSサービスの一つとなっています。
AWS Lambda を使用することで、サーバーのプロビジョニングや管理をすることなく、コードを実行できます。課金は実際に使用したコンピューティング時間に対してのみ発生し、コードが実行されていないときには料金も発生しません。
Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。コードさえアップロードすれば、高可用性を実現しながらコードを実行およびスケーリングするために必要なことは、すべて Lambda により行われます。コードは、他の AWS のサービスから自動的にトリガーされるよう設定することも、ウェブやモバイルアプリケーションから直接呼び出すよう設定することもできます。
AWS Lambda (サーバーレスでコードを実行・自動管理) | AWS
また、Lambdaは言語を問わずJava、Node.js、Pythonなど様々な言語を用いて実装出来るのも魅力の一つです。
最近はPythonでLambdaを実装するのが多いですね。
ローカル開発がちょっと大変
この便利なLambdaですが、ローカルで動作確認をしたい場合にちょっと厄介です。
外部のライブラリを使用している場合はZipに固める必要があったり、関数のトリガーもLambdaに依存しているため、ローカルで同じように動かすにはローカル様にちょっとソースを変える必要があったりと差分が発生してしまいます。
python-lambda-localが便利過ぎる
そんな中、PythonでLambdaをローカル環境で実装・動作確認をする際にとてもpython-lambda-local と言う便利なツールを見つけました。
こちらを使えば、ローカルでのLambdaスクリプトを簡単に実装・動作確認することが出来ます。
また、インストールもpipコマンドでワンタッチで出来たのでインストールから使い方までご紹介致します。
手順
pipでpython-lambda-localをインストール
今回はpipコマンドでpython-lambda-localをインストールします。pip install python-lambda-local
を実行すると以下のようにインストールが完了すると思います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
Collecting python-lambda-local
Downloading https://files.pythonhosted.org/packages/d1/87/f54ade7dea162f501451340ec6ce1f906d7ef5ed09f310c6a9dd468caaac/python-lambda-local-0.1.7.tar.gz
Collecting boto3 (from python-lambda-local)
Downloading https://files.pythonhosted.org/packages/0b/3b/925c7aded1c54c12d5be964ab543420b01d95759c5756a574588d49444ad/boto3-1.9.71-py2.py3-none-any.whl (128kB)
100% |████████████████████████████████| 133kB 2.9MB/s
Collecting botocore<1.13.0,>=1.12.71 (from boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/50/c0/cd4f8bec8a10876f0ce34f0cf264fda04e09df41d1a473a43f890c71fffa/botocore-1.12.71-py2.py3-none-any.whl (5.2MB)
100% |████████████████████████████████| 5.2MB 1.8MB/s
Collecting jmespath<1.0.0,>=0.7.1 (from boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/b7/31/05c8d001f7f87f0f07289a5fc0fc3832e9a57f2dbd4d3b0fee70e0d51365/jmespath-0.9.3-py2.py3-none-any.whl
Collecting s3transfer<0.2.0,>=0.1.10 (from boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/d7/14/2a0004d487464d120c9fb85313a75cd3d71a7506955be458eebfe19a6b1d/s3transfer-0.1.13-py2.py3-none-any.whl (59kB)
100% |████████████████████████████████| 61kB 532kB/s
Collecting docutils>=0.10 (from botocore<1.13.0,>=1.12.71->boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/50/09/c53398e0005b11f7ffb27b7aa720c617aba53be4fb4f4f3f06b9b5c60f28/docutils-0.14-py2-none-any.whl (543kB)
100% |████████████████████████████████| 552kB 2.0MB/s
Collecting python-dateutil<3.0.0,>=2.1; python_version >= "2.7" (from botocore<1.13.0,>=1.12.71->boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/74/68/d87d9b36af36f44254a8d512cbfc48369103a3b9e474be9bdfe536abfc45/python_dateutil-2.7.5-py2.py3-none-any.whl (225kB)
100% |████████████████████████████████| 235kB 2.2MB/s
Requirement already satisfied: urllib3<1.25,>=1.20; python_version == "2.7" in /usr/local/lib/python2.7/site-packages (from botocore<1.13.0,>=1.12.71->boto3->python-lambda-local) (1.23)
Collecting futures<4.0.0,>=2.2.0; python_version == "2.6" or python_version == "2.7" (from s3transfer<0.2.0,>=0.1.10->boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/2d/99/b2c4e9d5a30f6471e410a146232b4118e697fa3ffc06d6a65efde84debd0/futures-3.2.0-py2-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1; python_version >= "2.7"->botocore<1.13.0,>=1.12.71->boto3->python-lambda-local)
Downloading https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Building wheels for collected packages: python-lambda-local
Running setup.py bdist_wheel for python-lambda-local ... done
Stored in directory: /Users/blogenist/Library/Caches/pip/wheels/12/e2/66/adee4094d943f6647227ccc5feeca73ddafe31aac3351b6c84
Successfully built python-lambda-local
Installing collected packages: jmespath, docutils, six, python-dateutil, botocore, futures, s3transfer, boto3, python-lambda-local
Successfully installed boto3-1.9.71 botocore-1.12.71 docutils-0.14 futures-3.2.0 jmespath-0.9.3 python-dateutil-2.7.5 python-lambda-local-0.1.7 s3transfer-0.1.13 six-1.12.0
|
確認
では、python-lambda-local --version
コマンドで動作確認をしてみましょう。
以下のようにバージョンが表示されればインストール成功です。
1 |
python-lambda-local 0.1.7
|
外部依存無しの場合
では、実際にスクリプトを書いてみましょう。
まずはお決まりのHello World!!!を出力してみます。
こちらはライブラリなどには何も依存していないスクリプトとなります。
1
2
3
4
|
print('Start Function.')
def lambda_handler(event, context):
print("Hello World!!")
|
次に、event.jsonを用意します。
こちらは実行する際のリクエスト情報を定義します。
今回は特にリクエスト内容は不要ですが、必須で指定する必要があるようなので以下のような空のJsonのファイルを用意しておきます
1 |
{}
|
ValueError: No JSON object could be decoded
ファイルは用意したが、中身が空の場合は以下のようなエラーが出てしまいます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Process Process-1:
Traceback (most recent call last):
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/process.py", line 267, in _bootstrap
self.run()
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python2.7/site-packages/lambda_local/main.py", line 49, in run
e = event.read_event(args.event)
File "/usr/local/lib/python2.7/site-packages/lambda_local/event.py", line 11, in read_event
data = json.load(event)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 291, in load
**kw)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 339, in loads
return _default_decoder.decode(s)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 364, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 382, in raw_decode
raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded
|
LambdaはJson形式で待ち構えているので、ファイルが空の場合にJsonオブジェクトをデコード出来ずにエラーになってしまうので気をつけましょう。
確認
では、実行してみましょう。
実行コマンドはpython-lambda-local --function {関数名} --timeout {タイムアウト秒} {スクリプトファイル名} {イベントファイル名}
のように組み立てます。
今回だと以下のようになります。
1 |
python-lambda-local --function lambda_handler --timeout 5 lambda_function.py event.json
|
実行すると以下のように出力されます。
1
2
3
4
5
6
7
8
|
Start Function.
[root - INFO - 2018-12-27 14:21:04,297] Event: {}
[root - INFO - 2018-12-27 14:21:04,298] START RequestId: c4dc58b6-29e9-47df-b800-9f51ea96abbe
Hello World!!
[root - INFO - 2018-12-27 14:21:04,299] END RequestId: c4dc58b6-29e9-47df-b800-9f51ea96abbe
[root - INFO - 2018-12-27 14:21:04,299] RESULT:
None
[root - INFO - 2018-12-27 14:21:04,299] REPORT RequestId: c4dc58b6-29e9-47df-b800-9f51ea96abbe Duration: 0.36 ms
|
正常にHello World!!!
が出力されましたね♪
リクエスト依存の場合
では、POSTで送られてくるデータを操作するケースを想定してみます。
データはevent.jsonに定義する事でスクリプト内で取得出来るようになります。
event.jsonを以下のように変えてみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
users:[
{
"name": "Taro",
"age": 22
},{
"name": "Jiro",
"age": 20
},{
"name": "Hanako",
"age": 98
}
]
}
|
そして、lambda_function.pyは以下のように修正します。
1
2
3
4
5
6
7
|
print('Start Function.')
def lambda_handler(event, context):
for user in event['users']:
print(user['name'])
print(user['age'])
print("================")
|
確認
では、先ほどと同じコマンドで実行してみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Start Function.
[root - INFO - 2018-12-27 14:37:48,434] Event: {u'users': [{u'age': 22, u'name': u'Taro'}, {u'age': 20, u'name': u'Jiro'}, {u'age': 98, u'name': u'Hanako'}]}
[root - INFO - 2018-12-27 14:37:48,435] START RequestId: c401f427-415b-4d1a-be8a-c65a2c1d87cd
Taro
22
================
Jiro
20
================
Hanako
98
================
[root - INFO - 2018-12-27 14:37:48,435] END RequestId: c401f427-415b-4d1a-be8a-c65a2c1d87cd
[root - INFO - 2018-12-27 14:37:48,435] RESULT:
None
[root - INFO - 2018-12-27 14:37:48,436] REPORT RequestId: c401f427-415b-4d1a-be8a-c65a2c1d87cd Duration: 0.29 ms
|
正常にJsonの情報を読み取れていますね♪
実際にPOSTでリクエストを投げられる場合も、Json形式で来るはずなのでローカルで正常に動いていれば、実際のAWSのLambda上でもJson構造が正しければ問題無く動くはずです。
外部ライブラリに依存する場合
次に外部ライブラリに依存するケースを試してみましょう。
Lambdaでは依存するライブラリはZip内に含めないといけない、と言う制約があります。
ローカルで動作確認するだけなら特に意識せずに通常通りに記述すれば良いです。
※実際にAWSのLambdaに載せる際はライブラリ毎Zipに含める必要があります
今回はrequestsモジュールを利用して、POSTされたurlにアクセスして、HttpStatusを出力する、と言うシンプルなスクリプトを書いてみましょう。
requestsをまだインストールしていない場合はpip install requests
コマンドでインストールをしてください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Downloading https://files.pythonhosted.org/packages/7d/e3/20f3d364d6c8e5d2353c72a67778eb189176f08e873c9900e10c0287b84b/requests-2.21.0-py2.py3-none-any.whl (57kB)
100% |████████████████████████████████| 61kB 435kB/s
Requirement already satisfied: urllib3<1.25,>=1.21.1 in /usr/local/lib/python2.7/site-packages (from requests) (1.23)
Collecting chardet<3.1.0,>=3.0.2 (from requests)
Downloading https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl (133kB)
100% |████████████████████████████████| 143kB 1.3MB/s
Collecting idna<2.9,>=2.5 (from requests)
Downloading https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl (58kB)
100% |████████████████████████████████| 61kB 992kB/s
Collecting certifi>=2017.4.17 (from requests)
Downloading https://files.pythonhosted.org/packages/9f/e0/accfc1b56b57e9750eba272e24c4dddeac86852c2bebd1236674d7887e8a/certifi-2018.11.29-py2.py3-none-any.whl (154kB)
100% |████████████████████████████████| 163kB 896kB/s
Installing collected packages: chardet, idna, certifi, requests
Successfully installed certifi-2018.11.29 chardet-3.0.4 idna-2.8 requests-2.21.0
|
lambda_function.pyの中身は以下のように修正します。
1
2
3
4
5
6
7
|
import requests
print('Start Function.')
def lambda_handler(event, context):
response = requests.get(event['url'])
return 'Status : %d' % response.status_code
|
確認
では、こちらも実行してみてください。
1
2
3
4
5
6
7
|
Start Function.
[root - INFO - 2018-12-27 17:54:34,702] Event: {u'url': u'https://www.google.co.jp/'}
[root - INFO - 2018-12-27 17:54:34,704] START RequestId: 60cb4394-12f3-463f-beba-d4f623dcb49b
[root - INFO - 2018-12-27 17:54:35,527] END RequestId: 60cb4394-12f3-463f-beba-d4f623dcb49b
[root - INFO - 2018-12-27 17:54:35,528] RESULT:
Status : 200
[root - INFO - 2018-12-27 17:54:35,528] REPORT RequestId: 60cb4394-12f3-463f-beba-d4f623dcb49b Duration: 820.28 ms
|
こちらも正常に処理が実行されましたね♪
AWSサービスのトリガーに依存する場合
では、最後により業務に近づいたケースを試してみましょう。
今回はAWSのS3バケットにファイルがPUTされたタイミングで動くスクリプトを書いてみます。
まずは、event.jsonにPUTされた際に送られてくるデータを定義します。
公式 に記載されていたサンプルイベントデータをベースに{バケット名}と{キー名}だけを修正します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
{
"Records":[
{
"eventVersion":"2.0",
"eventSource":"aws:s3",
"awsRegion":"us-west-2",
"eventTime":"1970-01-01T00:00:00.000Z",
"eventName":"ObjectCreated:Put",
"userIdentity":{
"principalId":"AIDAJDPLRKLG7UEXAMPLE"
},
"requestParameters":{
"sourceIPAddress":"127.0.0.1"
},
"responseElements":{
"x-amz-request-id":"C3D13FE58DE4C810",
"x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
},
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"testConfigRule",
"bucket":{
"name":"{バケット名}",
"ownerIdentity":{
"principalId":"A3NL1KOZZKExample"
},
"arn":"arn:aws:s3:::{バケット名}"
},
"object":{
"key":"{キー名}",
"size":1024,
"eTag":"d41d8cd98f00b204e9800998ecf8427e",
"versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko"
}
}
}
]
}
|
boto3モジュールをインストールしていない方はpip install boto3
でインストールしてください。
lambda_function.pyは以下のように修正します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
from boto3.session import Session
import urllib
print('Start Function.')
session = Session(profile_name='{profile名}')
s3 = session.client('s3')
def lambda_handler(event, context):
# Get Bucket Name
bucket = event['Records'][0]['s3']['bucket']['name']
# Get Key Name
key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key'].encode('utf8'))
try:
# Get Object
response = s3.get_object(Bucket=bucket, Key=key)
# Print Object ContentType
print("CONTENT TYPE: " + response['ContentType'])
return response['ContentType']
except Exception as e:
print(e)
raise e
|
このファイルの中でS3にアクセスする際にAWS CLIが利用されます。
session = Session(profile_name='{profile名}’)としています。
確認
では、実際に実行してみましょう。
1
2
3
4
5
6
7
8
9
|
Start Function.
[botocore.credentials - INFO - 2018-12-27 18:43:56,374] Found credentials in shared credentials file: ~/.aws/credentials
[root - INFO - 2018-12-27 18:43:56,545] Event: {u'Records': [{u'eventVersion': u'2.0', u'eventTime': u'1970-01-01T00:00:00.000Z', u'requestParameters': {u'sourceIPAddress': u'127.0.0.1'}, u's3': {u'configurationId': u'testConfigRule', u'object': {u'versionId': u'096fKKXTRTtl3on89fVO.nfljtsv6qko', u'eTag': u'd41d8cd98f00b204e9800998ecf8427e', u'key': u'{キー名}', u'size': 1024}, u'bucket': {u'arn': u'arn:aws:s3:::{バケット名}', u'name': u'{バケット名}', u'ownerIdentity': {u'principalId': u'A3NL1KOZZKExample'}}, u's3SchemaVersion': u'1.0'}, u'responseElements': {u'x-amz-id-2': u'FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD', u'x-amz-request-id': u'C3D13FE58DE4C810'}, u'awsRegion': u'us-west-2', u'eventName': u'ObjectCreated:Put', u'userIdentity': {u'principalId': u'AIDAJDPLRKLG7UEXAMPLE'}, u'eventSource': u'aws:s3'}]}
[root - INFO - 2018-12-27 18:43:56,545] START RequestId: b56de8ee-067d-471d-87a0-01e671f895cf
CONTENT TYPE: image/jpeg
[root - INFO - 2018-12-27 18:43:59,139] END RequestId: b56de8ee-067d-471d-87a0-01e671f895cf
[root - INFO - 2018-12-27 18:43:59,139] RESULT:
image/jpeg
[root - INFO - 2018-12-27 18:43:59,140] REPORT RequestId: b56de8ee-067d-471d-87a0-01e671f895cf Duration: 2593.92 ms
|
正常に存在するファイルのContentTypeが取得出来ましたね。
AWS上のLambdaに乗せればリアルなPUTイベントを取得出来るので様々な操作が可能になります♪
終わりに
今まではローカルで動作確認する際と実際にLambdaに上げる際にソースの修正やZip化をしたりと、差分が出てしまっていましたが、このツールを利用することでソースを変えること無くよりリアルな動作確認が出来るようになりました。
Lambdaを使っている方はとても便利なので是非使ってみてください♪