スケーラブル tail -f | grep on AWS

こんにちは。組長こと @fujiwara (id:sfujiwara) です。

早速ですが皆さん、ログは見ていますか? 当然見ていますよね。tail -f で追いかけるとか大好きですよね。という前提で話を進めましょう。

この記事は Tech KAYAC Advent Calendar 2016 の24日目の記事です。

TL;DR

  • 多種多様なホストから発生するログの末尾を tail -f で追いかけたい
  • fluentd + ローカルファイルで行うのはちょっと面倒
  • Amazon Kinesis + Lambda を使ってスケーラブルな仕組みを考えてみた
  • kinesis-tailf というツールも作った

f:id:sfujiwara:20161222165753p:plain

既存の仕組みと問題点

techblog.kayac.com

弊社の Lobi とソーシャルゲームでは、この記事で紹介されたような Fluentd を活用したログ集約の仕組みを整備しています。この構成は快調に動いているのですが、多少の問題があります。

  • 集約サーバの構築、可用性維持が面倒
    • 特に流量が多いと、集約サーバがボトルネックになりがち
    • ファイルバッファを使用するため、インスタンスを使い捨てたりオートスケールしたりしづらい
  • 集約サーバを複数用意する場合、流れているログの末尾を tail -f で追いかけづらい

アラート検知や定型の集計作業は Norikra に送ってそこでクエリしたり、S3からRedshiftに投入してクエリしたりで行っていますが、障害発生時や動作確認時など、流れているログをなるべくタイムラグ少なく、アドホックに眺めたりgrepしたりしたい場合があります。

集約サーバは性能と可用性を持たせるために複数台構成にし、そこで Norikra, S3 などへ送るのとは別にローカルファイルに書き込みを行っているので、拙作の nssh というツールで全台に並列に ssh して tail -f を実行する、というオペレーションを行うことがたびたびありました。

これをもうちょっと何とかできないか、ということで、スケーラブルな tail -f | grep の仕組みを考えてみました。

Kinesis Stream に流す

まず、頼れる土管こと Amazon Kinesis Streams にすべてのログを投げ込むことにします。集約サーバを経由するとそこがボトルネックになるので、ログが発生するすべてのホストに Fluentd を動作させ、そこから fluent-plugin-kinesis を使って送ることにしましょう。@type kinesis_producer を使用すると、複数レコードを Kinesis Producer Library (KPL) を使ってまとめ送りできるので、流量を少ないレコード数に押さえ込めます。

<match log.**>
  @type kinesis_producer
  region ap-northeast-1
  stream_name log-stream
  flush_interval 1s
  include_time_key true
  include_tag_key true
</match>

なるべく即座に送り出したいので、flush_interval 1s としています。

kinesis-tailf で追尾する

github.com

Go製の拙作のツールです。指定した Kinesis Stream を追いかけて、流れてきたレコードの内容を標準出力に出力し続けます。つまり Kinesis に対する tail -f ですね。KPL でまとめられたレコードの展開も自動で行います。

$ kinesis-tailf -stream log-stream -lf

これで、すべてのログを ssh なしで手元のマシンで tail できるようになりました。あとは pipe でフィルタコマンドに通して加工するなりなんなりと。めでたしめでたし。

……といいたいところですが、これではすべてのログがネットワーク経由で kinesis-tailf を実行しているホストに流れ込んできてしまうことになります。本番環境では大量のログが発生しているため、すべてを手元で読み取りたいことは少なく、たとえば status=500 のログであったり、特定のIPアドレスからのリクエストだったりを事前にフィルタしたいことのほうが多いでしょう。(出先での障害対応時に全ログをテザリング回線で手元に持ってくるとか考えたくありませんし)

ということで、AWS上でスケーラブルに grep することを考えます。

Lambda で絞り込んで別の stream へ

AWS Lambda で、Kinesisから流れてきたデータを加工するコードを実行できます。

kinesis-tailf/lambda at master · fujiwara/kinesis-tailf · GitHub

kinesis-tailf の lambda ディレクトリ以下に Apex を利用したコードが置いてありますので、project.json を適当に以下のように用意して apex deploy し、Lambda とログが流れている Kinesis Stream を繋いでください。role には Kinesis への読み書き権限が必要です。

{
  "name": "kinesis-grep",
  "description": "",
  "memory": 128,
  "language": "go",
  "timeout": 5,
  "role": "arn:*****",
  "environment": {
    "MATCH": "\"status\":500",
    "STREAM_NAME": "filtered-log-stream"
  }
}
  • MATCH で指定された正規表現にマッチするレコードのみを
  • STREAM_NAME で指定された stream に流し直す

という動作を行います。あとはこの出力先 (filtered-log-stream) を kinesis-tailf で追い続ければよい、ということになります。 Kinesis Stream のシャード数に応じて動作する Lambda も増えるため、流量が多い (=シャードが多い) 場合でも問題なくスケールするでしょう。

  • KPL でまとめられたレコードの展開に対応しています
  • 流量が多い場合は memory 割り当てを大きくしないと追いつけないことがあります
    • LambdaのCPU性能はmemoryに比例するため
  • 必要ないときは入力元 stream との関連づけを外しておけば費用は掛かりません

ということで、できあがったものは以下のような概念図になりました。

f:id:sfujiwara:20161222165753p:plain

実際に1万メッセージ/sec以上のログを10シャードのstreamに送りつけて試してみたところ、Lambdaのメモリ512MBで問題なく動作しました。ログが発生してから kinesis-tailf に流れてくるまでのタイムラグは4秒程度 (fluentd, kinesis, lambda, kinesis-tailf でそれぞれ1秒ぐらいラグがある) で、実用上も問題ない程度だと思います。

WEB+DB PRESS Vol.94

WEB+DB PRESS Vol.94

  • 作者: 藤原俊一郎,朽木拓,八木俊広,吉田太一郎,うらがみ,のざきひろふみ,うさみけんた,水嶋淳貴,佐々木健一,柴崎優季,前島真一,伊藤直也,遠藤雅伸,ひげぽん,海野弘成,はまちや2,竹原,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2016/08/24
  • メディア: 大型本
  • この商品を含むブログを見る


12/1 から24日間にわたって更新してきた Tech KAYAC Advent Calendar 2016 もこれが最後の記事となります。バラエティーに富んだ記事をお楽しみいただけましたでしょうか。

カヤックではログを眺めるのが好きなエンジニアも、そうでないエンジニアも募集しています!