ラベル fluentd の投稿を表示しています。 すべての投稿を表示
ラベル fluentd の投稿を表示しています。 すべての投稿を表示

2014年2月10日月曜日

TreasureDataってなんじゃ?

Treasure Dataはデータの収集、保存、管理、処理、可視化などを行えるログ解析の基盤サービスで、fluentdを使ってデータを収集し、hadoopで解析を行います。
自分も以前の記事で書きましたが、よくelasticsearch + kibanaや、splunkなどの部分がサービス化されているようなイメージです。

Treasure Dataについては、中の人が非常に詳しくブログで書かれています

それでは実際に触ってみます。


ユーザー登録&ログイン


http://www.treasuredata.com/jp/products/try-it-now.php
にアクセスして、サインアップのリンクをクリックします。




サインアップ画面が表示されるので、必要な情報を入力して登録します。




登録が終わると、確認メールが送られてくるので、確認リンクをクリックして登録完了です。
また、ログインする場合は、以下のような画面でログインすることになります。





ログの送信



ログの収集対象となるサーバーで、apacheのログデータをTreasure Dataに送信するように設定します。
まず、ログのあるサーバーでtd accountコマンドを使って先ほど登録したメールアドレスとパスワードを設定して、アカウントを紐付けます。

# td account
Enter your Treasure Data credentials.
Email: miura@cloudpack.jp
Password (typing will be hidden):
Authenticated successfully.
Use 'td db:create <db_name>' to create a database.


次に、Treasure Dataに送信する時のAPIキーを取得するために、td apikey:showコマンドを実行します。
(APIキーは伏せてあります。)
# td apikey:show
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


次に、/etc/td-agent/td-agent.confで、sourceをapacheアクセスログのtailを、matchで今のAPIキーを使ったtdlogを設定します。tdlogがTreasure Dataにログを送信するためのプラグインになります。
auto_create_tableを設定しておくと、自動的にログ用のテーブルがTreasure Data側に用意されます。

  <source>
    type tail
    format apache
    path /var/log/httpd/access_log
    pos_file /tmp/access.log.pos
    tag td.apache.access
  </source>

  <match td.*.*>
    type tdlog
    apikey XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    auto_create_table
    buffer_type file
    buffer_path /var/log/td-agent/buffer/td
    use_ssl true
  </match>


設定したら、td-agentを起動します。
/etc/init.d/td-agent start

ログ収集対象のサーバーの設定は以上です。

td tablesコマンドを使用すると、Treasure Data側で作成されたテーブルの一覧が表示されます。

# td tables;
+-----------+------------+------+-------+--------+---------------------------+---------------------------+----------------------------------------------------------------------------------------------------------+
| Database  | Table      | Type | Count | Size   | Last import               | Last log timestamp        | Schema                                                                                                   |
+-----------+------------+------+-------+--------+---------------------------+---------------------------+----------------------------------------------------------------------------------------------------------+
| apache    | access     | log  | 26    | 0.0 GB | 2014-02-10 04:01:49 +0900 | 2014-02-10 04:00:17 +0900 | host:string, path:string, method:string, referer:string, code:long, agent:string, user:string, size:long |
| sample_db | www_access | log  | 5,000 | 0.0 GB | 2014-01-30 16:43:07 +0900 | 2013-09-07 10:13:45 +0900 | host:string, path:string, method:string, referer:string, code:long, agent:string, user:string, size:long |
+-----------+------------+------+-------+--------+---------------------------+---------------------------+----------------------------------------------------------------------------------------------------------+
2 rows in set


Treasure Dataのアカウントを作成すると、デフォルトでsample_dbというデータベースが用意されていますが、ログが保存され始めると、apacheデータベースのaccessテーブルというのが作られています。
DB名、テーブル名はsourceのtagを元に作成されます。


Treasure Dataでの集計


Treasure Dataの管理画面のDatabasesにも、apacheデータベースが追加されていることがわかります。
ドリルダウンしていくと、登録されているログデータが構造化されているのを見ることができます。





左ペインのNew Queryで集計の設定をおこないます。
データベースはapacheを選択し、今回はQuery typeはHiveを選択します。
Queryに集計するためのHiveクエリを書き、あとはそのままで「Submit」をクリックすると、Jobの実行が開始されます。Job実行はスケジューリングすることもできます。




Copy result toでMySQLやS3など結果を保存する先を指定することもできます。




Jobの実行が終わると、Jobリストとして結果が表示されます。
今回は、結果の保存先を特に指定していないので、この結果画面にそのまま結果が表示されます。




ざっとですが、td-agentを使ったログの収集、Treasure Dataの管理コンソールでのログの確認、集計をさらってみました。

今回は以上です。


2014年2月6日木曜日

Kibanaってなんじゃ?(kibana3 + elasticsearch + fluentd)

ずんぶん前にKibanaの記事を書きましたが、時がたちKibana3がとても感じがよいと巷で評判なので、再入門してみます。

インデックスサーバー側


準備

インデックスサーバー側のポートは80,22の他に、fluent用に9200番ポートを開けておきます。
また、インデックスは最終的に大きくなるので、容量の大きなストレージに入れておきます。

# yum install xfsprogs httpd java-1.7.0-openjdk -y
# mkfs.xfs /dev/xvdf
# mount -t xfs /dev/xvdf /mnt/ebs/0


Kibana3のインストール

Kibanaはv3になってから、rubyではなくhtmlになりました。DocumentRoot下に置いてhttpdを起動するだけでOKです。

# cd /var/www/
# curl -OL http://download.elasticsearch.org/kibana/kibana/kibana-latest.zip
# unzip kibana-latest.zip
# mv html html.org
# mv kibana-latest html
# /etc/init.d/httpd start


ElasticSearchのインストール

# cd /mnt/ebs/0/
# curl -OL https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.11.tar.gz
# tar xzvf elasticsearch-0.90.11.tar.gz
# cd elasticsearch-0.90.11/
# ./bin/elasticsearch start

これで、インデックスサーバー側は準備ができました。



ログ送信サーバー側


apacheのログを送信するとして、ここではfluentdの設定はin:tailとout:elasticsearchプラグインを使います。

# yum install td-agent -y
# vim /etc/td-agent/td-agent.conf
<source>
  type tail
  format apache2
  path /var/log/httpd/access_log
  pos_file /tmp/access.log.pos
  tag server1.apache.access
</source>

<match server1.apache.access>
  type_name apache
  type elasticsearch
  include_tag_key true
  tag_key @log_name
  host XXX.XXX.XXX.XXX
  port 9200
  logstash_format true
  flush_interval 10s
</match>


# /etc/init.d/td-agent start

これでログの送信設定は完了です。



確認



ここまで設定できたらkibanaの画面を見てみます。
これがデフォルトのトップ画面です。
右側の[Logstash Dashboard]というリンクをクリックすると、ダッシュボード画面に遷移します。




ダッシュボードでは、fluentdから送られてきたログがひな形のダッシュボードに表示されていることがわかります。





クエリの追加

クエリフィールドはフィールドの「+」ボタンでいくつも登録できます。
クエリの書式はluceneの書式が基本となっているようです。




パネルの追加

ダッシュボードはグリッド上にできており、基本的に行(ROW)にパネルを追加していきます。
パネルはひとつまたは複数のクエリを使用します。

ROWにある「Add Panel」ボタンでパネルを追加します。




パネル追加画面では、パネルのタイプや使用するクエリや、その他パネル固有のパラメータを設定します。



いくつかのクエリとパネルを組み合わせて目的に会ったダッシュボードを造ります。



作成したら、名前をつけてダッシュボードを保存することで、リロードしても保持されるようになります。



以前と比べてかなりいろいろなデータを表示できるようになってきました。
細かいところはまた今度。

以上です。



2013年5月28日火曜日

Fluentdってなんじゃ?(設定ファイルをリモートから読み込む)

fluentdのドキュメントに以下のような記載がありました。

Configuration File - (3) Include Directive
# absolute path
include /path/to/config.conf

# if using a relative path, the directive will use 
# the dirname of this config file to expand the path
include extra.conf

# glob match pattern
include config.d/*.conf

# http
include http://example.com/fluent.conf

最後の行、、、HTTPから設定ファイルを取得してインクルードできるみたいです。

ということで試してみました。

common.conf
<source>
  type tail
  format apache
  pos_file /var/log/td-agent/httpd-access.log.pos
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type s3

  aws_key_id xxxxxxxxxxxxxxxxxxx 
  aws_sec_key yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  s3_bucket hoge-bucket
  path logs/
  buffer_path /var/log/fluent/s3
  flush_interval 10s 
</match>

これをS3にアップロードしてWEBホスティングしてみます。


そして、EC2では以下のように設定します。

/etc/td-agent/td-agent.conf
include http://s3-ap-northeast-1.amazonaws.com/hoge-bucket/conf/fluentd/common.conf
# /etc/init.d/td-agent start


httpdのコンテンツにアクセスしてしばらくたつと、、、


無事出力されていました!

これの何がいいかというと、

  • 各サーバーに共通の設定ファイルなどを外部化しておき、一括変更をかけられる (再起動は必要ですが、定期的に再起動をかけるようにしておくという手もあります)
  • いっそ全設定を外出しして完全に外部で管理する

などが可能になります。

以上です。

2013年4月27日土曜日

splunkってなんじゃ?(splunk stormでfluentd)

前回の記事でsplunk enterpriseを利用しましたが、今回はsplunk stormを使用してみます。
enterpriseはインストール型でしたが、stormはサービス型です。
stormでは無料枠ではデータストレージが1GBまでとなっています。

また今回はsplunkのfluentプラグインがあるので、ログをfluentでstormに投げてみたいと思います。

splunk stormに登録して、プロジェクトを作ります。




ストレージ容量を決めます。1GBまでは無料です。



データの入力を設定します。
ここではAPIを利用するように設定します。


APIのリンクをクリックすると、APIの認証とエンドポイントの情報が表示されます。
  • AccessToken
  • API Hostname
  • ProjectID




次に、fluentdの設定です。
splunkのAPIに対してログを送信するBufferedOutputプラグインを作成している方がいたので、それを使ってみます。

fluent-plugin-splunkapi
https://github.com/k24d/fluent-plugin-splunkapi

# /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-splunkapi


ソースやドキュメントを見ながらstorm用の設定を行います。
今回もapacheのログを送信します。

# vi /etc/td-agent/td-agent.conf
<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag server1.apache.access
</source>

<match *.apache.*>
  type splunkapi
  access_token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  project_id yyyyyyyyyyyyyyyyyyyyyyy
  protocol storm
  sourcetype fluent
  format text
  flush_interval 10s
  buffer_type memory
  buffer_queue_limit 16
</match>


access_tokenとproject_idには、上述のAPIの情報画面の情報を設定します。
fluentdを起動します。

# /etc/init.d/td-agent start


上の画像の「Explore data」をクリックすると、ログデータのサマリーが表示されます。
ソース欄に、fluentで設定したタグ名が表示されています。



このリンクをクリックすると、収集されたログデータの一覧が表示されます。




enterprise版と同様に、レポートを作成することもできますし、ダッシュボードに各種グラフを表示することもできます。





splunk stormでは、無料枠では1GB制限の他、1プロジェクトまでしか登録できないようです。

サービス型なので、サーバーメンテが必要ないのは良い点ですが、基本的にもっさりしています。
Plan選択画面で、2GB以上の有料枠に「Guaranteed response time for reported issues」とあるので
有料だとパフォーマンスが改善するのかもしれません。

自社でインフラを持ちたくなく、カスタマイズもそこまで必要ないという場合は、このようなサービス型のプロダクトは有効だと思います。

以上です。







2013年1月15日火曜日

Fluentdってなんじゃ?(datacounter+Growthforecast+RDSでステータスコードの傾向を可視化)


Fluentdではさまざまなデータを構造化することができるので、フロント側の可視化ツールに渡すことも柔軟に対応できます。今回はGrowthForecastという可視化ツールに表示させてみます。

データはApacheのログを使い、表示する内容はHTTPステータスコードの2xx,3xx,4xx,5xxのそれぞれの回数を時系列で表示してみたいと思います。またGrouthForecastはデータのスタックにDBを使用し、デフォルトではsqlite3ですが、今回はRDSのMySQLを利用してみます。

構成は下図のようなイメージです。



管理サーバー(222.222.222.222)


まずは、管理サーバーのEC2インスタンスにGrowthForecastをインストールします。

必須ライブラリをインストールします。

# yum groupinstall "Development Tools"
# yum install pkgconfig glib2-devel gettext libxml2-devel pango-devel cairo-devel cpan mysql-devel bitmap-console-fonts

perlbrewcpanmをインストールします。

# cpan YAML JSON::XS
# curl -kL http://install.perlbrew.pl | bash
# cpan App::perlbrew perlbrew init
# perlbrew install perl-5.16.0
# perlbrew switch perl-5.16.0

# cd /usr/bin
# curl -LO http://xrl.us/cpanm
# chmod +x cpanm
# cpanm --self-upgrade

GrowthForecastをインストールします。

# cpanm -n http://nomadscafe.jp/pub/GrowthForecast/GrowthForecast-0.34.tar.gz

次に、RDSを設定します。
今回は内容はほぼデフォルトでOKです。



設定したら接続権限を設定します。
RDSのエンドポイントに対して接続し、GRANT文を定義します。

# mysql -u memorycraft growthforecast -pmemorycraft-pass -h growthforecast.cwnvl1ncuiwq.ap-northeast-1.rds.amazonaws.com
mysql>GRANT CREATE, ALTER, DELETE, INSERT, UPDATE, SELECT ON growthforecast.* TO 'memorycraft'@'222.222.222.222' IDENTIFIED BY 'memorycraft-pass';

また、RDSのセキュリティグループも設定します。


また、管理サーバーのEC2の5125(GrowthForecastのデフォルトポート)のInboundに管理者とWEBサーバーから接続できるように追加しておきます。




GrowthForacastを起動します。
--with-mysqlオプションをつけると、データベースにMySQLを使用するようになるので、RDSのエンドポイント付きのdsnを渡します。

MYSQL_USER=memorycraft MYSQL_PASSWORD=memorycraft-pass growthforecast.pl --with-mysql dbi:mysql:growthforecast:growthforecast.cwnvl1ncuiwq.ap-northeast-1.rds.amazonaws.com

これで起動されました。
http://222.222.222.222:5125/を見ると以下のように、まだグラフがなにも表示されていません。





WEBサーバー(111.111.111.111)


GrowthForecastはAPI経由でデータを登録するのが基本です。
そしてfluentdには、GrowthForecastにAPI登録する、fluent-plugin-growthforecastというアウトプットフィルタがあるので、
今回はそれを利用します。
また、ログ中のHTTPステータスコードをカウントするために、fluent-plugin-datacounterも利用します。

fluentd(td-agent)のインストールは以前の記事の通りです。

/usr/lib/fluent/ruby/bin/fluent-gem install fluent-plugin-growthforecast
/usr/lib/fluent/ruby/bin/fluent-gem install fluent-plugin-datacounter

td-agent.confは以下のように設定します。
tail: Apacheのログを正規表現で構造化
datacounter: statusを2xx,3xx,4xx,5xxに分類して其々をカウント
growthforecast: datacounterの結果をGrowthForecastへ投稿

この場合は、
http://222.222.222.222:5125/api/admintool/httpstatus/グラフ名

への投稿になり、グラフ名には
apache.httpstatus_apache_2xx_count

のように入ります。
これは
growthforecastのタグ_オリジナルタグ_パターン名_count

の形で、datacounterとgrowthforecastの2つのディレクティブを通過しているためのようです。この名前が冗長であれば、それぞれのディレクティブでremove_prefixなどを使って調整など行えそうです。

<source>
  type tail
  format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  tag apache
  pos_file /tmp/access.log.pos
</source>

<match apache>
  type copy
  <store>
    type stdout
  </store>
  <store>
    type datacounter
    tag apache.httpstatus
    aggregate tag
    count_key status
    pattern1 2xx ^2\d\d$
    pattern2 3xx ^3\d\d$
    pattern3 4xx ^4\d\d$
    pattern4 5xx ^5\d\d$
  </store>
</match>

<match apache.httpstatus>
  type copy
  <store>
    type stdout
  </store>
  <store>
    type growthforecast
    gfapi_url http://222.222.222.222:5125/api/
    service admintool
    section httpstatus
    name_keys apache_2xx_count,apache_3xx_count,apache_4xx_count,apache_5xx_count
  </store>
</match>
ここまで出来たら、td-agentを起動します。

/etc/init.d/td-agent start

これで設定は完了です。
WEBサーバーのコンテンツに適当にアクセスしてから、管理サーバーのGrowthForecastのアドレス(http://222.222.222.222:5125)を見てみます。




項目が出来ています。このリンクを辿って行くと、、



おお、4つのグラフが表示されています。

ここで、「複合フラグの追加」というリンクをクリックすると4つの項目を1つのグラフに統合した新しいグラフを作ることができます。基本のグラフに加え、2番目以降の系列に残りの項目を追加していくと、以下の様にスタックされたグラグを作成することができます。




datacounterという汎用的すぎるプラグインと、GrowthForecastというとても便利なグラフ化ツールで、いろいろなリソースを可視化してみると、運用の負荷軽減だけではなく、ユーザーの動向などもわかってお得ですね。
以上です。


2012年12月21日金曜日

EMRってなんじゃ?(ログ、ゆりかごから墓場まで)

AWS Advent Calendar 2012 に参加させてもらいました。21日担当です。

AWSでWEBサイトをホストするときのログのライフサイクルについて、まとめてみました。

WEBサーバーの一般的なログの扱いは以下のような感じだと思います。

  1. 各インスタンスのアクセスログを1箇所に集める
  2. 複数のログファイルを1ファイルにまとめてソートする
  3. 集計をする
  4. 古いログのバックアップをとり、削除する


これをAWSで行なってみると例えば以下のようになります。

  1. fluentdを使って各インスタンスのログを1つのバケットAに送る
  2. EMRで1つにまとめてソート。別のバケットBに保存
  3. EMRで集計もして、別のバケットBに保存
  4. バケットAの期限が過ぎたものをGlacierに送る


図にすると以下のようなイメージです。



それでは1つずつやってみます。

1.各インスタンスのアクセスログを1箇所に集める


これはfluentdでtail→s3で行います。
WEBサーバーのインスタンスで、以下の用にfluentdをインストールします。
# cat /etc/yum.repos.d/td.repo
[treasuredata]
name=TreasureData
baseurl=http://packages.treasure-data.com/redhat/$basearch
gpgcheck=0

# yum install td-agent -y


次にtd-agent.confを設定しますが、ログファイル名があとで重複しないように、
td-agent.conf.tmplを使用します。以下のようにtime_slice_formatに${hostname}というプレイスホルダを入れます。
また、yyyymmddのフォーマットでフォルダを切って、そこに保存するようにします。
# cat /etc/td-agent/td-agent.conf.tmpl

<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type s3
  aws_key_id xxxxxxxxxxxxxxxxxxxx
  aws_sec_key yyyyyyyyyyyyyyyyyyyyyyyyyyy
  s3_bucket memorycraft-log
  path logs/
  buffer_path /var/log/fluent/s3
  time_slice_format %Y%m%d/${hostname}-%H
  time_slice_wait 10m
</match>


次に、/etc/init.d/td-agent の最初の方に、以下のようにtmplをホスト名で書き換える処理を入れます。
#!/bin/bash
#
# /etc/rc.d/init.d/td-agent
#
# chkconfig: - 80 20
# description: td-agent
# processname: td-agent
# pidfile: /var/run/td-agent/td-agent.pid
#
### BEGIN INIT INFO
# Provides:          td-agent
# Default-Stop:      0 1 6
# Required-Start:    $local_fs
# Required-Stop:     $local_fs
# Short-Description: td-agent's init script
# Description:       td-agent is a data collector
### END INIT INFO


# Source function library.
. /etc/init.d/functions

sed -e "s/\${hostname}/`hostname`/g" /etc/td-agent/td-agent.conf.tmpl > /etc/td-agent/td-agent.conf


…


そして、td-agentとhttpdを起動時ONにします。
# chkconfig httpd on
# chkconfig td-agent on


ここまでやったら、/var/www/html に適当なヘルスチェックファイルを置いて、AMIにかためます。
その後、何台か起動し、ELBにぶら下げます。

いくつかのURLでアクセスをして、ログの実績を作っておきます。

しばらくすると、S3のmemorycraft-logバケットに各ホストのログが溜まってきます。




データは以下のようなフォーマットになります。

日付<タブ文字>fluentタグ<タブ文字>access_logをJSON文字列化したもの

2012-12-21T04:22:02+09:00     apache.access     {"host":"10.158.169.28","user":"-","method":"GET","path":"/favicon.ico","code":"404","size":"321","referer":"-","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11"}

2.複数のログファイルを1ファイルにまとめてソートする


EMRをつかいます。
あとで集計も行うので、ここでは手軽にSQLライクな集計ができるHiveを利用してみます。
Hiveスクリプトは以下の流れです。

入力データを指定

アクセスログの溜まったmemorycraft-logバケットをテーブルとしてアクセスできるようにします。
実行時に日付指定をするために変数DATEを渡すので、それを使ってfluentdが保存するyyyymmddのフォルダを指定します。
CREATE EXTERNAL TABLE IF NOT EXISTS fluentLog (dt string, tag string, json string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log/logs/${DATE}';


出力データを設定

1ファイルにまとめてソートしたデータの出力先をmemorycraft-log-archiveバケットのarchives/yyyymmddというフォルダで出力するように設定します。このときカラムはfluentdが送る日付部分(dt)と、データのJSON部分の内容(host~agent)にします。また、fluentdのS3出力フォーマットに合わせて、フィールドと改行をそれぞれタブ区切り、改行コードで指定します。
CREATE EXTERNAL TABLE IF NOT EXISTS archiveLog (dt string, host string, user string, method string, path string, code string, size bigint, referer string, agent string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log-archive/archives/${DATE}';


まとめてソート

INSERT ~ SELECT ~
を利用すると、入力ソースから整形して出力先へ出力することができます。
整形するときは LATERAL VIEW json_tuple関数で、JSONをパース分割すると通常のカラムとしてSELECTできます。
また、fluentの日付部分(dt)はJSON外なのでそのままカラムとして扱えます。

そして、日付部分dtが今回の対象日時に限定するように念のため絞っておきます。
アクセスログのフォーマットはyyyy-mm-ddなので、yyyymmddの形にしてから比較します。
${DATE}をyyyy-mm-ddに整形したほうがパフォーマンスはいいと思います。またフォルダ名など全体的にフォーマットをyyyy-mm-ddに統一すればこのような変換は必要ありません。今回は気にせず、yyyymmddで進めます。
INSERT OVERWRITE TABLE archiveLog
SELECT
 dt,host,user,method,path,code,size,referer,agent
FROM
 fluentLog LATERAL VIEW json_tuple(fluentLog.json, 'host', 'user', 'method', 'path', 'code', 'size', 'referer', 'agent') j
 AS host,user,method, path, code, size, referer, agent
WHERE
 regexp_replace(substr(dt, 0, 10), '-', '') = '${DATE}'
ORDER BY dt;

このクエリを実行すると's3://memorycraft-log-archive/archives/yyyymmdd/に日付でソートされたデータが1つのファイルにまとまって出力されることになります。


3.集計をする



出力データを設定

もう一つ、それぞれのURLに何回アクセスがあったかを集計したデータの出力先をmemorycraft-log-archiveバケットのstats/yyyymmddというフォルダに設定します。
CREATE EXTERNAL TABLE IF NOT EXISTS statLog (path string, cnt bigint)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log-archive/stats/${DATE}';


集計

ここではpathでGROUP BYすることで、pathごとのアクセス回数をSELECTすることができます。
INSERT OVERWRITE TABLE statLog
SELECT
 path,COUNT(dt)
FROM
 fluentLog LATERAL VIEW json_tuple(fluentLog.json, 'host', 'user', 'method', 'path', 'code', 'size', 'referer', 'agent') j
 AS host,user,method, path, code, size, referer, agent
WHERE
 regexp_replace(substr(dt, 0, 10), '-', '') = '${DATE}'
GROUP BY path
ORDER BY path;


このクエリを実行すると's3://memorycraft-log-archive/stats/yyyymmdd/にpathとそのpathへのアクセス回数のデータが1つのファイルにまとまって出力されることになります。

ここまでのクエリをすべて1つのファイルにまとめて、whatislog.qという名前で、s3://memorycraft-hive/にアップロードします。hiveはこのスクリプトをつかってジョブを実行します。

whatislog.q
CREATE EXTERNAL TABLE IF NOT EXISTS fluentLog (dt string, tag string, json string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log/logs/${DATE}';

CREATE EXTERNAL TABLE IF NOT EXISTS archiveLog (dt string, host string, user string, method string, path string, code string, size bigint, referer string, agent string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log-archive/archives/${DATE}';

INSERT OVERWRITE TABLE archiveLog
SELECT
 dt,host,user,method,path,code,size,referer,agent
FROM
 fluentLog LATERAL VIEW json_tuple(fluentLog.json, 'host', 'user', 'method', 'path', 'code', 'size', 'referer', 'agent') j
 AS host,user,method, path, code, size, referer, agent
WHERE
 regexp_replace(substr(dt, 0, 10), '-', '') = '${DATE}'
ORDER BY dt;


CREATE EXTERNAL TABLE IF NOT EXISTS statLog (path string, cnt bigint)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
LOCATION 's3://memorycraft-log-archive/stats/${DATE}';

INSERT OVERWRITE TABLE statLog
SELECT
 path,COUNT(dt)
FROM
 fluentLog LATERAL VIEW json_tuple(fluentLog.json, 'host', 'user', 'method', 'path', 'code', 'size', 'referer', 'agent') j
 AS host,user,method, path, code, size, referer, agent
WHERE
 regexp_replace(substr(dt, 0, 10), '-', '') = '${DATE}'
GROUP BY path
ORDER BY path;



次に、ローカルの操作端末にelastic map reduceのコマンドラインインターフェースをインストールします。
# wget http://elasticmapreduce.s3.amazonaws.com/elastic-mapreduce-ruby.zip
# unzip elastic-mapreduce-ruby.zip
# yum install ruby


設定情報をcredentials.jsonに記載して保存します。
# cat credential.json
{
"access_id":"xxxxxxxxxxxxxxxxxx",
"private_key":"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"keypair":"memorycraft",
"key-pair-file":"./memorycraft.pem",
"log_uri":"s3://memorycraft-hive-log/",
"region":"ap-northeast-1"
}



そして、以下のようにhiveにDATE引数を渡してコマンドを実行し、ジョブを開始します。
# SCRIPT=s3://memorycraft-hive
# ./elastic-mapreduce --create --name "What is Log" --num-instances 2 --master-instance-type m1.small --slave-instance-type m1.small  --hive-script --arg $SCRIPT/whatislog.q --args -d,DATE='20121221'
Created job flow j-1WQTYOTXBCNXC


するとAWSコンソールのElasticMapReduceにジョブフローが追加され、Debugダイアログを見ると実行中である旨の表示を確認できます。




処理が終わるとフローが完了表示になります。




S3の出力先ディレクトリをみると、ファイルが出力されています。




まとめてソートの出力ファイルをダウンロードしてみると、ちゃんとまとめてソートされていることがわかります。
2012-12-21T04:18:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:19:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:19:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:19:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:20:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:20:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:20:22+09:00     10.158.169.28     -     GET     /     304     \N     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:22+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:25+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:25+09:00     10.158.169.28     -     GET     /assets/img/log.png     404     328     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:20:38+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:38+09:00     10.158.169.28     -     GET     /assets/img/title.png     404     330     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:20:41+09:00     10.158.169.28     -     GET     /assets/img/title1.png     404     331     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:41+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:43+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:43+09:00     10.158.169.28     -     GET     /assets/img/title2.png     404     331     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:49+09:00     10.158.169.28     -     GET     /assets/img/loading.gif     404     332     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:49+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:53+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:53+09:00     10.158.169.28     -     GET     /assets/js/client.js     404     329     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:57+09:00     10.158.169.28     -     GET     /assets/js/jquery.min.js     404     333     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:20:57+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:01+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:01+09:00     10.158.169.28     -     GET     /assets/css/bootstrap.min.css     404     338     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:03+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:21:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:21:09+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:12+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:12+09:00     10.158.169.28     -     GET     /test     404     314     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:15+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:15+09:00     10.158.169.28     -     GET     /singin     404     316     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:22+09:00     10.158.169.28     -     GET     /signup     404     316     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:22+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:26+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:37+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:37+09:00     10.158.169.28     -     GET     /download/test.zip     404     327     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:21:38+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:21:40+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:58+09:00     10.158.169.28     -     GET     /assets/img/logo.png     404     329     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:21:58+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:22:02+09:00     10.158.169.28     -     GET     /favicon.ico     404     321     -     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
2012-12-21T04:22:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0
2012-12-21T04:22:08+09:00     10.158.169.28     -     GET     /index.html     200     5     -     ELB-HealthChecker/1.0


また、集計の出力ファイルをダウンロードしてみると、以下のようにURLと回数が表示されていることがわかります。
/     1
/assets/css/bootstrap.min.css     1
/assets/img/loading.gif     1
/assets/img/log.png     1
/assets/img/logo.png     1
/assets/img/title.png     1
/assets/img/title1.png     1
/assets/img/title2.png     1
/assets/js/client.js     1
/assets/js/jquery.min.js     1
/download/test.zip     1
/favicon.ico     19
/index.html     14
/signup     1
/singin     1
/test     1



マージ、集計ともうまくいったようです。

また、Hiveはスクリプトをアップするのではなく、EMRのマスタノードからHiveコンソールに入ってクエリを実行することもできるので、お客さんからイレギュラーな集計処理を頼まれた時も手軽に集計ができます。

4.古いログのバックアップをとり、削除する


次はfluentから送られてきて溜まったログをS3からGlaceirに送る設定をします。
Glaceirは入出力にかなり時間がかかるものの、S3よりも更に安価に大容量のデータを保存することができます。
削除したくないけど、使用はしばらくしないようなログの保存先に向いています。

今回の場合はmemorycraft-logのデータで、集計が終わって期限が1週間をすぎたものをGlaceirにアーカイブするように設定してみます。
AWSコンソールのS3バケットプロパティで、LifeCycleセクションから「Add Rule」ボタンを押します。





ダイアログが表示されるので、ルールに名前をつけて、「Apply to Entire Bucket」にチェックを入れます。
もしこのバケットが他のデータも含んでいたり、アーカイブに条件をつけたい場合は、チェックを入れずに対象としたいファイル名のプリフィクスを入力します。




下部の「Add Transition」をクリックし、アーカイブされるまでの日数を入力します。ここでは7を入れます。
ファイルの作成日時から7日たつとアーカイブされる設定です。
もし、日付を指定したいのであれば、「Time Period Format」をDateのほうにチェックを入れて日付を入力します。

OKを押して設定完了です。

これで、不要なファイルはS3からGlaceirに送られるようになります。
といっても、このブログを書きながらやったので、まだGlacierに送られないので結果は追って観察してみたいと思います。

こんな感じで、AWS内でログのライフサイクルをひと通り眺めてみました。
もちろんS3やCloudfrontのログにも応用できますし、EC2内のログのローテーションや自動削除、EMRによる更に複雑な集計など、いろいろなバリエーションが考えられます。


EC2インスタンスの外側でこれだけのことができるのであればとても気が楽ですね。便利だー。

2012年11月7日水曜日

Fluentdってなんじゃ?(Mongo&Node編 ブラウザでtail)

前回までで、fluentdを利用してApacheのログをmongoDBに保存することができました。

構成はこのようになっていました。
  +----------------------------+   +------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16)|
  |----------------------------|   |------------------------|
  |          fluentd           |   |                        |
  |                            |   |                        |
  | +---------+     +--------+ |   |      +----------+      |
  | | input   |     | output | |   |      |  fluent  |      |
  | |---------|+--> |--------| +--------->|----------|      |
  | |  tail   |     |  mongo | |   |      |   test   |      |
  | +---------+     +--------+ |   |      +----------+      |
  +----------------------------+   +------------------------+

今回はnode.jsを使用して、mongoDBに保存されたログをブラウザでリアルタイムにtailしてみます。
構成は以下のとおりです。
  +----------------------------+   +-------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16) |
  |----------------------------|   |-------------------------|
  |          fluentd           |   |         mongod          |
  |                            |   |                         |
  | +---------+     +--------+ |   |      +----------+       |
  | | input   |     | output | |   |      |  fluent  |       |
  | |---------|+--> |--------|+---------->|----------|       |
  | |  tail   |     |  mongo | |   |      |   logs   |       |
  | +---------+     +--------+ |   |      +-----+----+       |
  +----------------------------+   +------------|------------+
                                                |
                                 +--------------+
                                 | +-------------------------+
                                 | | node server(10.0.0.100) |
                                 | |-------------------------|
                                 | |         node.js         |
                                 | |                         |
                                 | |      +-----------+      |
                                 +------->| mongoose  |      |
                                   |      |-----------|      |
                                   |      | socket.io +----------> Brower
                                   |      +-----------+      |
                                   +-------------------------+


今回、web serverとmongo serverは、ほとんど前回の設定のままですが、
新規nodeサーバーでmongodbにアクセスするmongooseはデフォルトで複数系の名前のコレクションにアクセスするようになっています。
そこで、webサーバーのtd-agentのmongo outputプラグインの書き出し先をlogsという名前に変更しておきます。
また、リアルタイムに表示させたいのでflush_intervalを0sに設定しておきます。
<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access="apache.access">
  type mongo
  flush_interval 0s
  database fluent
  collection logs

  host 10.0.0.16
  user memorycraft
  password *******
</match>


それでは、nodeサーバーを設定します。
新規にEC2インスタンスをたちあげてnodeとhttpdをインストールします。
# yum install httpd -y
# /etc/init.d/httpd start

# yum install -y wget
# cd /usr/local/src/
# wget http://nodejs.org/dist/v0.8.14/node-v0.8.14.tar.gz
# tar xzvf node-v0.8.14.tar.gz 
# cd node-v0.8.14
# ./configure
# make
# make install


次にnpmをインストールしてからsocket.io, log, forever, mongooseをnpmインストールします。
# curl https://npmjs.org/install.sh | sh
# npm install -g socket.io
# npm install -g forever


nodeを動かすためのユーザーを作成します
# useradd appadmin
# passwd appadmin
# chmod 755 /home/appadmin
# su - appadmin


ノードモジュールへのパスを通すために環境変数を設定します。
$ vi ~/.bash_profile 
$ source ~/.bash_profile 
---
export NODE_PATH=/usr/local/lib/node_modules
---

$ source ~/.bash_profile


ここで、nodeサーバーとHTML用のコンテンツ置き場を用意します。
ディレクトリ構成は以下のとおりです。
cd ~/nodetest
tree
.
|-- node
|   |-- logs
|   |   `-- app.log
|   `-- server.js (nodeサーバー)
`-- public
    |-- assets
    |   |-- css
    |   |-- img
    |   `-- js
    |       `-- client.js (nodeクライアント)
    `-- index.html (tail用の画面)


また、publicをhttpdのDocumentRootにするため、以下のように設定します。
$ su -
# cd /var/www
# mv html html.org
# ln -s /home/appadmin/nodetest/public html
# vi /etc/httpd/conf/httpd/conf
---
~略~
<directory html="html" var="var" www="www">
    Options FollowSymLinks
    AllowOverride All
~略~
---
</directory>

# /etc/init.d/httpd restart


そして、httpdとnodeに接続させるため、SecurityGroupを以下のように設定します。



次にサーバーとコンテンツを実装してみます。 それぞれの内容は以下の通りです。

node/server.js
//3001番でlistenします
var server = require('http').createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('server connected');
});
server.listen(3001);

//logsコレクションのスキーマ定義をしておきます。
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AccessSchema = new Schema({
    host: String,
    user: String,
    method: String,
    path: String,
    code: Number,
    size: Number,
    referer: String,
    agent: String,
    time : Date
});
mongoose.model('log', AccessSchema);

//fluentデータベースに接続します。
mongoose.connect('mongodb://10.0.0.16/fluent');
var Access = mongoose.model('log');

//socket.ioを起動します。
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
    /**
     * クライアント接続時に、初期データとして、
     * 保存されているlogsコレクションをすべてemitします。
     */
    Access.find({}).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            socket.emit('init', docs);
        }
    });
});

/** 
 * 1.5秒おきに最終取得ログの日時以降のログを取得して
 * 全員にブロードキャストし、最終日時を保存します。
 */
var last = new Date();
setInterval(function(){
    Access.find({}).where('time').gt(last).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            for(var i=0; i<docs.length; i++){
                if(last < docs[i].time){
                    last = docs[i].time;
                }
            }
            io.sockets.emit('log', docs);
        }
    });
}, 1500);


public/assets/js/client.js
$(function(){
  //接続します
  var socket = io.connect('http://' + location.hostname + ':3001/');
  socket.on('connect', function() {
   /**
     * 接続成功時に初期データを時系列降順で表示させます
     */
    socket.on('init', function(data){
        $("#log").empty();
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

    /**
     * 随時送られてくるデータは一番上に表示させます
     */
    socket.on('log', function(data){
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

  });
});


public/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
    function load(){
        $.getScript("assets/js/client.js");
    }
    $.getScript("http://" + location.hostname + ":3001/socket.io/socket.io.js", function(){
        load();
    });
});
</script>
<body>
    <div id="content">
        <h1>ログ</h1>
        <div id="log" width="500px" height="100%">
        </div>
    </div>
</body>
</html>


早速動かしてみます。
$ cd ~/nodetest/node
$ forever start server.js

これでnodeサーバーが起動しました
それでは、ブラウザを見てみます。



初期ログが表示されています。うまく動いているようです。
そこで、fluentdのtail対象サーバーであるwebサーバーへアクセスをしてみます。
わかりやすくするために、何度かリロードしておきます。


そして、さきほどのnodeサーバーの画面をみると、、、


おお!ログが追加で現れました!
これであるサーバーのログデータを別のサーバーでリアルタイムに表示させることができました。

以上です。

2012年11月5日月曜日

Fluentdってなんじゃ?(MongoDB編)


今回は、fluentdを利用して、Apacheのログを別サーバーのMongoDBに保存してみようと思います。

前回のlogサーバー(10.0.0.16)にmongoDBをホストし、webサーバー(10.0.0.8)のtd-agentから直接mongoに保存します。

まずはlogサーバーにmongoDBをインストールします。
yumリポジトリとして以下を追加してからyumインストールを行います。
# vi /etc/yum.repos.d/10gen.repo]
---
[10gen]
name=10gen Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/i686
gpgcheck=0
---

# yum install mongo-10gen mongo-10gen-server -y

mongoのインストールは完了しました。
起動しておきます。
# /etc/init.d/mongod start


続いてwebサーバー側にmongoプラグインを設定します。
mongo Output Pluginはビルトインではなく、td-agent用のrubygemでインストールして使用するため、以下のようにインストールを行います。
# /usr/lib/fluent/ruby/bin/fluent-gem install fluent-plugin-mongo
Fetching: fluent-plugin-mongo-0.6.10.gem (100%)
Successfully installed fluent-plugin-mongo-0.6.10
1 gem installed
Installing ri documentation for fluent-plugin-mongo-0.6.10...
Installing RDoc documentation for fluent-plugin-mongo-0.6.10...


設定ファイルには、以下のように出力先をtype mongoを設定します。これは、

source
 apacheフォーマットの/var/log/httpd/access_logファイルをtailし、apache.accessというタグをつける

match
  apache.accessタグの入力があったら、10.0.0.16のmongoDBのfluentデータベースへmemorycraftユーザーで接続し、hogeコレクションとしてデータを登録する

という意味になります。
# vim /etc/td-agent/td-agent.conf

---

<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type mongo
  database fluent
  collection hoge
  host 10.0.0.16
  user memorycraft
  password ******
</match>

---


この時点ではwebサーバーからlogサーバーへmongoの接続ポートが閉じているので、まだ接続できません。
EC2で、mongoのデフォルトポートの27017番を開放します。




ここで一度webサーバー側のtd-agentを再起動しておきます。
# /etc/init.d/td-agent restart


この時点でmongoに接続すると、fluentデータベースはできているようです。
ここで、認証用にユーザーを作成します。
# mongo
MongoDB shell version: 2.2.1
connecting to: test

> use fluent
switched to db fluent
> db.addUser("memorycraft", "*****")
{
 "_id" : ObjectId("5096e46f1b4da85be896fb1e"),
 "user" : "memorycraft",
 "readOnly" : false,
 "pwd" : "2922d9ce24a959b1f359f7ba2d044017"
}

これで認証が設定されたので、webサーバー側のtd-agentを改めて再起動します。
# /etc/init.d/td-agent restart


ここで、mongoシェルに接続してみるとfluentというDBにhogeというコレクションができていることがわかります。
# mongo
MongoDB shell version: 2.2.1
connecting to: test
> use fluent
switched to db fluent
> show collections
system.indexes
system.users
hoge


中身を見てみると、、、
> db.hoge.find()
{ "_id" : ObjectId("5096e4e5a3441113d4000001"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/", "code" : "403", "size" : "5039", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:09Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000002"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/icons/poweredby.png", "code" : "304", "size" : "-", "referer" : "http://54.249.23.246/", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000003"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/icons/apache_pb.gif", "code" : "304", "size" : "-", "referer" : "http://54.249.23.246/", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000004"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/favicon.ico", "code" : "404", "size" : "288", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }



おおー。データが入っています。
たとえば、pathが"/"のデータのみのログも簡単に抽出できるようになりました!
> db.hoge.find({path:"/"})
{ "_id" : ObjectId("5096e4e5a3441113d4000001"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/", "code" : "403", "size" : "5039", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:09Z") }


このようにfluentdを用いると、ログの集約や集計がデイリーやマンスリーではなく準リアルタイムで行えるようになります。とても便利ですね。

今回はここまで。

Fluentdってなんじゃ?(Apache編)

前回は同じサーバー内でコマンドからファイル出力を行いました。
内容としては以下のような流れです。

  +--------------------------------------+
  | server                               |
  +--------------------------------------+
  |                fluentd               |
  |                                      |
  | +-------------+       +------------+ |
  | |    input    |       |   output   | |
  | |-------------| +---> |------------| |
  | |command(tcp) |       |    file    | |
  | +-------------+       +------------+ |
  +--------------------------------------+

今回はApacheのログを別のサーバーにファイル転送してみたいと思います。
以下のようなイメージです。
  +--------------------------------------+   +--------------------------------------+
  | web server (10.0.0.8)                |   | log server (10.0.0.16)               |
  +--------------------------------------+   |--------------------------------------|
  |                fluentd               |   |               fluentd                |
  |                                      |   |                                      |
  | +-------------+       +------------+ |   | +------------+       +-------------+ |
  | |    input    |       |   output   | |   | |   input    |       |    output   | |
  | |-------------| +---> |------------| +---> |------------| +---> |-------------| |
  | |    tail     |       |    tcp     | |   | |   tcp      |       |    file     | |
  | +-------------+       +------------+ |   | +------------+       +-------------+ |
  +--------------------------------------+   +--------------------------------------+


まず、送信側のwebサーバーでは、以下のようにアパッチのログをapache.accessというタグでtailし、tcp送信するように設定します。

source

 ログのフォーマット指定でapacheを指定することでapacheのログ構造をJSON化できるようになります。
そのほかにはsyslogのフォーマットがプリセットで指定できますが、その他アプリのログなどは名前付きキャプチャの正規表現で指定することにより、任意のプロパティ名でJSON化できます。

match

 matchディレクティブで設定するforwardは、Bufferd Outputプラグインというタイプで、デフォルトではメモリにデータをバッファし60秒でflushします。
つまり60秒に一回まとめて送信を行います。今回はわかりやすくリアルタイムで行うよう、flush_intervalを0sとし、0秒でflushするように設定してみます。
また、serverディレクティブで送信先のサーバーのIPを指定します。 特に指定しない場合、ポートは24224番が使用されます。

# vim /etc/td-agent/td-agent.conf
---
<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type forward
  flush_interval 0s
  <server>
    host 10.0.0.16
  </server>
</match>
---

# /etc/init.d/td-agent restart


受信側のサーバーにもtd-agentをインストールし、以下のように、tcpを受信しファイルに出力するように設定します。

source
 webサーバーのtcpから受信するためforward Input Pluginを指定します。

match
 apache.accessタグのデータを/var/log/fluent/apache_access_log***に書き出します。

# mkdir /var/log/fluent
# chown td-agent:td-agent /var/log/fluent
# vim /etc/td-agent/td-agent.conf
---
<source>
  type forward  
</source>

<match apache.access>
  type file
  path /var/log/fluent/apache_access_log
</match>
---

# /etc/init.d/td-agent start

ここで、webサーバーとlogサーバーの間にFWなどがある場合、fluentdのポートを開放する必要があります。 今回はEC2を使用しているので、Security Groupの設定でTCPの24224番を開放します。また、データ転送にはTCPを使用しますが、死活監視にはUDPが使われるので、両方を開放しておきます。



webサーバーのコンテンツにブラウザからアクセスしてみます。



すると、logサーバー側の/var/log/fluent/apache_access_log.xxxというファイルにJSONが記録されているのがわかります。
tail -1000f /var/log/fluent/apache_access_log.20121105.b4cdb099dbbaa1e42
---
2012-11-05T05:20:11+09:00 apache.access {"host":"219.117.233.241","user":"-","method":"GET","path":"/","code":"403","size":"5039","referer":"-","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17"}
2012-11-05T05:20:11+09:00 apache.access {"host":"219.117.233.241","user":"-","method":"GET","path":"/icons/poweredby.png","code":"304","size":"-","referer":"http://54.249.23.246/","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17"}
2012-11-05T05:20:11+09:00 apache.access {"host":"219.117.233.241","user":"-","method":"GET","path":"/icons/apache_pb.gif","code":"304","size":"-","referer":"http://54.249.23.246/","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17"}
2012-11-05T05:20:11+09:00 apache.access {"host":"219.117.233.241","user":"-","method":"GET","path":"/favicon.ico","code":"404","size":"288","referer":"-","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17"}

これで、リモートのサーバーにログを送ることができました。 また、このようにApacheのログがオブジェクト化されることで、後々集計集約しやすくなりました。

次回は、ログをDBに送り、抽出してみたいと思います。
以上です。

Fluentdってなんじゃ?

今回はfluentdについて触れてみたいと思います。

普段サーバーの運用をしていて、規模が大きくなりサーバーが分散されるとWebサーバーやアプリケーションのログが各サーバーに散在することになり、ユーザーからの問い合わせや集計などのためにログをかき集めたりする必要が出てきます。
fluentdはイベントログをJSONフォーマットで転送して一箇所に集約するためのツールです。

集計などを行う場合、Apacheなどの生ログはテキストデータなのですが、fluentdを経由しJSON化することで、ログのデータをオブジェクトとして扱うことが出来るので、DBに入れてフィルタをかけて集計なども簡単に行えます。

また、fluentdは入力元となるログの種類や出力先の指定をプラグイン形式で様々なフォーマットに対応して、それぞれを組み合わせることで数多くの用途に答えることができます。
例えば以下のような入出力ができます。

入力元:
  • fluent apiを利用してアプリケーションから直接出力
  • 既存ログのtail
  • TCPソケットからのデータ
  • コマンドの実行結果
出力先:
  • ファイルに書き出み
  • 各種DBへ保存
  • SQSでキューにメッセージ送信
  • S3へ保存
  • Growlで通知
  • メール送信
  • Twitterへ投稿

こうみるとPlaggerを思い出しますね。

fluentdはruby環境が必要ですが、TreasureDataというデータウェアハウスがtd-agentというruby環境込みのstableパッケージを提供しています。td-agentを使用すると、ruby環境がない場合や既存のruby環境を壊したくない場合にとても有用です。今回はtd-agentを利用してみます。

まずはtd-agentをインストールします。
TreasureDataのyumリポジトリを登録してから、yumでインストールを行います。
# vi /etc/yum.repos.d/td.repo
---
[treasuredata]
name=TreasureData
baseurl=http://packages.treasure-data.com/redhat/$basearch
gpgcheck=0

---

# yum install td-agent -y
インストールはこれで完了です。

次に、設定ファイルを見てみます。

設定ファイルはシンプルで、source定義で指定した入力を、マッチしたmatch定義の設定で出力します。
まずは、flluentdのQuickStartにあるように付属のflluent-catを使用して、擬似的なTCPからの入力をローカルのファイルに出力するように設定します。
#vi /etc/td-agent/td-agent.conf

---
<source>
  type forward
  tag test
</source>

<match test>
  type file
  path /var/log/fluent/test
</match>
---

これは、

source
 forward Input Pluginを使用してtcpからの入力にtestというタグをつける

match test 
 file Output Pluginを使用して、タグが「test」にマッチした入力があれば、/var/log/fluent/test*** というファイルに受け取ったデータを出力する

という意味の設定になります。 設定を記述したら起動します。
#/etc/init.d/td-agent start
そしてfluent-catで以下のようにコマンドからJSONを送ってみます。
その際、引数として、タグとして設定した「test」を渡します。
# echo '{"msg":"Hello World!"}' | /usr/lib/fluent/ruby/bin/fluent-cat test
すると、以下のようなファイルが生成され、時間+タグ+JSONの形式で出力されていることがわかります。
# tail -1000f /var/log/fluent/test.20121105.b4cdae0705726cb91
2012-11-05T01:40:32+09:00 test {"msg":"Hello World!"}


ここまででは、ただファイルに入力したのと変わらないので、便利さがわかりづらいと思います。
次回はログをリモートのサーバーに転送してみたいと思います。
以上です。