Web系零細企業での出来事

組み込み屋からWeb系エンジニアへ異世界転生を果たした筆者がWeb系技術の話題を中心に書き連ねるブログ

サーバーレスでEvernoteに登録したブックマークをランダムにSlackへ通知するアプリを作る②

 前回はEvernoteAPIを利用して、Bookmarkの一覧を取得するところまでを解説しました。今回は、AWS SAMを使ってサーバーレスなインフラを構築していきます。

参考

www.ketancho.net

AWS CLIの準備

まずはこれがないと始まりません。pipでインストールしましょう。

pip install awscli

AWS CLIの初期設定を行います。 ※予めAWSコンソールからsam-sampleというユーザーを作成し、アクセスキーを取得しておきます。

$ aws configure --profile sam-sample
AWS Access Key ID [None]: A****
AWS Secret Access Key [None]: U****
Default region name [None]: ap-northeast-1
Default output format [None]: json

S3バケットの準備

 SAMでのデプロイまでの流れは以下の通りです。

  1. SAMテンプレートファイルをパッケージ化しS3に保存。
  2. パッケージ化したテンプレートを使用するよう記述したCloudFormationテンプレートファイルを作成。
  3. CloudFormationテンプレートファイルに基づいてデプロイ。

パッケージ化したSAMテンプレートファイルを保存するためのS3バケットをあらかじめ作成しておきます。さきほど作成したsam-sampleユーザーにバケット作成権限を持たせている場合には下記コマンドで作成可能です。バケット名はグローバルでユニークである必要がありますので、被らない名前をつけてください。

aws s3 mb s3://sam-bucket --profile sam-sample

SAMテンプレートの作成

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Create Lambda function by using AWS SAM.

# 共通設定
Globals:
  Function:
    Runtime: python2.7
    Timeout: 15
    MemorySize: 256

# リソース定義
Resources:
  GetBookmarkLambda:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: 'get-bookmark/lambda_function.zip'
      Handler: handler.handler
      Events:
        Timer:
          Type: Schedule
          Properties:
            Schedule: rate(5 minutes)

AWSTemplateFormatVersionは現在のところ'2010-09-09'しかありませんので、これを指定してください。

TransformではAWS::Serverless-2016-10-31を指定するとyaml形式でテンプレートを記述することができます。元々はjsonで記述するしかなかったのですが、現在ではより可読性の高いyamlで記述するのがおすすめです。

Resourcesで実際に使用するリソースを定義していきます。SAMで使用できるリソースはLambda(AWS::Serverless::Function)、APIGateway(AWS::Serverless::Api)、DynamoDB(AWS::Serverless::SimpleTable)などがあります。今回の構成ではLambdaしか使いません。

Lambda functionの作成

# coding:utf-8
import random
import logging
from HTMLParser import HTMLParser

from evernote.api.client import EvernoteClient
from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec

import settings


class LinkParser(HTMLParser):
    # ノート内のURLを抽出するパーサー
    def __init__(self):
        HTMLParser.__init__(self)
        self.url = ""

    def handle_starttag(self, tag, attrs):
        if tag == "a":  # 開始タグがaであるかどうか判定
            attrs = dict(attrs)  # タプルを辞書に変換する
            if 'href' in attrs:  # キー値(属性名)がhrefであるか判定
                self.url = attrs['href']

    @property
    def link(self):
        return self.url


def handler(event, context):
    # Evernoteクライアントを初期化する(1)
    client = EvernoteClient(token=settings.AUTH_TOKEN, sandbox=False)
    note_store = client.get_note_store()

    # ノートブック一覧の取得(2)
    notebooks = note_store.listNotebooks()

    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    for notebook in notebooks:
        # bookmarksという名前のノートブックにブックマークを保存している。
        if notebook.name == 'bookmarks':
            # 検索条件設定:指定ノートブックにひも付くノート(3)
            filter = NoteFilter()
            filter.notebookGuid = notebook.guid

            # 検索結果設定:ノートタイトルのみ取得(4)
            resultSpec = NotesMetadataResultSpec()
            resultSpec.includeTitle = True

            # ノートメタデータの検索(5)
            metalist = note_store.findNotesMetadata(filter, 0, 10000, resultSpec)

            parser = LinkParser()

            # ノートGUIDを検索条件にノートデータの取得(6)
            # ランダムに5件のbookmarkを抽出
            for n in random.sample(metalist.notes, 5):
                content = note_store.getNoteContent(n.guid)
                parser.feed(content)
                logger.info('Note title: {}\tLink: {}'.format(n.title, parser.link))
    
    return 'OK!'

前回書いたコードをLambda用に少し書き換えました。NOTEのタイトルとブックマークURLをログに出力するようにしています。

デプロイ

今回はLambdaでPython外部モジュールのevernoteを使うので、Lambda functionのコードとモジュールそのものを一緒にzipで固めてデプロイする必要があります。 まずは外部モジュールをカレントディレクトリにインストールします。

pip install evernote -t ./

次にzip圧縮

zip -r lambda_function.zip ./

テンプレートをパッケージ化させてさきほど作成したS3バケットにアップロードします。

aws cloudfront package \
       --template-file template.yaml \
       --s3-bucket sam-backet \
       --output-template-file packaged-template.yaml \ 
       --profile sam-sample

このコマンドでtemplate.yamlの内容をパッケージ化し、それをインポートしたpackaged-template.yamlという名称のCloudFormation用テンプレートファイルができあがります。後はこのテンプレートを基にCloudFormationでデプロイします。

aws cloudformation deploy \
       --template-file packaged-template.yaml \
       --stack-name get-bookmark-stack \
       --capabilities CAPABILITY_IAM \
       --profile sam-sample

CloudFormationでは、テンプレートで定義されたひと塊のリソース群をスタックと呼びます。今回は、get-bookmark-stackというスタック名をつけてデプロイしました。あとは自動的にリソースが作製されてデプロイ完了です。5分ごとにLambdaが実行されているログが確認出来れば成功です。

まとめ

今回はLambdaを用いたサーバーレスなインフラ構成をAWS SAMを使ってデプロイするところまで紹介しました。次回はslackへの通知機能を実装していきます。