この記事はフロムスクラッチ Advent Calendar 2016 の8日目の記事です。
はじめに
自己紹介
- 今年30歳のフルスタックエンジニア
- 好きな言語:C++, C#
- 経歴:大学で人工衛星開発 → ITコンサル企業エンジニア → ITベンチャーアーキテクト
- 苦手なものはないがこれと言って得意なものもないのが悩み
- AWSは結構触っている。
やったこと
AWSのAPI GatewayへのGETリクエストをJSONに変換してKinesis Firehoseに流し込み、S3に出力するところまでやってみました。Firehoseがまだ東京リージョンで使えない為か、Firehoseについて書いてある記事があまり見当たりませんでした。
POSTについては参考記事にあるこちらの記事(Amazon API GatewayでAWS Service Proxyを使ってKinesis Firehoseにアクセスする)に書いてある手順で問題なく出来ましたが、GETを処理するのに地味に手こずりました。
API Gatewayとは?
簡単に言うとRESTfulなAPIを簡単に提供できるサービスです。スロットリングやキャシュなどAPIに必要な機能があれこれ用意されているだけでなく、ドキュメンテーションやバージョン管理なども行うことができます。AWSの他のサービスのAPIをラップしてRESTfulなAPIとして外部に提供することも可能です。
参考:Amazon API Gateway
Kinesis Firehoseとは?
ストリーミングデータをKinesis Analytics、S3、Redshift、Elasticsearch Serviceにロードするサービスです。ストリーミングデータを扱うサービスにはもともとKinesis Streamがありますが、データを素早く着地させる目的であればFirehoseの方がシンプルに実現することができます。
参考:Amazon Kinesis Firehose
手順
1. リージョン
Kinesis Firehoseは現時点ではバージニア北部(US Standard)、オレゴン(Oregon)、アイルランド(Ireland)でしか利用することができないため、リージョンをオレゴンにします。
2. S3 Bucketの用意
何はともあれ、データの出力先であるS3のバケットを用意します。FirehoseのリージョンがOregonなのでS3のバケットもOregonリージョンで作成します。※別リージョンでも大丈夫だと思いますが、データ転送量がかかってしまうので気を付けた方がよいです。
3. Kinesis Firehose
FirehoseのStreamのストリームを作成します。
コンソールメニューから Kinesis → Firehose と移動し、 [Create Delivery Stream] をクリックします。
今回はS3に出力するので、先ほど作成したS3のバケットを指定します。
バケット配下のフォルダに出力する場合には、S3 prefix
の最後に"/"を付けます。
次に、バッファサイズと転送頻度、圧縮、暗号化の有無、S3へのアクセスの為にIAM Roleを選択します。5分待つのが面倒だったので、転送頻度を最短の60sに変更しました。
StatusがACTIVEになったら準備完了です。
4. API Gateway
コンソールメニューから Amazon API Gateway に移動し、[APIの作成]からAPI名を入力してAPIを作成します。更に アクション → [メソッドの作成] から GET メソッドを作成します。
APIのリクエストパラメータを取得するために、[URLクエリ文字列パラメータ]に想定されるパラメータを登録します。
[統合リクエスト]をクリックし、Kinesis Firehoseにデータを流し込むために、画像のように設定します。(※具体的な手順は参考記事を参照してください)
5. マッピングテンプレート
さて、ようやくここからが本番です(笑)
[統合リクエスト] をクリックし [本文マッピングテンプレート] を開きます。Content-Typeにapplication/json
を登録しJSONテンプレートを登録するのですが、ここで結構はまりました。
結果的には、マッピングテンプレートに次のように登録するとうまくいきました。
先にネタバレしますが、#foreach
の中のKey-Valueをダブルクォーテーション2つずつで囲っているのがポイントです。こうしないと動きません。
#set($allParams = $input.params())
#set($params = $allParams.get('querystring'))
#set($data = "{
#foreach($paramName in $params.keySet())
""$paramName"" : ""$util.escapeJavaScript($params.get($paramName))"" #if($foreach.hasNext),#end
#end
}")
{
"DeliveryStreamName": "demo-stream",
"Record": {
"Data": "$util.base64Encode($data)"
}
}
最終的に、次のような形式のJSONが生成されればよいので、$data
をどうやって生成するか、ということが肝になってきます。
{
"DeliveryStreamName": "KINESIS_STREAM_NAME",
"Record": {
"Data": "$util.base64Encode($data)"
}
}
この2行でクエリパラメータを取得して$params
に格納することはできています。
更に、#foreach
を使うことで、$params
の中身をJSONにすることもできていることが確認できていました。
しかし!!!#set
で$data
に格納しようとするとエラーになる!!!なぜ!!!
2日くらい悩んだ末、ダブルクォーテーションを2つ重ねるとうまくいくことが分かりました。
#set($allParams = $input.params())
#set($params = $allParams.get('querystring'))
{
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end
#end
}
#set($data = "{
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end
#end
}")
マッピングテンプレートは、Apache Velocity Template Languageというテンプレート言語で記述されているのですが、#set
のリファレンスを見ると次のようになっています。どうやら''
で囲った場合はそのまま文字列として出力し、""
で囲った場合はメソッドなどが含まれていればそれを実行するようです。
細かいところまでは理解しきれていないのですが、おそらく#set
で$data
に値を格納する際に、""
で囲っているので、その内部は""
を重ねないといけなかったようです。。。
6. テスト実行
マッピングテンプレートを登録したら、APIをテスト実行してみます。
テストを開き、クエリ文字列に適当な値を入力して [テスト] をクリックすると、右側にログが表示され、正常に実行されたことが分かります。
Firehoseで出力先に指定していたS3のバケットを見ると、ファイルが出力されています。ファイルの内容は特に面白味がないですが、テストで指定したパラメータがJSONで出力されていることが分かります。
{
"bar" : "fuga" , "foo" : "hoge" }
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }{
"bar" : "fuga" , "foo" : "hoge" }
まとめ
- APIで受け取ったログをファイルに出力するだけなら、API Gateway + Firehoseですぐにできる。
- マッピングテンプレートは、VTLのコツをつかむのに苦労した。
- 負荷をかけたときのパフォーマンスやログのロストが懸念されるので、検証を行いたい。
- API Gatewayのスロットリングの制御がどの粒度で行えるのかも調べてみる。