開拓馬の厩

いろいろやる

Python & Serverless FrameworkでLambdaファンクションをサクッと作ってデプロイする

Serverless FrameworkAmazon AWSの開発&管理を補助するフレームワークです。 最近アルバイト先でserverlessを用いたアプリケーションに関わり、その際に使いかたを軽く勉強したので、ブログに書き残しておきます。

環境

  • Node.js 10.6.0
  • Python 2.7.15
  • serverless 1.29.2

Installation

ServerlessはNode.jsのアプリケーションとして配布されており、npmコマンドからインストールできます。

npm install -g serverless

# 確認
serverless --help
sls --help # slsという短縮コマンドが自動でエイリアスされるらしい

サービスを作ってデプロイ

インストールが完了したので、簡易的なLambdaのファンクションを作成してAWSにデプロイするところまでやってみましょう。 今回は毎朝8時にLINE Notificationを送ってくれる目覚ましファンクションを作ってみます。

サービスの新規作成

sls createコマンドでサービスを新規作成できます。 このとき-tオプションでアプリのテンプレートを、-pオプションでサービスを保存するパスを指定できます。 テンプレートはaws-python*1、保存先は適当な作業ディレクトリ上のlambdaMezamashiというディレクトリにします。

cd /path/to/working/dir #適当な作業ディレクトリに移動
sls create -t aws-python -p lambdaMezamashi
cd lambdaMezamashi
ls
# => handler.py   serverless.yml

コマンドを打つと、lambdaMezamashiというディレクトリが作成され、handler.pyserverless.ymlという2つのファイルが自動生成されます。 handler.pyはLambdaのファンクションを書くファイルで、serverless.ymlは設定ファイルになります。

とりあえず動かす

アプリが完成したのでとりあえず動かしてみます。 テンプレートにはhelloという関数名で簡単なメッセージを返す関数が定義されています。 sls invoke local -f {関数名}で指定した関数名の関数をローカル環境で実行できます。

sls invoke local -f hello
# => {
# =>     "body": "{\"input\": {}, \"message\": \"Go Serverless v1.0! Your function executed successfully!\"}", "statusCode": 200
# => }

LINE Notificationを送る関数を記述

LINE Notificationの使いかたはこちらの記事でも解説しています。

まず、LINE Notifyのページからアクセストークンを取得し、環境変数LINE_NOTIFY_TOKENに書き入れます。

export LINE_NOTIFY_TOKEN=XXXXXXXXX # bash zsh等の場合
set -x LINE_NOTIFY_TOKEN XXXXXXXXX # fishの場合

次に以下のようにhandler.pyを書き換え、LINE NotifyのAPIを叩くコードを追加します。

# -*- coding: utf-8 -*-

import os
import json
import requests

def mezamashi(event, context):
    output_url = "https://notify-api.line.me/api/notify"
    token = os.getenv("LINE_NOTIFY_TOKEN", "")

    headers = {"Authorization" : "Bearer "+ token}
    payload = {"message" :  "こんにちは"}

    response = requests.post(output_url ,headers = headers ,params=payload)

    return response.json()

def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

次にpipコマンドでrequestsをインストールします。

pip install requests

次にserverless.ymlを書き換え、mezamashi関数に関数名を割り当てます。

service: lambdaMezamashi

provider:
  name: aws
  runtime: python2.7

  environment:
    # LINE Notifyのアクセストークンを記入
    # invoke localで実行する場合はこれを記述しなくても動作するが、デプロイ時に必要となる
    LINE_NOTIFY_TOKEN: XXXXXXXXXXXXXXXX

functions:
  hello:
    handler: handler.hello
  mezamashi:
    handler: handler.mezamashi

そうしたら、serverless invoke localを用いて動作確認を行います。

sls invoke local -f mezamashi
# => {
# =>     "status": 200,
# =>     "message": "ok"
# => }
# (LINEに通知が来る)

スケジュール実行の設定

Lambdaファンクションの実装が完成したので、これをスケジュール実行するように設定を書きます。 serverless.ymlfunctionsの部分にイベント設定を追記するだけです。

functions:

    #<中略>

mezamashi:
    handler: handler.mezamashi
    events: # イベントの設定を記述
      - schedule: cron(0 23 * * ? *) # cron式で日本時間の朝8時を指定

ついでにAPI Gatewayも使う

LambdaはAPI Gatewayと組み合わせることでwebアプリのバックエンドとして使用するケースもよくあります。 少し脱線しますが、ymlを用いてLambdaファンクションをAPI Gatewayと結びつける方法も説明しておきます。

eventsの項目に- http: {メソッド} {APIのURI}の形式で記述します。 こうすることでデプロイした際に自動でAPI Gatewayが作成されます。

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

オフラインでテストする

ファンクションが書けたので早速デプロイと行きたいところですが、その前にローカルホストでLambdaファンクションをホストして、APIのテストを行う方法を解説します。 これは、業務でデバッグAPIのテストコードを走らせる場合にも必要なことなので覚えておいて損はありません。

まず、必要なプラグインをインストールします。インストールはsls plugin install -n {プラグイン名}で行います。どうやらこのコマンドはnpm i --save-devをラップしてnode_moduleをインストールしつつ、serverless.ymlプラグイン情報を追記してくれるものらしいです。

sls plugin install -n serverless-offline
npm install serverless-offline-python --save-dev # slsコマンドからインストールできないためnpmを直接使う

次にserverless.ymlの末尾に以下のように書き込んでserverlessとプラグインを紐付けます*2

plugins:
  - serverless-offline
  - serverless-offline-python

serverless-offlineserverless-offline-pythonをインストールしたことで、serverless offlineというコマンドが使えるようになります。これによりローカル環境でcurlコマンド等を用いてAPIデバッグが可能になります。

sls offline
#=> Serverless: Starting Offline: dev/ap-northeast-1.
#=>  (中略)
#=> Serverless: Offline listening on http://localhost:3000

# =====他のterminal窓から以下を実行=====

curl localhost:3000/hello
#=> helloファンクションが動く

ちなみにserverless-offline-schedulerというオフライン環境でスケジュール実行のデバッグができるプラグインがありますが、Node.jsで書いたものしか動かないみたいです*3

デプロイする

最後にデプロイの方法を説明します*4

AWSアカウントの設定

最初にAWSアカウントの設定をします。

AWSのマネジメントコンソールにログインし、IAMから新規ユーザーを追加します。 f:id:pf_siedler:20180822104021p:plain f:id:pf_siedler:20180822104717p:plain

ユーザー名を適当に設定し、「プログラムによるアクセス」にチェックを入れます。 f:id:pf_siedler:20180822104040p:plain

「既存のポリシーを直接アタッチ」から「AdministratorAccess」を選択します*5 *6f:id:pf_siedler:20180822104044p:plain

ユーザーの作成が完了したら、「アクセスキーID」と「シークレットアクセスキー」を控えておきます。シークレットアクセスキーはこのときにしか確認できないため、忘れた場合には再度アカウントを作成する必要があります。 f:id:pf_siedler:20180822104034p:plain

これを、sls config credentialsコマンドを用いて設定します。XXXXXの箇所は適宜アクセスキー、アクセスキーシークレットに書き換えてください。

sls config credentials --provider aws --key XXXXX --secret XXXXX

デプロイ、実行、削除

アカウントの設定が済んだら遂にデプロイです。

Serverless Frameworkはsls deployコマンドを実行するだけで簡単にデプロイできるようになっています。 しかし、今回のようにPythonで実装している場合は、自分で書いたコードだけでなくサードパーティ製のライブラリも一緒にデプロイする必要があります。

serverless-python-requirementsというプラグインをインストールするとデプロイ時に自動でこの作業をやってくれるため、今回はこのプラグインを利用することにします。

sls plugin install -n serverless-python-requirements # プラグインのインストール
pip freeze > requirements.txt # 依存関係をrequirements.txtというファイルに書き出す

上記のコマンドを実行したら、デプロイします。

sls deploy
#=> Serverless: Installing requirements of requirements.txt in .serverless...
#=> Serverless: Packaging service...
#=> (中略)
#=> service: lambdaMezamashi
#=> stage: dev
#=> region: ap-northeast-1
#=> stack: lambdaMezamashi-dev
#=> api keys:
#=>   None
#=> endpoints:
#=>   GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
#=> functions:
#=>   hello: lambdaMezamashi-dev-hello
#=>   mezamashi: lambdaMezamashi-dev-mezamashi

これで完成です。毎朝8時にLINEに通知が来るようになりますし、https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/helloURIcurlコマンドやAjax通信でアクセスするとhelloファンクションをWeb APIとして叩くことができます。

デプロイしたアプリの削除も簡単に行うことができ、sls removeコマンドを実行するだけです。

sls remove
#=> Serverless: Getting all objects in S3 bucket...
#=> Serverless: Removing objects in S3 bucket...
#=> Serverless: Removing Stack...
#=> Serverless: Checking Stack removal progress...
#=> .............................
#=> Serverless: Stack removal finished...

さいごに

今回はServerless Frameworkの使い方を軽く説明しました。 Serverless Frameworkは他にもDynamoDBやS3といったサービスを操作することができ、その名の通りサーバレスなwebアプリケーションを構築することができます。

S3でwebページをホストし、Lambda & API Gatewayでバックエンドの処理を行い、DynamoDBやCognitoでユーザ情報を管理するという構築にすればどんなWebアプリでも作れそうですね。

参考資料

*1:was-python3だとローカルで動かす際にpyenv等を用いてver 3.6のPython環境を用意する必要があります。今回は面倒なので2.x系で我慢

*2:# sls plugin ~~ コマンドを走らせた際に自動で追記してくれるため、serverless-offline-python以外は不要かも

*3:実際にソースコードを確認したところ、Lambdaファンクションが記述されたjsファイルをrequireで読み込んで実行するという実装がされているようだった https://github.com/ajmath/serverless-offline-scheduler

*4:これ以降の部分で解説するコマンドを実行するとLambdaファンクションがAPI Gatewayを通して全世界から実行可能な状態になります。Lambdaファンクションに脆弱性が存在した場合には思わぬ被害を被る可能性があることを心に止めておいてください。

*5:公式ドキュメントでもAdmin権限を設定する(https://serverless.com/framework/docs/providers/aws/guide/credentials/)という解説されています。

*6:Serverless Frameworkは開発中でやれることがどんどん増えています。そのためAdmin権限でないと機能に制限がかかり、開発が面倒になる可能性があります。Amdin権限で開発しつつ、製品を公開する際に権限を必要最低限に絞るような運用が望ましいと思われます。