Serverless FrameworkでLambdaに入門する

今まではGCPを使っていたのですが、最近は会社でAWSを使い始めました
今回はLambdaを使う必要があったので入門しました
Lambdaで関数を作成する際にはzipをアップロードしたりS3からアップロードしたりブラウザで書いたりする方法があるようですが

  • ローカルでもある程度開発したものをLambdaにデプロイしたい
  • デプロイもコマンド一発で行いたい

これらを満たすものとして今回はServerless Frameworkを使用してみました

serverless.com

導入自体は npm install -g serverless で完了するので楽ちんですね

今回はPython3を使って構築することにしたので下記のようにPython3でのテンプレートを指定して新規プロジェクトを作成します

slsはserverlessのalias
$ sls create -t aws-python3 -p s3_objects

ファイルとしてはhandler.pyserverless.ymlの2つが生成されてYAMLファイルはLambdaでの設定などを記載して
Pythonのファイルには実際の関数の中身を書いていきます

例えば今回は定期的にS3の指定されたパス配下のファイル一覧をSlackに通知するとします

まずSlackへの通知は下記のパッケージを使用します

github.com

ただLambdaには上記のパッケージは最初は使えないので自分で追加してあげる必要があります
Serverless Frameworkにはプラグインを追加することが出来るのですが、今回はPythonのパッケージリストを記載したファイルを用意しておくとデプロイ時のパッケージに含んでくれる下記のプラグインを使用しました

github.com

プラグインのインストール自体はnpm install --save serverless-python-requirementsで完了してserverless.ymlに下記のように追加します

plugins:
  - serverless-python-requirements

後はserverless.yml, handler.pyと同じ階層にrequirements.txtというファイルを追加して下記のように定義してあげます
※boto3はここで定義しなくてもLambda上では使えるみたいですが今回はローカルでもpip install -r requirements.txtでいれたかったので定義しています

boto3==1.4.4
slackweb==1.0.5

serverless.ymlには下記のように設定しました
今回は最低限動くものを定義しています
公式にサンプルが用意されているので参考になりました

service: hoge-service
frameworkVersion: ">=1.14.0 <2.0.0"

provider:
  name: aws
  runtime: python3.6
  stage: prd
  region: ap-northeast-1
  role: arn:aws:iam::xxxxxxxxx:role/ServerlessRole

plugins:
  - serverless-python-requirements

functions:
  hello:
    handler: handler.hello
    events:
      - schedule: cron(30 9 * * ? *) # 毎日9:30に実行される
    environment:
      BUCKET_NAME: 'hoge-bucket'
      S3_KEY_PREFIX: 'hello/world'
      WEBHOOK_URL: 'https://hooks.slack.com/services/xxxxxxxx/yyyyyyyyy'

handler.pyは下記のようにします

import json
import boto3
import slackweb
from os import getenv

def hello(event, context):
    slack = slackweb.Slack(url=getenv("WEBHOOK_URL"))
    slack.notify(text='START!!')

    objects = _get_all_objects()
    all_keys = [row['Key'] for row in objects]

    slack.notify(text="ファイル一覧です\n%s" % "\n".join(all_keys))

    slack.notify(text='END!!')
     
    return all_keys

def _get_all_objects(start_after='', objects=[]):
    client = boto3.Session().client('s3')
    response = client.list_objects_v2(
        Bucket=getenv('BUCKET_NAME'),
        Prefix=getenv('S3_KEY_PREFIX'),
        StartAfter=start_after
    )
    if 'Contents' in response:
        objects.extend(response['Contents'])
        if 'IsTruncated' in response:
            _get_all_objects(start_after=objects[-1]['Key'], objects=objects)

    return objects

S3からオブジェクトする際のlist_objects_v2では一度に最大で1000件までしか取れないので再帰的にメソッドを呼び出して指定されたディレクトリ配下のすべてのオブジェクトを取れるようにしています

ローカルで確認する際には

$ sls invoke local -f hello

を使用してデプロイしてそのLambda関数を呼び出すのは

# デプロイ
$ sls deploy -v 
# 作成されたLambda関数をcallする
$ sls invoke -f hello

これにより毎日13時にhoge-bucketBucketのhello/world配下のファイル一覧が通知されるようになりました

Lambda便利ですねー