Edited at

Lambda(Python) や API Gateway の管理を Chalice でやってみた

Chalice を使う機会があったので、

せっかくなので備忘録も兼ねてまとめました。

ちなみに Python 歴は1ヶ月未満です。


Chalice とは?

https://github.com/aws/chalice

Amazon 産の AWS 向け Python フレームワークです。

AWS Lambda を使用する際に簡単に管理が出来るようになります。

また API Gateway, S3, SNS, SQS, CloudWatch Event などといった連携サービスもそのまま一緒に管理できます。

Chalice では、

Python の Chalice モジュールを利用しつつ、

コマンドラインツールで chalice コマンドを叩いてデプロイ等をする形になります。

また chalice delete コマンドを用いて、簡単に削除もできます。


開発環境


前提


  • AWS Credentials が設定されていること

  • Python 3.6 系が使えること


開発環境のバージョン確認

$ python --version

Python 3.6.6

$ pip --version
pip 10.0.1

$ chalice --version
chalice 1.6.0


インストール方法

$ pip install chalice


Hello World!

https://github.com/aws/chalice#quickstart

後述する @app.route を使って、

API Gateway + Lambda のデプロイが実施されます。

$ chalice new-project chibi-chalice

$ tree -a chibi-chalice/
chibi-chalice/
├── .chalice
│   └── config.json
├── .gitignore
├── app.py
└── requirements.txt

$ cd chibi-chalice

$ chalice deploy
Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev
Creating Rest API
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev
- Rest API URL: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/

# このように `@app.route` を実装して chalice deploy を叩くと
# IAM Role が自動的に作成され、
# Lambda Function が自動的に作成され、
# API Gateway が自動的に作成されます。


動作確認

# もう API が叩ける状態に!

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/
{"hello": "world"}


Local でも実行できる!

$ chalice local

Serving on 127.0.0.1:8000

$ curl localhost:8000

{"hello": "world"}


各種使用方法

Hello World を試したところで、

自動的に API Gateway + AWS Lambda が簡単にデプロイできることがわかりました。

Chalice は Decorator を使って各種サービス向けの実装ができるため、

掘り下げて色々デプロイして遊んでみます。


@app.route

https://github.com/aws/chalice/blob/master/docs/source/topics/routing.rst

@app.route は、

Hello World でも使用した「API Gateway + AWS Lambda」の定義になります。


デコレーション定義

def route(self, path, **kwargs):


サンプル

from chalice import Chalice

app = Chalice(app_name='chibi-chalice')

@app.route('/')
def index():
return {'hello': 'world'}

このような形で実装してデプロイを行えば、

Hello World のように GET API が叩けるわけです。


POST や PUT を実装する場合

@app.route('/', methods=['POST'])

def index():
request = app.current_request
return {
'method': request.method,
'json-body': request.json_body
}


動作確認

$ curl -X POST -H "Content-Type:application/json" -d '{"Hello":"World"}' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/

{"method": "POST", "json-body": {"Hello": "World"}}


URL にパラメータを使う場合

@app.route('/users/{user}')

def index(user):
return {'user': user}


動作確認

$ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/users/chibi

{"user": "chibi"}


URL クエリを使う場合

@app.route('/users/{user}')

def index(user):
request = app.current_request
return {
'user': user,
'query': request.query_params
}


動作確認

$ curl https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/users/chibi?hoge=foo

{"user": "chibi", "query": {"hoge": "foo"}}


レスポンスをカスタマイズする

from chalice import Chalice, Response

app = Chalice(app_name='chibi-chalice')

@app.route('/')
def index():
return Response(body='Hello World!',
status_code=200,
headers={'Content-Type': 'text/plain', 'hoge': 'foo'})


動作確認

$ curl -v https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/

< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 12
< hoge: foo
Hello World!


CORS サポート

# このように書けば良いらしい

@app.route('/', methods=['PUT'], cors=True)


@app.lambda_function

https://github.com/aws/chalice/blob/master/docs/source/topics/purelambda.rst

@app.lambda_function は、

純粋な「AWS Lambda」の定義になります。


デコレーション定義

def lambda_function(self, name=None):


サンプル

from chalice import Chalice

app = Chalice(app_name='chibi-chalice')

@app.lambda_function()
def handler(event, context):
return {'hello': 'lambda'}


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev-handler
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxx:function:chibi-chalice-dev-handler

# このように `@app.lambda_function` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function が自動的に作成されます。
# ※ API-Gateway は作成されません。



動作確認

$ aws lambda invoke --function-name chibi-chalice-dev-handler outputfile.txt

{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}

$ cat outputfile.txt
{"hello": "lambda"}



Lambda 関数名を任意に指定する場合

@app.lambda_function(name='MyFunction')

def handler(event, context):
return {'hello': 'MyFunction'}


動作確認

$ aws lambda invoke --function-name chibi-chalice-dev-MyFunction outputfile.txt

{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}

$ cat outputfile.txt
{"hello": "MyFunction"}



@app.schedule

https://github.com/aws/chalice/blob/master/docs/source/topics/events.rst#scheduled-events

@app.schedule は、

「CloudWatch Event + AWS Lambda」の定義になります。


デコレーション定義

def schedule(self, expression, name=None):


サンプル

from chalice import Chalice, Rate

app = Chalice(app_name="chibi-chalice")

@app.schedule(Rate(1, unit=Rate.MINUTES))
def handle_schedule(event):
print(event)
return {"hello": "world"}


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev-handle_schedule
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_schedule
# このように `@app.schedule` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function が自動的に作成されます。
# ※スケジュールイベントはきっと設定されているはず。。。


動作確認

$ aws events list-rules --name-prefix chibi-chalice-dev-handle_schedule

{
"Rules": [
{
"Name": "chibi-chalice-dev-handle_schedule-event",
"Arn": "arn:aws:events:ap-northeast-1:xxxxxxxxxx:rule/chibi-chalice-dev-handle_schedule-event",
"State": "ENABLED",
"ScheduleExpression": "rate(1 minute)"
}
]
}


別の記述方法

@app.schedule('rate(1 minutes)')

def handle_schedule():
print(event)
return {'hello': 'schedule'}


@app.on_s3_event

https://github.com/aws/chalice/blob/master/docs/source/topics/events.rst#s3-events

@app.on_s3_event は、

「S3 + AWS Lambda」の定義になります。


デコレーション定義

def on_s3_event(self, bucket, events=None, prefix=None, suffix=None, name=None):


サンプル

from chalice import Chalice

app = Chalice(app_name='chibi-chalice')

@app.on_s3_event(bucket='chibi-existing-bucket')
def handle_s3_event(event):
print(f'Object uploaded for bucket: {event.bucket}, key: {event.key}')

※ Bucket は存在している必要があります。


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev-handle_s3_event
Configuring S3 events in bucket chibi-existing-bucket to function chibi-chalice-dev-handle_s3_event
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_s3_event
# このように `@app.on_s3_event` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function が自動的に作成され、
# S3 events を設定してくれます。


動作確認

$ aws s3api get-bucket-notification-configuration --bucket chibi-existing-bucket

{
"LambdaFunctionConfigurations": [
{
"Id": "xxxxxxxxxx",
"LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_s3_event",
"Events": [
"s3:ObjectCreated:*"
]
}
]
}



@app.on_sqs_message

https://github.com/aws/chalice/blob/master/docs/source/topics/events.rst#sqs-events

@app.on_sqs_message は、

「SQS + AWS Lambda」の定義になります。


デコレーション定義

def on_sqs_message(self, queue, batch_size=1, name=None):


サンプル

from chalice import Chalice

app = Chalice(app_name='chibi-chalice')

@app.on_sqs_message(queue='chibi-existing-queue')
def handle_sqs_message(event):
for record in event:
print(f'Message body: {record.body}')

※Queue は存在している必要があります。

※Queue の「デフォルトの可視性タイムアウト」は Lambda のタイムアウト以上に設定する必要があります。


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev-handle_sqs_message
Subscribing chibi-chalice-dev-handle_sqs_message to SQS queue chibi-existing-queue
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_sqs_message
# このように `@app.on_sqs_message` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function が自動的に作成され、
# SQS の Lambda トリガーを設定してくれます。


動作確認

$ aws lambda list-event-source-mappings --function-name chibi-chalice-dev-handle_sqs_message

{
"EventSourceMappings": [
{
"UUID": "xxxxxxxxxx",
"BatchSize": 1,
"EventSourceArn": "arn:aws:sqs:ap-northeast-1:xxxxxxxxxx:chibi-existing-queue",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_sqs_message",
"LastModified": 1537413283.145,
"State": "Enabled",
"StateTransitionReason": "USER_INITIATED"
}
]
}


@app.on_sns_message

https://github.com/aws/chalice/blob/master/docs/source/topics/events.rst#sns-events

@app.on_sns_message は、

「SNS + AWS Lambda」の定義になります。


デコレーション定義

def on_sns_message(self, topic, name=None):


サンプル

from chalice import Chalice

app = Chalice(app_name='chibi-chalice')

@app.on_sns_message(topic='chibi-existing-topic')
def handle_sns_message(event):
print(f'Received message with subject: {event.subject}, message: {event.message}')

※Topic は存在している必要があります。


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-chalice-dev
Creating lambda function: chibi-chalice-dev-handle_sns_message
Subscribing chibi-chalice-dev-handle_sns_message to SNS topic chibi-existing-topic
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_sns_message
# このように `@app.on_sns_message` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function が自動的に作成され、
# SNS のサブスクリプションに Lambda を設定してくれます。


動作確認

$ aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxx:chibi-existing-topic

{
"Subscriptions": [
{
"SubscriptionArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxx:chibi-existing-topic:xxxxxxxxxx",
"Owner": "xxxxxxxxxx",
"Protocol": "lambda",
"Endpoint": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-chalice-dev-handle_sns_message",
"TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxx:chibi-existing-topic"
}
]
}


@app.authorizer

https://github.com/aws/chalice/blob/master/docs/source/topics/authorizers.rst#built-in-authorizers

@app.authorizer は、

「API Gateway の Lambda オーソライザー + Lambda」の定義になります。

Lambda のデプロイとオーソライザーの設定が一括でできます。


デコレーション定義

def authorizer(self, name=None, **kwargs):


サンプル

from chalice import Chalice, AuthResponse

app = Chalice(app_name='chibi-auth')

@app.authorizer()
def demo_auth(auth_request):
token = auth_request.token
# This is just for demo purposes as shown in the API Gateway docs.
# Normally you'd call an oauth provider, validate the
# jwt token, etc.
# In this exampe, the token is treated as the status for demo
# purposes.
if token == 'allow':
return AuthResponse(routes=['/', '/one', '/two'], principal_id='user')
else:
# By specifying an empty list of routes,
# we're saying this user is not authorized
# for any URLs, which will result in an
# Unauthorized response.
return AuthResponse(routes=[], principal_id='user')

@app.route('/', authorizer=demo_auth)
def index():
return { 'message': 'root' }

@app.route('/one', authorizer=demo_auth)
def one():
return { 'message': 'one' }

@app.route('/two', authorizer=demo_auth)
def two():
return { 'message': 'two' }

※サンプル通りのコードなのだが何故か動かないので /one , /two も追加

※ルート( / )は上手く動かせないのか。。。?

※また、コメントに記載されている通り本来であれば jwt token 等を validate します。


デプロイ

$ chalice deploy

Creating deployment package.
Creating IAM role: chibi-auth-dev
Creating lambda function: chibi-auth-dev
Creating lambda function: chibi-auth-dev-demo_auth
Creating Rest API
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-auth-dev
- Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:chibi-auth-dev-demo_auth
- Rest API URL: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/
# このように `@app.authorizer` を実装して chalice deploy を叩くと
# IAM Role、Lambda Function, API Gateway が自動的に作成され、
# API Gateway に Lambda Authorizer が設定されます。


動作確認

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api

{"message":"Unauthorized"}
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api -H "Authorization: allow"
{"Message":"User is not authorized to access this resource"}

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/one
{"message":"Unauthorized"}
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/one -H "Authorization: allow"
{"message": "one"}

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/two
{"message":"Unauthorized"}
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/two -H "Authorization: allow"
{"message": "two"}



@app.route (オーソライザ)

@app.authorizer を使わなくても簡単に設定が可能です。


IAM オーソライザ

https://github.com/aws/chalice/blob/master/docs/source/topics/authorizers.rst#aws-iam-authorizer

from chalice import Chalice, IAMAuthorizer

app = Chalice(app_name='chibi-auth')

authorizer = IAMAuthorizer()

@app.route('/iam-auth', methods=['GET'], authorizer=authorizer)
def authenticated():
return {"success": True}


Cognito User Pool オーソライザ

from chalice import Chalice, CognitoUserPoolAuthorizer

app = Chalice(app_name='chibi-auth')

authorizer = CognitoUserPoolAuthorizer(
'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name'])

@app.route('/user-pools', methods=['GET'], authorizer=authorizer)
def authenticated():
return {"success": True}


Lambda オーソライザ

from chalice import Chalice, CustomAuthorizer

app = Chalice(app_name='chibi-auth')

authorizer = CustomAuthorizer(
'MyCustomAuth', header='Authorization',
authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-01'
'/functions/arn:aws:lambda:region:account-id:'
'function:FunctionName/invocations'))

@app.route('/custom-auth', methods=['GET'], authorizer=authorizer)
def authenticated():
return {"success": True}


まとめ

初めてレベルで Python を触ったけど Chalice 便利!

また何か新しいものを触ったら記事にしたい。