Feedforce Developer Blog

フィードフォース開発者ブログ

Python でサーバーレスなら Chalice もいいぞ

今日はフェス!唐揚げにレモンはかける派で参戦します!

どーも、io チームでバックエンドやってます id:pokotyamu です!

今日は、FFLT で話した Chalice について紹介しようと思います!

Chalice とは?

github.com

Chalice とは AWS 製のサーバーレスフレームワークツールです。 似たような他のツールといえば、 Serverless Framework が一番有名かと思います。

自分は、先日行われた Serverless Conf Tokyo で初めて知りました! その時の感想やレポート記事はこちらから↓

特徴として、 API Gateway + AWS Lambda の構成かつ Python のみサポートというほんとに最小構成をターゲットにしています。

ただ、Chalice の中で、Dynamo DB や S3 を管理することは出来ません。 ここが Serverless Framework との大きな違いです。 GCP や Azure を対象とせずに、絞っているからこその特徴をこの記事では紹介していきます!

基本構成

新規プロジェクトの作成は以下のコマンドで行えます。

$ chalice new-project <project-name>

すると次のような構成でファイル群が作られます。

<project-name>
├── app.py
└── requirements.txt

0 directories, 2 files

あとは、AWS のクレデンシャルを環境変数にセットして、 $ chalice deploy とするだけです!

app.py の中で、関数を定義していきます。

from chalice import Chalice

app = Chalice(app_name='sample')


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

@app.route('/') こちらが、実際のエンドポイントを作成するためのデコレーターで、http メソッドの定義もこちらで行うことが出来ます。

URL パラメーターを使いたい時は、API Gateway で作るときと同じように、{} で囲むことで作ることができ、そのデコレートされている関数の引数とすれば、そのまま使うことが出来ます。

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

ここがすごいぞ Chalice

エラーレスポンスのコード化

Lambda でエラーになった際に、 400 とか 403 などのエラーを最終的なレスポンスとして返したくなることはよくあると思います。

そんな時、

  • API Gateway の統合レスポンスでエラーメッセージのパターンを定義してステータスコードを割り当てる
  • Lambda コードで、ステータスやヘッダーを意識してコードを書いた上で、 Lambda Proxy で割り当てる

といった方法が考えられると思います。

ただ、1つ目の方法の場合、エラーメッセージが変わった時に、ビジネスロジックとパターンマッチのコードどちらも修正が必要になります。

また、2つ目の方法は、コードを書く中で API Gateway のことを意識して、エラーの組み立てを行わなければなりません。

Chalice の場合は、デフォルトで以下のエラークラスが用意されています。

* BadRequestError - return a status code of 400
* UnauthorizedError - return a status code of 401
* ForbiddenError - return a status code of 403
* NotFoundError - return a status code of 404
* ConflictError - return a status code of 409
* TooManyRequestsError - return a status code of 429
* ChaliceViewError - return a status code of 500

これらのエラークラスを raise させるだけで、それぞれに対応したステータスコードでエラーレスポンスを行うことができるのです。

from chalice import BadRequestError

CITIES_TO_STATE = {
    'seattle': 'WA',
    'portland': 'OR',
}

@app.route('/cities/{city}')
def state_of_city(city):
    try:
        return {'state': CITIES_TO_STATE[city]}
    except KeyError:
        raise BadRequestError("Unknown city '%s', valid choices are: %s" % (
            city, ', '.join(CITIES_TO_STATE.keys())))
$ curl {end-point}/cities/seattle
{"state": "WA"}
$ curl {end-point}/cities/fukuoka
{"Code": "BadRequestError", "Message": "BadRequestError: Unknown city 'fukuoka', valid choices are: seattle, portland"}

これによって、エラーメッセージの変更でパターンマッチを変えて、再度デプロイし直して検証してみたいなことをする必要もありません!また、 Lambda Proxy のお約束事も特に気にする必要もありません!

素敵な未来だった👏👏👏

IAM policy 管理からの開放

AWS を使う上でわりと頭を悩ませるのが、 IAM ロールにどこまで権限を与えるか?という点です。 Admin をアタッチは基本的にアンチパターンなので、必要な分だけいい感じに Action に追加してほしい。

そんな時に Chalice では、 boto3 で使っているメソッドから自動的に必要な IAM を提示して更新してくれるという機能を持っています。

import json
import boto3
from botocore.exceptions import ClientError

from chalice import NotFoundError


S3 = boto3.client('s3', region_name='us-west-2')
BUCKET = 'your-bucket-name'


@app.route('/objects/{key}', methods=['GET', 'PUT'])
def s3objects(key):
    request = app.current_request
    if request.method == 'PUT':
        S3.put_object(Bucket=BUCKET, Key=key,
                      Body=json.dumps(request.json_body))
    elif request.method == 'GET':
        try:
            response = S3.get_object(Bucket=BUCKET, Key=key)
            return json.loads(response['Body'].read())
        except ClientError as e:
            raise NotFoundError(key)

この例では、 S3 バケットに対して、 PUT でリクエストのボディーに入っている json を書き出し。 GET で指定したファイル名のデータを取得する関数を定義しています。

実際にデプロイしてみましょう。

$ chalice deploy
Creating deployment package.

The following actions will be added to the execution policy:

s3:GetObject
s3:PutObject

Would you like to continue?  [Y/n]: y
Updating IAM policy for role: sample-dev
Updating lambda function: sample-dev
API Gateway rest API already found: xxxxxxxx
Deploying to API Gateway stage: api
https://{endpoint}-api.ap-northeast-1.amazonaws.com/api/

一度デプロイ時に S3 の GetObject と PutObject を追加してもいいか聞かれます。 ここで yes とすると自動的に IAM policy を更新してくれるんです!

これで悩ましい IAM policy から開放されますね!!!!

その他機能

Chalice には、他にもローカルにサーバを立てる $ chalice local コマンド。

vendor/ に 3rd Party の Library を入れておくことで、deploy 時によしなに含めてくれる機能。(参考: chalice/packaging.rst at master · aws/chalice)

Python と API Gateway + Lambda に特化してるからこそ、デフォルトでできる機能がたくさん入っています!!

まとめ

今回 Chalice について紹介させていただきました。 ぜひ、 Python + API Gateway + Lambda でサーバーレスを試してみたいなと思われている方はお試しください!

ちなみに、僕はこの内容をスライドだけでは伝わらないと思いまして、デモすることにしました。

しかし、デモの練習用に使ってたファイルを本番でいじってしまい、全然変更が反映されない。。。なぜ。。。みたいなテンパリから、時間切れで爆死しました。 みなさんも、 LT でデモする時は、心のゆとり。たくさんの練習。無駄なアプリを起動しない。この3つを気をつけると成功すると思いますよ!

みなさんの温かい感想とともに失礼します|彡サッ

f:id:pokotyamu:20171110223727j:plain