『打刻システムの構築(前編)』 『打刻システムの構築(後編)』 では打刻APIを作成し、APIが受け取った打刻イベントをSNSのトピックにパブリッシュ。トピックのリスナーであるLambda関数 dakoku-google-sheets-writer が Google Sheets へ打刻イベントを記録する仕組みを構築した。
記録される時刻はdakoku-google-sheets-writer内で取得した現在日時を利用しているが、この方法ではトピックに新たなリスナーを追加した場合、同一打刻イベントに対して各リスナーがそれぞれに現在時刻を取得する事になり塩梅が悪い。
各リスナー毎に時刻を取得するのではなくAmazon SNSイベントのTimestampプロパティを参照すれば良いのだが、これとは別に以前から
- ClojureScriptでLambda
- Lambda@Edge
を使う機会を窺っていたのでこれを機に利用する。
内容としては打刻APIの前面、CloudFrontにClojureScriptで書いたLambda@Edgeを配置。
APIの呼び出し時にLamda@Edge側で現在時刻を取得し HTTPヘッダ X-Dakoku-Timestamp
にセット、API Gatewayで同ヘッダの値をSNSのURLクエリ文字列パラメータSubject
に載せ替え打刻トピックへパブリッシュ。 後続のリスナーはパラメータSubject
から打刻された時刻を取得するように変更する。
少々というか、かなり回りくどい事になっているがLambda@EdgeのHello Worldのようなサンプルを作るより実際に自分が日々利用する処理に適用した方がモチベーションもあがるので良しとしよう。
Lambda@Edgeについて
CloudFrontイベントをエッジロケーションで処理することができるLambda関数。通常のLambda関数と比べて厳しい制約がある。
制約
特に意識する必要がある制約は以下。
- Node.js 6.10のランタイムのみ
- メモリは最大128MBまで
- Origin (request / response) eventの処理は3秒以内で完了
- Viewer (request / response) eventの処理は1秒以内で完了
- Viewer (request / response) eventの処理内でS3/DynamoDB等のネットワーク呼び出しは不可
- Lambda関数のzipパッケージは1MB以内
- 環境変数は利用不可
全ての制約についての詳細は CloudFront開発者ガイド を参照のこと。
リージョン
CloudFrontにはリージョンの概念は無いが管理上米国東部(バージニア北部)に属するためLambda関数とアップロード用のS3バケットについても米国東部(バージニア北部)リージョンである必要がある。
Lambda関数を作成
先の制約にあったようにLambda@Edgeのランタイム環境はNode.js(6.10)のみではあるが、 ClojureScriptはデフォルトでES3のコードを出力するため問題なく実行することができる。
コード
入力となるCloudFrontイベントを一度clojureのデータ構造に変換したのち、ヘッダ X-Dakoku-Timestamp
を追加。その後にJSオブジェクトに戻し処理を続行している。
(ns jp.nijohando.dakoku.request-rewriter.lambda
(:require [cljs.nodejs :as nodejs]))
(defn- now
[]
(.now js/Date))
(defn- rewrite
[event]
(-> (get-in event [:Records 0 :cf :request])
(update-in [:headers :x-dakoku-timestamp] concat [{:key "X-Dakoku-Timestamp" :value (str (now))}])))
(defn handler
[event context callback]
(->> (js->clj event :keywordize-keys true)
(rewrite)
(clj->js)
(callback nil)))
(nodejs/enable-util-print!)
(aset js/exports "handler" handler)
関数handler
を同名でexportしている。
ClojureScriptのビルド結果ファイルがindex.js
であればLambdaを登録する際のハンドラ名はindex.handler
となる。
ビルド&パッケージ
lein-cljsbuildを使わずにcljs.build.api
を直接使ってビルドしている。
またパッケージングは自前でZipアーカイブを作成している。
今回はnpmエコシステムに依存していないが、依存する場合はnode_moudles
配下を含める必要がある。
パッケージのサイズは最適化レベル :advanced
で9kバイト弱となった。
Lambda@Edgeの制約としてパッケージサイズを1MB以内に納める必要があるが、サイズについては本体コードのサイズ、最適化レベル等よりも依存するnpmパッケージ量が大きな要因となりそうだ。
REPL
今回課題だったのがREPL環境。
REPL自体はcljs.repl/repl*を直接使いNode.js環境のREPLを問題無く利用する事ができたのだが、
肝心のREPL駆動開発時におけるコードのリロード方法のセオリーが分からなかった。
(tools.namespaceやFigwheelのような)
ということで今回は致し方なく素朴にrequireをreloadフラグ付きで利用した。
2017/01/28 追記
『ドアの動き検知システムの構築3』にてClojureScriptでLambdaを書く際にFigwheelを試した。
S3バケットの作成
Lambda@EdgeをアップロードするためのS3バケットをに米国東部(バージニア北部)に作成する。
AWSマネージメントコンソールから S3 を開き、バケットの作成
- バケット名
dakoku-edge.nijohando.jp
- リージョン
- 米国東部(バージニア北部)
作成 でバケットを作成。
Lambdaの登録
AWSマネージメントコンソールから Lambda を開く。
リージョンを 米国東部(バージニア北部) に切り替え、関数の作成 から 一から作成
トリガーの追加 で何も指定せずに 次へ
- 名前
dakoku-request-rewriter
- ランタイム
- Node.js 6.10
- コード エントリ タイプ
- Amazon S3からのファイルのアップロード
- S3リンクのURL
https://s3.amazonaws.com/dakoku-edge.nijohando.jp/dakoku-request-rewriter.zip
- ハンドラ
index.handler
- ロール
- テンプレートから新しいロールを作成
- ロール名
dakoku-request-rewriter
- ポリシーテンプレート
- 基本的なエッジLambdaのアクセス権限
次へ 関数の作成
関数を登録したら アクション -> 新しいバージョンは発行 で現在のLambda関数のバージョンを固定する。
Lambda@EdgeではCloudFront側からLambdaを参照する際に$LATESTやエイリアスによる参照ができないためバージョン付きのARNで参照する必要がある。
バージョン1が発行されるのでこのバージョンのARNをメモしておく。
(次のCloudFront側からのLambda関数の参照時に利用する)
CloudFrontイベントとLambda関数を紐付ける
AWSマネージメントコンソールから CloudFront 、打刻用ディストリビューションを選択。
Behaviors から events-ifttt-<任意の英数字の組み合わせ>
と Default (*)
の各Behaviorに対して、
Edit
- Lambda Function AssociationsEvent Type
- Origin Request
- Event Type
- Origin Request
- Lambda Function ARN
- 先ほど作成したLambda関数
dakoku-request-rewriter
のバージョン1のARN
Yes, Edit で保存。
API Gateway
API GatewayにてX-Dakoku-Timestamp
ヘッダの追加とURLクエリ文字列パラメータSubject
へのマッピングを行う。
HTTPヘッダの追加
AWS マネージメントコンソールから API Gateway を開き、API dakoku
の events-ifttt
リソース、POST
メソッドの メソッドリクエスト を選択。
HTTPリクエストヘッダ から ヘッダの追加
- 名前
X-Dakoku-Timestamp
- 必須
- ✓
HTTPヘッダをクエリ文字列にマッピング
events-ifttt
リソース、POST
メソッドの 統合リクエスト を選択。
URL クエリ文字列パラメータ から クエリ文字列の追加
- 名前
Subject
- マッピング元
method.request.header.X-Dakoku-Timestamp
✓
で適用。
APIのデプロイ
API Gatewayの変更を本番環境へ反映するためにデプロイを行う。
API dakoku
を選択し、Action から APIのデプロイ を実施。
- デプロイされるステージ
- prod
デプロイ で反映。
最後に
以上で打刻API呼び出し時にLamdadakoku-request-rewriter
が実行され打刻時刻が後続の打刻トピックリスナーへ伝播するようになった。
次回の『打刻情報をKinesis FirehoseでS3へ保存』ではこの仕組みに乗っかり打刻情報をKinessis Firehoseを経由してS3に蓄積する打刻トピックリスナーを追加する。