woshidan's blog

そんなことよりコードにダイブ。

AWSによるサーバーレスアーキテクチャを読みました

3連休はサーバレスをやるぞ! と決めていたので、2ヶ月くらい積ん読してた気がする本をようやく読みました。

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

最初の方、思ったよりLambda関数を作成してはロールを付与して確認して、の流れが慣れなくて思ったより時間がかかりました。。

また感想がてらいくつかの話題についてまとめておきます。

サンプルのLambda関数を動かしてみて

2年以上前の本だったのでAuth0などのライブラリが古くて動かないところもあったのですが、そもそもLambda関数を作ってイベントから起動させたり、API Gatewayから呼び出したり、というのに不慣れだったため、7章くらいまでは一部妥協しながらも手を動かしながら読み進めていました。

Lambdaで実装しようとするときの一連の流れは

  • Lambda関数用のロールとロール用ポリシー作る
  • 上を関数のロールに指定してLambda関数を作る
  • Nodeなど関数を書く
  • 書いた関数をzipでまとめてデプロイ
  • その後、S3などイベントを発行するサービスから連携の設定をする

という感じです。

ポリシーの作り方について、Lambda関数の実行に必要最低限のポリシーを用意するのに管理ポリシーの AWSLambdaBasicExecutionRole (CloudWatchへのログ書き込みに必要な権限のみ)というのがあり、これに対し関数で行う内容によってさらに必要な権限を付与していきました。

特に参考になったのが、テスト手法の紹介で、lamdbaと同様の処理*1がローカルで実行できる run-local-lambda の紹介や、Lambda関数のコンソールにあるテストの設定を作る際に利用出来るイベントテンプレートがすごくよかったです。

イベントテンプレートはAWSの各種サービスから受け取るイベントの内容を下のgifのように1クリックで確認できて、すごい楽になりました。

f:id:woshidan:20181125135514g:plain

あと、地味にLambda関数のデフォルト実行時間の3秒が短くて、サンプルの動画をダウンロードしてメタデータを読み取る関数がエラーになりまくるなどしました。

バックエンドで実行すべき処理の例

サーバレスアプリケーションでは、基本的にフロントエンド*2から直接リクエストできる処理はなるべく間にバックエンドのサーバーなどを挟まない方がよいとされているのですが、それでもバックエンドから呼び出した方がよい処理はあります。

たとえば、

  • フロントエンドのコードというのはユーザーのブラウザにロードされる情報なので、ユーザーのブラウザに読み込まれてはいけないような 機密情報 を利用する処理であったり。
  • ブラウザというのはいつ閉じられてもおかしくないのに、中断されたら困るような処理をブラウザ上で実行したり。

というのはバックエンドで行った方がよい処理です。

フロントエンドから直接サービスを呼ぶために、HTTPリクエストをAWSのサービスに合わせたクエリに変換する

上の節にもサーバレスアプリケーションでは、基本的にフロントエンドから直接リクエストできる処理はなるべく間にバックエンドのサーバーなどを挟まない方がよい、と書いたのですが、たとえば、

  • Firebaseをフロントエンドから利用する
  • Auth0などの認証・認可のサービスをフロントエンドから利用する

といったサーバレスを意識して作られているだろうサービスを利用するのは、これらのサービスのSDKやサンプルコードも公開されているしイメージがつきやすいです。

しかし、それ以外のサービス、たとえばAWSに用意したRDSへ接続して SELECT 文を発行するような場合、単純なHTTPリクエストでもHTTPリクエストの内容をクエリに変換するようなコードを動かすバックエンド(LambdaやEC2上で動くWebアプリケーション)を用意する必要があるのではないでしょうか?

たとえば GET /users/2 というHTTPリクエストを受け取ったとき、それを SELECT * FROM users where id = 2; のクエリとその発行に変換するためにLambda関数を用意するといった具合に。

そういう疑問があったのですが、単純なクエリであればAmazon API GatewayAWSサービス統合を利用すれば、バックエンドを用意せずに実装することが可能です。

リクエストのパラメータとクエリに使う値、レスポンスの形式をテンプレートを書いて指定するのですが、DynamoDBやKinesis Firehoseの例は以下の記事が参考になります。

dev.classmethod.jp

dev.classmethod.jp

Lambda関数を書いていく上で重要な要素について

Lambdaの関数を書いてく上で重要な要素

  • 関数ハンドラ
  • イベントオブジェクト
  • コンテキストオブジェクト
  • コールバック関数
  • ログ

についてメモします。

まず、関数ハンドラは、Lambdaランタイムが書いた関数を実行するために呼び出しているもの(=エントリポイント)です。関数ハンドラの外に書いたコードはウォームスタート時は実行されないので、変数の初期化などは関数ハンドラの外で行うとよさそうです。

関数ハンドラの構文は

exports.handler = fucntion(event, context, callback) { // コード }

となっていますが、それぞれの引数は

  • 第1引数 ... イベントデータ
  • 第2引数 ... コンテキストオブジェクト
  • 第3引数 ... コールバックオブジェクト

と呼びます。コールバックオブジェクトは、関数の呼び出し元に情報を返したい時やエラーのログを残したい時に使います。

ちなみに、LambdaではS3へのファイルアップロードなどのイベント駆動で動作する関数は呼び出し元に情報を返す必要はなく、これらの実装(情報を返したりログを残す)はLambda関数を作成する上ではオプションです(やった方がいいです)。

イベントオブジェクトは、Lambda関数を呼び出した側がLambda関数に渡す情報が入ったオブジェクトで、S3のファイルアップロードによるイベントで起動される関数なら、ファイル名やバケット名が入ったイベントオブジェクトを受け取ります。

単純にリクエストパラメータとイベントオブジェクトの値をJSONとして対応させたいならAPI GatewayでLambda統合を利用すればよく、認証情報などがリクエストヘッダに含まれるのをイベントオブジェクトから取り出せるようにしたい、などカスタマイズする場合はマッピング*3を行います。

コンテキストオブジェクトはLambdaランタイムについての情報がわかる便利なプロパティやメソッドが生えています。たとえば、getRemaininTimeInMillis() メソッドで実行中のLambda関数のタイムアウトまでの残り時間が分かります*4

コールバック関数は、ハンドラ関数のオプションの第3引数で、Amazon API Gateway経由で呼び出される関数のように、関数の呼び出し元に情報を返すためのものです。

callback(Error error, Object result) のように第1引数がエラーを表すオブジェクトとなっていて、処理に成功した場合の第一引数はnullとします。

ログを出力したい場合は consle.log("message") でCloudWatchにログを書き込めます。 console.warn()console.error() なども利用できますが、CloudWatch側としては実質的な処理に違いはありません*5

もう一冊読みたいな、と思ってたんだけど一冊目だけで思ったよりわたわたしてしまいました。

現場からは以上です。

*1:イベントのJSONが渡されてハンドラ関数が実行されるという意味で

*2:具体的にはWebブラウザで動作するJavaScriptですね

*3:https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html など

*4:詳しくは http://amzn.to/1UK9eib

*5:ただ、運用としてはアラートレベルやログオブジェクトはしっかり管理した方がよいでしょう。