読者です 読者をやめる 読者になる 読者になる

俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

S3のログをLambdaを使ってRedshiftへ送る - 日報 #157

2015年12月19日 (追記) こっちの方法のほうが安定します。

久しぶり世界。

最近KinesisだのLambdaだのもうなんだの、いっぱいAWS界隈で便利げなものが活発化してきて、
僕も乗り遅れんと必死なわけです。
とはいえ、RoomClipとして「便利」でなければ意味もなく、
仕様こそ知っているけど「うーむ」といって放置しておりました。

が、この度そこそこ合理的な理由を持ってLambdaの使いみちについて考えられたので、
さっそく実装してみることに。

テーマは「S3のバケットログをどうやって回収するか」です。 写真投稿アプリにおいて、S3上においてある写真ファイルが最終的に命なわけでして、
ビジネス上もとても大事なリソースとなります。
そのようなファイルの「アクセスログ」を回収する方法について、ちょっと考えてみます。

S3のログはコンソール上から設定できます。
設定するのは超カンタンなんですが、
結果出力されるログファイルが結構厄介な子です。
ログ用のバケットに「すごい任意のタイミングで」ログファイルを置いていくような仕様なんですね。
これを定期的に回収して整形してなんか見やすい場所に放り投げるcron処理のようなものが必要です。

ほいでまぁ、シンプルにRedshiftに全ログ放り投げたいと思うのは自然な考え方なので、
適当なEC2を作ってcronで1日1回、S3バケットをまるっと舐めて、該当日付のログファイルをつなげてCOPYするのかなぁとか思うわけです。
でもこれ、実は結構面倒なんですよね。
というのもS3のログファイルってかなり適当にきってあって(10分毎とかじゃなくて、結構バラバラ)、
そうなるとSDK使ってもピンポイントで「回収しなければならないファイル」をリスト化するのって難儀なんです。
もちろんできなかないけど。
それにcronだと1日1回っていうのもなんだかかったるいし、そもそもこのためにEC2つくるのも微妙だし、 このために他のcron走ってるEC2使ってもいんだけど、やっぱロギングって影響範囲を小さくしたいから、 できるだけ本丸から切り離していたいものですよね。

ということで、Lambdaをつかうというアイディアにたどり着きました。
S3のログバケットにPUTでオブジェクトが生成されたのをトリガに立ち上がるラムダ、
オブジェクトメタデータだけ取得してCOPYコマンドを生成し、Redshiftにクエリを投げ込む。
という流れです。
キモになるのは、Redshiftとの連携のあたり。
Lambdaは固定IPもなければセキュリティグループももってないので、
Redshiftにセキュアに接続しようとすると中々困難です。
この時点で諦めて、COPYコマンドだけはEC2に任せる、とかNAT使うとかも考えられるのですが、
ここまできたらEC2レスでやりきりたいよね。

というと、別の方法論が考えられます。
1つ目は「毎回LambdaのIPを特定して、Redshiftにセキュリティをセットする」です。
もう1つ目はちょいアクロバティックですが、「LambdaがS3のオブジェクトをもってきて、一行ずつKinesisになげる」です。

前者は「複数のLambdaが立ち上がった時、たまたまセキュリティが通らない奴がいる」可能性があり、
後者は「TimeoutせずにKinesisに送り切ることができるか」と「結局同じオブジェクトがS3にできて、それをKinesisがCOPYする」という問題があります。
んー、つまるところLambda使うのスジ悪そう、、、という風に考えました。

ですが、
まぁせっかくだし、Lambdaが固定IPになる可能性とかVPCリソースへのアクセス可能になる話とかもきてるし、
前者のやり方で組むだけ組んでみようと思ったので、組みました。

プログラムフローは下記の通り。

  1. LambdaのIPを http://checkip.amazonaws.com/ から取得する
  2. そのIPをaws-sdkをつかってRedshiftにセキュリティingressさせる
  3. Redshiftに接続し、COPYコマンドを走らす
  4. 接続を解除して、IPをrevokeする

とても簡単です。
ですが、問題は2,4周りです。
LambdaのIPは必ずしも毎回異なるわけではないので、
仮に接続直前でIP許可があっても、別のプロセスでrevokeされてたら接続不可能となってtimeoutします。
なので、IPをrevokeするのはingressしたプロセスだけにして、
AlreadyExistsだったプロセスはrevokeさせないように運用します。
完璧ではないですが、これで大分マシなはずです。

ということで、コードをgitHubにあげてありますので、気になる方はどうぞ。
Node.jsで実装しました。

https://github.com/hirayama/Lambda-S3Logger

なーんかLambda、Kinesisの良い事例ないかなぁ…

あ!
あとチップスとして、S3ログフォーマットが、空白区切りデータのくせに日付表示に空白を含むという半端じゃない困った子ちゃんなので、
Redshiftにぶっこむときそうとう苦しみます。
Dateでソートができないとか、全く意味無いですもんね。
なのでCOPYコマンドで少し工夫する必要があります。

COPY s3log_table
FROM s3://hogehoge/
CREDENTIALS aws_access_key_id={aws_access_key};aws_secret_access_key={aws_secret_access_key}
delimiter ' '
REMOVEQUOTES
COMPUPDATE OFF
MAXERROR 1000
TIMEFORMAT AS '[DD/MON/YYYY:HH24:MI:SS'
REGION '{S3Region}

こんな感じでTIMEFORMATを指定しておくといいです。
味噌は最初のカギカッコw