ほわいとぼーど

ぷろぐらまのメモ帳

fabricのroleで試行錯誤した話

普段は構築はChefで行うのですが、
コマンド実行もサポートしたい要件があったのでfabricを試してみました。
ただ、自分のやりたいことに対してroleの挙動が難しかったので
試行錯誤の様子をメモとして残します。
fabric初心者なのでこれが良い方法かどうかはわかりません。

どんな事がしたかったか?

複数の構成要素(client、server1、server2、、、)に1コマンド実行したい。
ただし、手元のVMで動作確認する場合に一部の構成要素をひとまとめにすることがある。
(server1、server2をserverとして扱い両方の機能を1台に押し込める)

環境

今回は説明のために実際よりは簡略化し、種類としては「client」「server1」「server2」
の3つとし、更に「server1」「server2」両方の機能をもつものを「server」とします。
実施内容としては、

  • file_common.binを全台に配布する。
  • 各機能に合わせたファイルをそれぞれに配布する。(file_client.bin, file_server1.bin, file_server2.bin)

というわけで書いてみます。


[deploy1.py]

from fabric.api import env, put
from fabric.decorators import task, roles

env.user = "vagrant"
env.password = "vagrant"

@task
def env_full():
    env.roledefs = {
       'client'  : ['192.168.100.171'],
       'server1' : ['192.168.100.172'],
       'server2' : ['192.168.100.173'],
    }

@task
def env_small():
    env.roledefs = {
       'server' : ['192.168.100.170'],
       'client' : ['192.168.100.171'],
    }

@task
def deploy():
    _deploy_common()
    _deploy_client()
    _deploy_server1()
    _deploy_server2()

@roles('server', 'client', 'server1', 'server2')
def _deploy_common():
    put('resources/file_common.bin', '/tmp/file_common.bin')

@roles('client')
def _deploy_client():
    put('resources/file_client.bin', '/tmp/file_client.bin')

@roles('server', 'server1')
def _deploy_server1():
    put('resources/file_server1.bin', '/tmp/file_server1.bin')

@roles('server', 'server2')
def _deploy_server2():
    put('resources/file_server2.bin', '/tmp/file_server2.bin')

そして実行

vagrant@fabric:$ fab -f deploy1.py env_small deploy                                                     
No hosts found. Please specify (single) host string for connection: 

roleを設定したつもりでしたが、実行対象が無いと怒られました。
@rolesをつけただけでは実行対象として認識されないようです。
自分が試行錯誤した結果、「taskとして実行される」「executeを使って実行される」
ないと@rolesが認識されないようです。

excuteを使ってdeployメソッドを書き換えます。


[deploy2.py]

from fabric.api import env, put, execute
...中略

@task
def deploy():
    execute(_deploy_common)
    execute(_deploy_client)
    execute(_deploy_server1)
    execute(_deploy_server2)

...中略
vagrant@fabric:$ fab -f deploy2.py env_small deploy

Fatal error: The following specified roles do not exist:
    server1
    server2

Aborting.

vagrant@fabric:$ fab -f deploy2.py env_full deploy

Fatal error: The following specified roles do not exist:
    server

Aborting.

今度はroleの一部がないと言われます。
両方実行した結果を載せたので何となく想像がつくと思いますが、
@rolesで記載したものが存在しないと言われているようです。
自分は@rolesをフィルタリング的な意味合として書きましたが、
単純に実行対象を環境変数から取得して実行するだけの仕組みということかも?

仕方が無いので@rolesを付けるメソッドは分けることにします。
構成を色々いじる必要があり、試行錯誤して以下になりました。


[deploy3.py]

from fabric.api import env, put, execute
from fabric.decorators import task, roles

env.user = "vagrant"
env.password = "vagrant"

def _env_small():
    env.roledefs = {
       'server' : ['192.168.100.170'],
       'client' : ['192.168.100.171'],
    }

def _env_full():
    env.roledefs = {
       'client'  : ['192.168.100.171'],
       'server1' : ['192.168.100.172'],
       'server2' : ['192.168.100.173'],
    }

@task
def deploy_small():
    _env_small()
    execute(_deploy_common_small)
    execute(_deploy_server)
    execute(_deploy_client)

@task
def deploy_full():
    _env_full()
    execute(_deploy_common_full)
    execute(_deploy_client)
    execute(_deploy_server1)
    execute(_deploy_server2)

@roles('server', 'client')
def _deploy_common_small():
    _deploy_common()

@roles('client', 'server1', 'server2')
def _deploy_common_full():
    _deploy_common()

def _deploy_common():
    put('resources/file_common.bin', '/tmp/file_common.bin')

@roles('client')
def _deploy_client():
    put('resources/file_client.bin', '/tmp/file_client.bin')

@roles('server1')
def _deploy_server1():
    put('resources/file_server1.bin', '/tmp/file_server1.bin')

@roles('server2')
def _deploy_server2():
    put('resources/file_server2.bin', '/tmp/file_server2.bin')

@roles('server')
def _deploy_server():
    _deploy_server1()
    _deploy_server2()

実行部分が@rolesで分かれるのでenv.roledefs定義も
中で呼ぶように変更しました。
_deploy_commonも分ける必要があったのは2重に呼ばないためです。
@runs_onceという1回しか呼ばないためのdecoratorもあるのですが、
これを付けたら本当に全体で1回しか呼ばれませんでした。
やりたいのは各ノードで1回にしたいということです。

vagrant@fabric:$ fab -f deploy3.py deploy_small
[192.168.100.170] Executing task '_deploy_common_small'
[192.168.100.170] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.171] Executing task '_deploy_common_small'
[192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.170] Executing task '_deploy_server'
[192.168.100.170] put: resources/file_server1.bin -> /tmp/file_server1.bin
[192.168.100.170] put: resources/file_server2.bin -> /tmp/file_server2.bin
[192.168.100.171] Executing task '_deploy_client'
[192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin

Done.
Disconnecting from 192.168.100.170... done.
Disconnecting from 192.168.100.171... done.

vagrant@fabric:$ fab -f deploy3.py deploy_full
[192.168.100.171] Executing task '_deploy_common_full'
[192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.172] Executing task '_deploy_common_full'
[192.168.100.172] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.173] Executing task '_deploy_common_full'
[192.168.100.173] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.171] Executing task '_deploy_client'
[192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin
[192.168.100.172] Executing task '_deploy_server1'
[192.168.100.172] put: resources/file_server1.bin -> /tmp/file_server1.bin
[192.168.100.173] Executing task '_deploy_server2'
[192.168.100.173] put: resources/file_server2.bin -> /tmp/file_server2.bin

Done.
Disconnecting from 192.168.100.173... done.
Disconnecting from 192.168.100.171... done.
Disconnecting from 192.168.100.172... done.

うまくいきましたね!
しかし@roles呼び分けるためにラップするだけのメソッドが増えてしまいました。
また、単にputしてるだけなのでわかりづらいのですが、この実行は直列実行です。
異なるノードに対しては並列で実行したいですよね。


並列に扱うには@parallelを付ければいいのですが、
付けたメソッドに対して並列実行したい対象が通る形にしないと並列になりません。
例えば上記のdeploy3.pyだと_deploy_common_full()@parallelを付ければ
「client」「server1」「server2」に対して並列実行されますが、
_deploy_client()_deploy_server1()_deploy_server2()@parallelを付けても、
並列には実行されないのです。
というわけで大改造します。


[deploy4.py]

from fabric.api import env, put, execute
from fabric.decorators import task, roles, parallel

...中略

@task
def deploy_small():
    _env_small()
    execute(_deploy_roles_small)

@task
def deploy_full():
    _env_full()
    execute(_deploy_roles_full)

@roles('server', 'client')
@parallel
def _deploy_roles_small():
    _deploy_roles()

@roles('client', 'server1', 'server2')
@parallel
def _deploy_roles_full():
    _deploy_roles()

def _deploy_roles():
    _deploy_common()
    
    current_role = _get_current_role()
    if current_role == 'server':
        _deploy_server1()
        _deploy_server2()
    elif current_role == 'client':
        _deploy_client()
    elif current_role == 'server1':
        _deploy_server1()
    elif current_role == 'server2':
        _deploy_server2()
    else:
        print "invalid role: {0}".format(current_roles)

def _get_current_role():
    for role in env.roledefs.keys():
        if env.host_string in env.roledefs[role]:
            return role
    return None

def _deploy_common():
    put('resources/file_common.bin', '/tmp/file_common.bin')

def _deploy_client():
    put('resources/file_client.bin', '/tmp/file_client.bin')

def _deploy_server1():
    put('resources/file_server1.bin', '/tmp/file_server1.bin')

def _deploy_server2():
    put('resources/file_server2.bin', '/tmp/file_server2.bin')

並列処理したい処理部をroleを判別して挙動を変えるメソッドに押し込めてます。
判別するためのroleはenv.host_stringで現在実行中のhost対象が取れるので、
それを元にenv.roledefsから逆引きして特定しています。
このやり方はstackoverflowで見つけました。
@rolesの付け替えのためのラップメソッドはどうしても残りますが、
全体的には前よりもわかりやすくなったのではないでしょうか。

では実行してみましょう。

vagrant@fabric:$ fab -f deploy4.py deploy_small
[192.168.100.170] Executing task '_deploy_roles_small'
[192.168.100.171] Executing task '_deploy_roles_small'
[192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.170] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin
[192.168.100.170] put: resources/file_server1.bin -> /tmp/file_server1.bin
[192.168.100.170] put: resources/file_server2.bin -> /tmp/file_server2.bin

Done.

vagrant@fabric:$ fab -f deploy4.py deploy_full
[192.168.100.171] Executing task '_deploy_roles_full'
[192.168.100.172] Executing task '_deploy_roles_full'
[192.168.100.173] Executing task '_deploy_roles_full'
[192.168.100.173] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.173] put: resources/file_server2.bin -> /tmp/file_server2.bin
[192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin
[192.168.100.172] put: resources/file_common.bin -> /tmp/file_common.bin
[192.168.100.172] put: resources/file_server1.bin -> /tmp/file_server1.bin

Done.

できてそうですね。

ということで@roles@parallelと格闘してみたメモでした。
本当は更に個別のノード指定もサポートしたいのですが、そこは徐々にということにします。
しかしメソッド名とかセンス無くて泣きそう。

Chefでapt/dpkg時のファイル更新確認をskipする

[概要]
apt-get install や dpkg -i でインストールする際にdebパッケージに
含まれるファイルを先に配置しておくと出る更新確認メッセージをChefでスキップする方法。

[答え](正解かどうかは別として、、、)
packageのoptionsで「-o Dpkg::Options::='--force-confold'」を指定する。

参考:https://lists.debian.org/debian-user/2011/04/msg01168.html


[説明](どういう時に使うのか)
debパッケージを使ってインストールした場合、インストール完了後にサービス起動までしてしまう。
この振る舞いが正しいのかどうかはLinux情弱たる自分にはよくわかりませんが、
その後に設定ファイルを更新して再起動みたいなChefレシピを書いていました。

しかし設定更新前にはサービス起動したくない場合もあります。
elasticsearchは起動するとcluster nameが同じ仲間を自動的に探してjoinしてしまうので、
設定ファイルのデフォルト値を変更してから起動するようによく注意喚起されています。

起動しないオプションが無いか調べたのですが無かった(見つからなかった)ので、
じゃぁ、先に設定ファイル置いちゃったらうまいこといかないか?と思って
試したらやり方次第ではできそうだったので調べてみました。


例えば、以下のようなChefレシピ(抜粋)を書きました。

directory '/etc/elasticsearch' do
  mode "0755"
end
template '/etc/elasticsearch/elasticsearch.yml' do
  source 'elasticsearch.yml.erb'
end

execute "apt-key" do
  command "wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add -"
end
cookbook_file '/etc/apt/sources.list.d/elasticsearch.list' do
  owner 'root'
  mode '0644'
  source 'elasticsearch.list'
  # see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_apt.html
end
execute "apt-get_update" do
  command "sudo apt-get update"
end
package "elasticsearch" do
  options "-f --force-yes"
  action :upgrade
end

先に設定ファイルを置いてからapt-getしています。
これを実行すると、

  * package[elasticsearch] action upgrade
================================================================================
Error executing action `upgrade` on resource 'package[elasticsearch]'
================================================================================


Chef::Exceptions::Exec
----------------------
apt-get -q -y -f --force-yes install elasticsearch=0.90.10 returned 100, expected 0

こんな感じのエラーが出ます。
エラーメッセージにあるコマンドを実際に実行してみると理由がわかります。

vagrant@guest:~$ sudo apt-get -q -y -f --force-yes install elasticsearch
Reading package lists...
Building dependency tree...
Reading state information...
elasticsearch is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 118 not upgraded.
1 not fully installed or removed.
After this operation, 0 B of additional disk space will be used.
Setting up elasticsearch (0.90.10) ...

Configuration file `/etc/elasticsearch/elasticsearch.yml'
 ==> File on system created by you or by a script.
 ==> File also in package provided by package maintainer.
   What would you like to do about it ?  Your options are:
    Y or I  : install the package maintainer's version
    N or O  : keep your currently-installed version
      D     : show the differences between the versions
      Z     : start a shell to examine the situation
 The default action is to keep your current version.
*** elasticsearch.yml (Y/I/N/O/D/Z) [default=N] ? 

ファイル更新するか確認のプロンプトが表示されて処理が中断されていますね。

これを回避するためにオプションを指定します。

--force-confdef:デフォルト値採用、
--force-confnew:新しいもので更新、
--force-confold:古いもの優先

参考:https://lists.debian.org/debian-user/2011/04/msg01168.html

以下のように書き換えました。

package "elasticsearch" do
  options "-o Dpkg::Options::='--force-confold' -f --force-yes"
  action :upgrade
end

これにより、デフォルトのcluster nameでは一切起動することなく、
自身の設定したcluster nameで最初から起動できるようになりました。

実際にはこの後にplugin install等もあり、結局再起動は必要となるのですが・・・

なお、elasticsearchには公式のChefレシピがあるのですが、
こちらではtarファイルからinstallしていてcluster nameを起動前に変更できるはず。
自分も初期はtarから入れていたのですが、その頃はまだ公式レシピも無くて、
initファイルを自前で用意面倒とかでdeb/rpmパッケージが用意された際に乗り換えました。
Chefレシピは自前で書く派です(誰も聞いてない

以上、あまり活躍はしなさそうな技術メモでした。

2013年振り返り

・技術ブログを始めてみた。
 続く続かない、とかよりはブログ書きたいと思えるような状況を作りたい。

・今年初めて触ったもの
 ・AWS
 ・Vagrant
 ・Chef (Chef-solo, knife-solo, Chef-server)
 ・Serverspec
 ・Cassandra (OpsCenter, Jolokia)
 ・Maven
 ・nginx
 ・fluentd (td-agent, 各種plugin)
 ・Elasticsearch (Kibana2, Kibana3)
 ・GrowthForecast
 ・JMeter
 ・IntelliJ IDEA
 ・Serf
 ・Python
 
 ServerspceやKibanaは他の人にも覚えてもらえた半面、
 全般的に実務に導入までいってないのが不満。
 
・勉強会参加
 ・TDD meetup
 ・JTF
 ・はじめるDevOps
 ・データサイエンティストの始めかた
 ・BIGDATAダッシュボード勉強会
 ・BPstudy
 ・第1回Elasticsearch勉強会
 ・Web&Java
 ・DevOpsDay Tokyo 2013
 ・http2.0勉強会
 ・riak meetup tokyo #3
 ・第2回Elasticaserch勉強会
 ・fluentd casual talks 3
 ・Jenkins勉強会

 土日は家を出るのに気合がいるので平日を選ぶことを学んだ。
 
・Vaio Z購入
Xperia Z購入 (前はXperia acro)

 Sony派なのか?と言われるとそういうわけでもないのだが・・・
 (デスクトップはDellTwoTop、Tstukumo等)
 勉強会行くと、iphone, macが圧倒的に多いので肩身が狭い。

色々細かく書こうと思ったけどキリが無くて終わらなかったので逆にバッサリ
自分に必要なのはポイントを抑える力かもしれない。

Kibana向けFluentd設定例①アクセスログ可視化

どちらかというとFluentdの設定の話なのですが、
Kibana上でデータを扱うことも考慮して、くらいの感じで。

アクセスログの可視化はよくあるやつなので細かい所は飛ばします。

1段目:ログ収集先からの転送設定

<source>
  type     tail
  path     /path/to/apache_access.log
  format   /(?<message>.*)/
  pos_file /var/log/td-agent/apache_access.pos
  tag      apache.access.HOSTNAME
</source>

<match **>
  type           forward
  flush_interval 1s
  <server>
    host         FORWARD_HOST
  </server>
</match>

以前の記事ではin_tailにパースを書いていましたが、
パラメータが増減するのに毎回対応したり、パースするための負荷がかかることを考えると、
ログ収集ホスト上のin_tail正規表現をゴリゴリ書くのはあまり得策じゃない気がしています。
そこで1行を1メッセージとして送るような設定にしています。
fluentdが0.10.39以降であればformat noneとすることで同様のことが出来ます。
収集ログに日付が付与する場合はin_tailの変わりにfluent-plugin-tail-exを使うといいですね。


2段目:ログ受信側パース

<source>
  type forward
</source>

<match apache.access.**>
  type        parser
  add_prefix  parsed
  format      /(正規表現)/
  time_format %d/%b/%Y:%H:%M:%S %z
  key_name    message
</match>

ここからはログ解析サーバ上です。
forwardされたイベントをfluent-plugin-parserでパースします。
key_nameには解析したいフィールド名、ここではmessageになります。
formatには正規表現を書きますが、ログフォーマット側でLTSVにしておけば
format ltsvするだけでよくて楽ですね。


3段目:データ加工

<match parsed.apache.access.**>
  type         record_reformer
  output_tag   reformed.${tags[1]}.${tags[2]}
  
  ip           ${forwardedfor[/([^,]*),*/,1]}
  method       ${req[/^([^ ]*) .*$/,1]}
  uri          ${req[/^[^ ]* ([^ ]*) .*$/,1]}
  sessionId    ${req[/^[^ ]* \/path\/to\/api\?sessinId\=(.*?)(\&param\=[^ ]*)? .*$/,1]}
  log_host     ${tags[3]}
</match>

2段目の時点で生のデータを扱う分には十分なのですが、
時にはデータ加工したい場合があり、fluent-plugin-record-reformerが便利です。
整形したデータを生のレコードに+αしてくれるのが良いです。
しかも複数項目書けます。
例えば「req (referer情報)」に「GET /path/to/api?sessionId=10000001&param=1 HTTP/1.0」
という情報が入っているとして、1つ目をmethod、2つ目の部分をuriとして取り出しています。
あるいはuri全体ではなく、getパラメータに入っているsessionIdを取り出したりします。
またforwardedforにはproxyを通すとカンマ区切りで複数のアドレスが入っているので、
1番先頭だけを取り出して続くgeoipの判定に使います。
整形は${フィールド名[/(正規表現)/,n]}のように書いて取り出したい部分をグループ化して
nで何番目のグループを取り出したいかを指定します。
この辺りはruby正規表現のとり扱いになるので、
rubyのコンソールとかで少し試してみればすぐわかると思います。

log_hostは以前の記事fluent-plugin-elasticsearch上でtag_key @log_nameで行っていた
収集元ホストの登録をここで行うように変更したものです。
fluent-plugin-elasticsearchtag_keyだとtagをかなりうまいこと編集する必要がありましたが、
tags使うと簡単に必要な部分だけ取り出せますね。
${tags[3]}はtagをカンマで分割した4番目を取得しています。
なお、fluent-plugin-record-reformer 0.1.1からはfluent-plugin-forestと表記を合わせて
tag_partsが使えるようになります。

今回の記事のポイントはほぼ3段目に集約されます。
methodやuriはログフォーマットで直接指定すれば加工しなくても済みますが、
既に運用されているような場合に出力元に変更を加えるのが簡単じゃない場合も多いし、
そういう場合に受け取った先で簡単に加工できるという例です。


4段目:

<match reformed.**>
  type                    geoip
  geoip_lookup_key        ip
  enable_key_country_code geoip_country
  add_tag_prefix          es.
</match>

fluent-plugin-geoip用の設定。
pluginとは別にライブラリのインストールが必要なので注意してください。

ここまで見てきてplugin毎にtagの扱い方が異なるので若干混乱しますね。


5段目:

<match es.reformed.**>
  type              forest
  subtype           elasticsearch
  remove_prefix     es.reformed
  <template>
    type_name       ${tag}
    host            localhost
    port            9200
    logstash_format true
    logstash_prefix logstash
    flush_interval  10s
  </template>
</match>

fluent-plugin-elasticsearchはplaceholderに対応してないのでfluent-plugin-forest推奨。
fluent-plugin-record-reformerでのoutput_tagとの組み合わせにより
type_nameに入る値は今回の例では「apache.access」となります。
以前はtype_name、host名なんかを登録するのにtag操作が結構大変でしたが、
tagのplaceholderが便利になったお陰でかなり楽になりました。


fluentdではこんな感じでログ出力をいい感じに加工してデータストアに投入できます。
Kibana関係ないじゃないかって言われそうですが、Kibanaで表示する内容を考えた結果、
fluent-plugin-record-reformerで途中で加工するのが便利っていう話です。
Kibanaで解析するときにユーザIDやセッションIDみたいなのをfacet集計したい事例はよくあると思うので、
切り出しておいてmappingをnot_analyzedで用意するとランキング集計しやすいですね。
Kibana上ではtermsパネルで対象項目を指定するだけで表示できます。
デフォルトで5項目になってるけど数字を増やせばもっと表示可能。
(1000くらいにはしたことある)

f:id:a3no:20131215090919j:plain
参考例。本当はもっとランキングぽいの出したかったけど手元に出せるデータがなく。


3つ例を書くつもりだったけど、調査修正して動確してたらハマって時間かかってしまった。
record_reformerは前に設定書いた時点ではtag_parts使えてたんだけど、
手元で実施したら0.1.1ではなく0.1.0が入ってしまって使えなかったり、
Elasticsearch-1.0.0.Beta2を使ったら0.90.7で動いてたmapping templateが動かなかった。
記事は書いたものの色々と調査するものが増えてしまった・・・

Fluentd Casual Talks #3[2013/12/13(Fri.)]に参加してきました。

勉強会ブログたまりまくってるので今回は可及的速やかにさっくり書く。
資料やお名前は後で補完予定発表者多いので網羅できたか不安

http://togetter.com/li/602421

①@tagomorisさん 「fluent-plugin-norikra」 
・StreamにSQLクエリを書ける
・定間隔実行クエリ(タイムバッチ)を書ける
・ハッシュの入れ子もマッチ
・numeric-monitor pluginでやるような内容を簡単に書ける
・再起動いらずでクエリを増減できる
・Fluentdの起動と連動して子プロセスとして起動できる

norikra、データのつまみ食いをうまく出来ると捗りそう。
転送中に仕込んで何か気づいたり試したり将来的に整形して流したり。
あと連動して起動するの良い。

②@sonotsさん 「Fluentdでshadowサーバを用意したら捗った話
・Hikanko OSS fluentdの設定をUIからデプロイまでできる
・Yohoushi
・dummy_log_generator
・2万行の設定ファイル→死
・shadowサーバ(Kage等)をcopy+forward plugiinで簡単に作れるよ!
・(普段の構成)workerマシン4台で152プロセス 10万行/sec

(「Fluentd Casual Talks #3 で Shadow Server on Fluentd の話をしてきた」)

パラメータチューニング参考にする。
数日前にdummy_log_generator挑戦してうまくいかなかったので後でまた挑戦する。

③@stanakaさん 「Alternative Fluentd implementation in Go
・hatenaでもKibana使ってる
・Goは2009年Release、2012年に1.0、今は1.2
Rest API Webアプリケーションでも性能悪くない

ik触ってみるにはin_tail欲しい
ltsv.org参照してたけど、@stanakaさんがオーナーだったの今気づいた。

④@Masakiyo2さん、@okahashi117さん 「Windows版fluentdで幸せになれますか」
・0.10.41はまだ動かない
・0.10.35ベースで開発中、中川さんのWindows対応版Cool.io 1.2を入れて動く
・エンジン側、plugin側でそれぞれ色々手を入れてる。
・WinAPIを色んなところで使用。
・本番ではまだ使っちゃダメ

Windowsユーザなのでこれを導入すればbasy0C7さんみたいなことが・・・?

⑤@kzk_moverさん 「Treasure Agent Monitoring Service (ベータ)
・fluentd、そろそろJPをUSがアクセスで超えそうな勢い
・基本監視 プロセス、ポート、システム情報(CPU、mem、Network)
・Fluentd特化監視 buffer溢れ、リトライ回数、エラー/ワーニング
・monitor_agentでとれる
・TD monitoring service今日から開始!pluginライブリリース
・td-monitoring pluginでTDに監視情報を送付、グラフっで見れる。
・フリー(無料) アカウントは必要 まずは連絡を

監視系はプロセス監視くらいなので時間とって調査したい。
td-monitoringの送り先変えれるやつ出してくれたらKibanaに入れてみたい

ココからLT

⑥@kenjiskywalkerさん 「large td-agent.conf with chef
・fluentdの設定をinclude conf.d/*.confにしない
・includeをちゃんと個別に書くことで処理が明確になる
・Chefレシピでそれを管理する

(「Fluentd Casual Talks #3 でChefの話をしてきました #fluentdcasual」)

開発時はともかく本番は曖昧なところは無くしたいところ。
適当に入れておくと後のメンテナンスも困るし。

⑦@yoshi_kenさん 「Fluentd as a Middleware Engine」
MySQL→ES=Yamabiko
・fluentdの仕組みが色々便利で安全なのでミドルに流用しやすい

(「Yamabiko」)

データストアから検索エンジンは何かしらありうるので、
仕組みが一括で提供されるの便利そう。

⑧@bash0C7さん 「ご家庭でFluentd
・家でfluentd
Macbook Air→AWS SQS→ラズベリーPIをFluentdで!
・ツイッターから画像検索→TumblurをFluentdで!

(「ご家庭でFluentd」)

画像をmsgpackの発想すごい。ツイッターから流すの楽しそう。

⑨@kazegusuriさん 「OutputとBufferedOutputの間の何か
・Output pluginとBufferedOutput pluginの使い分け
・Bufferedだと2重登録おこりうる
・OutputとBufferedOutputの中間ようなplugin bufferize
・詰まったら再送分と通常分を次回まとめて送る

Output周りはES詰まる挙動同じになってそうなので後で参考にする。

⑩@choplinさん 「postgres関連の何か」
・mongo plugin 1番だけど怖いのでpostgres使おう
・postgresでJSON使える
・postgres用mskpack作った
・ローカル比較で早い

(「pg_msgpack」)

これはAWS postgresの流れか

⑪@frsyukiさん「What's new in v11
・無停止再起動
・マルチプロセス 
・エラーストリーム
 今まではmatchの中でエラーが出たらそれ以降の振る舞いはout plugin依存
 これからはエラー処理をコアで行う。エラー処理を書ける(エラー出たイベントはファイルに書くとか)
・plugin gemのバージョン管理。fluentd自体も書ける→バージョンアップ
・ログレベルをplugin毎に指定可能
・設定に変数を入れられる
・タグ書き換えが不要になる仕組み matchのnest
・v11ライブリリース 0.11.0.preview1

色々期待溢れてやばい。
fluentdユーザコミュニティは活発なので
どれも割りと待ってましたという感じです。

非常に有意義で参考になる情報ばかりで、色々試したくなりもしました。
dummy_log_generatorからshadow serverでv10とv11に流してnorikraもはさんで
Kibanaに入れつつ監視データも入れてパフォチュー、と一瞬浮かんだが
マシンリソースが無いのできっと誰かやると期待。

fluentd-plugin-elasticsearchでのログ時刻の扱い

これまでの自分のElasticsearch関連の記事では"request_time"という
独自に定義したラベルを用いて登録を行っていました。
一方、fluentdは"time"というラベルを用いるとログの時刻をfluentd内で扱うイベントタイムとみなします。
"time"とした場合と、それ以外でfluentd-plugin-elasticsearchの動作に
違いが見られたのでメモしておきます。

ところで"request_time"としたのには理由があったのですが、
その理由は本筋ではないので最後に書くことにします。


①ログ時刻を通常のラベルを用いて登録する場合

この場合はラベル名を"time"以外にしてパースします。
つまりこれまでの場合は"request_time"としていました。
この時点では他の項目と同じJSON(MessagePack)の一項目なので文字列です。
そしてそのラベルに対してElasticsearchのフィールドを日付型としてMappingをします。
これによりElasticsearch内で"request_time"は日付型フィールドとして扱われるようになりました。
後はこの"request_time"フィールドをKibana3でtime fieldとして設定します。
例えばtimepickerの設定画面では以下になります。

f:id:a3no:20130916234322j:plain

index作成にlogstash形式を指定していた場合(logstash_format true)
logstash形式indexの日付はログをimportしたfluentdで読み込んだ日付になります。

ところで上記画像のtime fieldにはデフォルトでは"@timestamp"が設定されています。
@timestampフィールドはどうやったら使えるのでしょうか?


②fluentdで"time"ラベルを使う

その答えはfluentdの"time"ラベルにありました。
fluentdのパースを以下のように設定したとします。

format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

この場合、記事の冒頭でも書きましたがログ時刻はfluentdのイベントタイムとして扱われます。
詳しくは書きませんがJSONボディ部とは異なる扱いになります。

そしてfluentd-plugin-elasticsearchでlogstash形式に設定すると、
"time"が"@timestamp"フィールドに登録されます。
更にこの場合、logstash形式index名もログ日付に追随します。
つまり、ログ日付と同じ日付でlostash-YYYY.MM.DDなindexが作成されます。

fluentdのパースの時点でちゃんと設定しておけば、
自動的にログの内容にあった登録がされるということですね。


なお、logstash形式ではなく自分でindex名を指定した場合は
"@timestamp"フィールドは出来ませんでした。
ただ以前に試した時はログ登録時刻で設定されてた気もするのでココは怪しいです。
(追記)確認しました。logstash_format trueなら@timestampフィールドが作られるが、
そうでない場合は@timestampフィールドは作られない、です。

それから、なぜ最初から"time"を使わず"request_time"にしてたのかですが、
fluentdの正規表現を書くのに@tagomorisさんのサイトを参考にテストしていたのですが、
「require 'time'」を見落として設定してなかったばっかりに
いくらやっても"time"としてパースするのに失敗したので
諦めて文字列なフィールドとして取得するようにしちゃったというオチでした。
間抜けすぎてどうしようもないですね。。。

knife-solo用boxをchefで作ってみた

以前、「Vagrantでknife-solo+Serverspecなboxを作っていた話」でした作業を
Chef使ってるのに手作業はなかろうとChefのレシピに落とし込んでみました。

作ったレシピは以下。

[site-cookbooks/knifesolobox/recipes/default.rb]

execute "install-chef" do
  user "root"
  command "curl -L https://www.opscode.com/chef/install.sh | bash"
end

%w{knife-solo rake serverspec}.each do |gem|
  gem_package "#{gem}" do
    gem_binary("/opt/chef/embedded/bin/gem")
    options("--no-ri --no-rdoc")
  end
end

%w{rake rspec}.each do |bin|
  link "/usr/sbin/#{bin}" do
    to "/opt/chef/embedded/bin/#{bin}"
  end
end

directory "/home/vagrant/.chef" do
  owner "vagrant"
  group "vagrant"
  mode  "0775"
end

cookbook_file "/home/vagrant/.chef/knife.rb" do
  source "knife.rb"
  owner "vagrant"
  group "vagrant"
  mode  "0644"
end

%w{insecure_private_key config}.each do |file|
  cookbook_file "/home/vagrant/.ssh/#{file}" do
    source "#{file}"
    owner "vagrant"
    group "vagrant"
    mode  "0600"
  end
end

link "/home/vagrant/chef-repo" do
  to "/vagrant"
end

[site-cookbooks/knifesolobox/files/default/knife.rb]

log_level                :info
log_location             STDOUT
node_name                'vagrant'
client_key               '/home/vagrant/.chef/vagrant.pem'
validation_client_name   'chef-validator'
validation_key           '/etc/chef-server/chef-validator.pem'
chef_server_url          'https://vagrant-ubuntu-precise-64:443'
syntax_check_cache_path  '/home/vagrant/.chef/syntax_check_cache'
knife[:solo_path] = '/tmp/chef-solo'

余り冪等性とかはこだわってない。まずはレシピ化が重要。
.ssh/configと.ssh/insecure_private_keyは前回記事を参照のこと。

Vagrantfileは以下。

Vagrant.configure("2") do |config|

  config.vm.box = "precise64official"
  
  config.vm.provision "chef_solo" do |chef|
    chef.add_recipe "knifesolobox"
    chef.cookbooks_path = "/path/to/chef-repo/site-cookbooks"
  end
end


実行してみます。

C:\vagrant\work\createbox>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'precise64official'...
[0K[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Fixed port collision for 22 => 2222. Now on port 2200.
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2200 (adapter 1)
[default] Booting VM...
[default] Waiting for machine to boot. This may take a few minutes...
[default] Machine booted and ready!
[default] The guest additions on this VM do not match the installed version of
VirtualBox! In most cases this is fine, but in rare cases it can
cause things such as shared folders to not work properly. If you see
shared folder errors, please update the guest additions within the
virtual machine and reload your VM.

Guest Additions Version: 4.1.12
VirtualBox Version: 4.2
[default] Mounting shared folders...
[default] -- /vagrant
[default] -- /tmp/vagrant-chef-1/chef-solo-1/cookbooks
[default] Running provisioner: chef_solo...
The chef binary (either `chef-solo` or `chef-client`) was not found on
the VM and is required for chef provisioning. Please verify that chef
is installed and that the binary is available on the PATH.

おうふ、、、
ChefでProvisioningするのにBoxに事前にchef-solo必要なんですね。
普段、knife-soloしかしてなかったので知らなかった。
そこでRyuzeeさんが紹介されているvagrant-omnibus pluginを使うことにしました。
Ryuzeeさんも書かれてますが、
knife-solo用のBoxを作るのにchef-soloを手で入れるとかなんか違うし、
じゃぁ、シェルでProvisioningして入れてから、、、と思ったけど
cookbookを途中でrsync出来るんだっけ?とか思って断念しました。


pluginインストール。

C:\vagrant\work\createbox>vagrant plugin install vagrant-omnibus
Installing the 'vagrant-omnibus' plugin. This can take a few minutes...
Installed the plugin 'vagrant-omnibus (1.1.1)'!


これでChefはインストールされるはずなので、
前のレシピの最初のexecuteは削っておきます。

それから、Vagrantfileに以下を追加。

  config.omnibus.chef_version = :latest


実行してみます。

C:\vagrant\work\createbox>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'precise64official'...
[0K[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] Booting VM...
[default] Waiting for machine to boot. This may take a few minutes...
[default] Machine booted and ready!
[default] The guest additions on this VM do not match the installed version of
VirtualBox! In most cases this is fine, but in rare cases it can
cause things such as shared folders to not work properly. If you see
shared folder errors, please update the guest additions within the
virtual machine and reload your VM.

Guest Additions Version: 4.1.12
VirtualBox Version: 4.2
[default] Mounting shared folders...
[default] -- /vagrant
[default] -- /tmp/vagrant-chef-1/chef-solo-1/cookbooks
[default] Installing Chef 11.6.0 Omnibus package...
[default] Running provisioner: chef_solo...
Generating chef JSON and uploading...
Running chef-solo...
stdin: is not a tty
[2013-09-15T22:09:31+00:00] INFO: Forking chef instance to converge...
[2013-09-15T22:09:31+00:00] INFO: *** Chef 11.6.0 ***
[2013-09-15T22:09:31+00:00] INFO: Setting the run_list to ["recipe[knifesolobox]"] from JSON
[2013-09-15T22:09:31+00:00] INFO: Run List is [recipe[knifesolobox]]
[2013-09-15T22:09:31+00:00] INFO: Run List expands to [knifesolobox]
[2013-09-15T22:09:31+00:00] INFO: Starting Chef Run for vagrant-ubuntu-precise-64
[2013-09-15T22:09:31+00:00] INFO: Running start handlers
[2013-09-15T22:09:31+00:00] INFO: Start handlers complete.
[2013-09-15T22:10:14+00:00] INFO: link[/usr/sbin/rake] created
[2013-09-15T22:10:14+00:00] INFO: link[/usr/sbin/rspec] created
[2013-09-15T22:10:14+00:00] INFO: directory[/home/vagrant/.chef] created directory /home/vagrant/.chef
[2013-09-15T22:10:14+00:00] INFO: directory[/home/vagrant/.chef] owner changed to 1001
[2013-09-15T22:10:14+00:00] INFO: directory[/home/vagrant/.chef] group changed to 1001
[2013-09-15T22:10:14+00:00] INFO: directory[/home/vagrant/.chef] mode changed to 775
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.chef/knife.rb] created file /home/vagrant/.chef/knife.rb
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.chef/knife.rb] updated file contents /home/vagrant/.chef/knife.rb
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.chef/knife.rb] owner changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.chef/knife.rb] group changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.chef/knife.rb] mode changed to 644
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/insecure_private_key] created file /home/vagrant/.ssh/insecure_private_key
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/insecure_private_key] updated file contents /home/vagrant/.ssh/insecure_private_key
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/insecure_private_key] owner changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/insecure_private_key] group changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/insecure_private_key] mode changed to 600
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/config] created file /home/vagrant/.ssh/config
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/config] updated file contents /home/vagrant/.ssh/config
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/config] owner changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/config] group changed to 1001
[2013-09-15T22:10:14+00:00] INFO: cookbook_file[/home/vagrant/.ssh/config] modechanged to 600
[2013-09-15T22:10:14+00:00] INFO: link[/home/vagrant/chef-repo] created
[2013-09-15T22:10:14+00:00] INFO: Chef Run complete in 43.603384421 seconds
[2013-09-15T22:10:14+00:00] INFO: Running report handlers
[2013-09-15T22:10:14+00:00] INFO: Report handlers complete

できました。
あとはvagrant packageすれば、box完成です。

手軽には出来ましたがpluginを使ったのでイマイチな感じがします。
やはりbox作るとなったら次はPackerでしょうか。
Packerは前少し触って失敗して以来放置してるんで
どこかで再挑戦してみたいですね。
あとは、できたBoxはServerspecでチェックしたいところ。