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
@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
@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
@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
@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
@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
@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
@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 オーソライザ
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 便利!
また何か新しいものを触ったら記事にしたい。