fluent-plugin-redis-slowlogを作った

fluent-plugin-redis-slowlogを作った。

以前からプラグインつくってみたいなーと思っていたけどアイデアとか無かった。
すると、会社でRedisのSlowlog集めたいな−って話が出て、Fluentdのプラグインとか存在してるだろうからそれ使えば楽じゃね?と思ったらプラグイン見つからなかった…。
なので作ってみた。

始めてFluentdのプラグインをつくるので以下を参考にさせていただいた
kenjiskywalkerさんにおんぶにだっこ状態ですが…
超初級!Fluentdのプラグインを書きたくなった時の下地づくり - さよならインターネット
kenjiskywalker/fluent-plugin-rds-slowlog · GitHub

fluent-plugin-redis-slowlogの使い方
fluent.confのinputプラグインとして利用します。

<source>
  type redis_slowlog  
  host localhost         # Redis-serverのホスト名又はIP
  port 6379              # Redis-serverのPort番号 (デフォルトは6379)
  interval 10            # Slowlogを取得に行くタイミング(デフォルト10秒に1回)
  logsize  128           # 一度に取得するSlowlogの数 (デフォルト128)※Redisの初期設定だとSlowlogが128個までしか保存されない。
                         # また、数が多くなるほど取得にかかる時間も増えるのでRedisの負荷等見て調整してください
  tag redis.slowlog      # タグ名
</source>

<match ** >
  type stdout
</match>

出力はコチラ
※どんなcommandでもSlowlogに登録されるようにRedis側にconfig set slowlog-log-slower-than 1を実行しています。なので、fluent-plugin-redis-slowlogから実行しているSlowlog取得のコマンドも出力されてます。

2014-06-08 05:33:31 +0900 redis.slowlog: {"id":173,"timestamp":1402173257,"exec_time":15,"command":["keys","*"]}
2014-06-08 05:33:31 +0900 redis.slowlog: {"id":172,"timestamp":1402173253,"exec_time":137,"command":["slowlog","get","128"]}
2014-06-08 05:33:41 +0900 redis.slowlog: {"id":174,"timestamp":1402173263,"exec_time":140,"command":["slowlog","get","128"]}
2014-06-08 05:33:51 +0900 redis.slowlog: {"id":176,"timestamp":1402173275,"exec_time":9,"command":["set","hoge","aga"]}
2014-06-08 05:33:51 +0900 redis.slowlog: {"id":175,"timestamp":1402173273,"exec_time":137,"command":["slowlog","get","128"]}
2014-06-08 05:34:01 +0900 redis.slowlog: {"id":177,"timestamp":1402173283,"exec_time":166,"command":["slowlog","get","128"]}

Format"id":slowlog-id,"timestamp": Redisでcommandが発行された時間(unixtime),"exec_time":コマンドの処理にかかった時間(マイクロ秒),"command":["実際に発行されたコマンド","と","引数"]}
尚このプラグイン毎回logsize分のSlowlogを取得しに行き、取得したSlowlogは削除していません。
なので毎回取得する度に同じSlowlogを取得している場合が普通に考えられます。
しかしそのままでは無意味なので、RedisのSlowlogに付与されているidを利用しています。
取得の度に前回に取得したslowlogのidの最大値を取り出しておき、その最大値より大きなidを持つものだけemitしています。

また、今度追加する予定ですが、このプラグイン再起動するとSlowlogのid情報が無くなってしまうので以前取り出したslowlogを取得してしまいます。 id情報をファイルに出力しておいて再起動時にはそのファイルのidを最大値として格納するよにしないと…

Docker+Packer で MySQLを動かしてみた

最近Dockerが話題なので会社でも利用し始めており、そしてPackerも勉強しようと思い調べてたらDockerで利用できるようだったので組み合わせてみた。

ついでに、MySQL 5.6をインストールしてみた

まずUbuntu Server 13.10にDockerとPackerを導入

# Docker Install
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get install lxc-docker

# Packer Install
wget https://dl.bintray.com/mitchellh/packer/0.5.2_linux_amd64.zip
sudo unzip 0.5.2_linux_amd64.zip -d /usr/local/bin/

packer で利用するjson(mysql.json)はこちら "builders"には"type": "docker"を指定して、Dockerイメージの元となるイメージと、 出力されるDockerイメージのファイル名を指定します。 ここをAWSの設定にしてやればMySQL5.6をAWSで起動するAMIを作れちゃうかな?修正必要ですけど。 "provisioners"ではSSHのインストールをMySQLインストール用スクリプトを呼び出しています。

{
        "builders": [{
                "type": "docker",
                "image": "ubuntu:12.04",
                "export_path": "mysql.tar"
        }],
                "provisioners": [
                {
                        "type": "shell",
                        "inline": [
                                "sleep 30",
                                "apt-get update",
                                "apt-get -y update",
                                "apt-get install -y openssh-server",
                                "mkdir /var/run/sshd /root/.ssh/"
                        ]
                },
                {
                        "type": "file",
                        "source": "ssh/id_rsa.pub",
                        "destination": "/root/.ssh/authorized_keys"
                },
                {
                        "type": "shell",
                        "inline": [
                                "chmod 600 /root/.ssh/authorized_keys"
                        ]
                },
                {
                        "type": "shell",
                        "script": "mysql-install.sh"
                }
        ]
}

上記json"provisioners"の2つ目にSSH-Keyを指定していますが、 Jsonファイルが置いてある場所にDockerで利用するSSHkeyを配置しておりそれを利用しています。

MySQLインストール用のスクリプト(mysql-install.sh)

# Dockerで利用する ubuntu:12.04だと以下をしとかないとMySQLインストールに必要なパッケージがインストールと、
# MySQLのスクリプトが失敗する。
sudo locale-gen en_US.UTF-8
sudo apt-get install -y perl

# MySQLのインストール
wget http://cdn.mysql.com/Downloads/MySQL-5.6/mysql-5.6.16-debian6.0-x86_64.deb
sudo dpkg -i mysql-5.6.16-debian6.0-x86_64.deb
sudo apt-get install -y libaio1
sudo cp -p /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql

# MySQLユーザの作成
sudo groupadd mysql
sudo useradd -r -g mysql mysql

# MySQLユーザに合わせて権限等の変更
sudo chown -R root:root /opt/mysql
sudo chown -R mysql:mysql /opt/mysql/server-5.6
sudo install -o mysql -g mysql -d /data/mysql
sudo -u mysql /opt/mysql/server-5.6/scripts/mysql_install_db --user=mysql --datadir=/data/mysql

# 適当な設定ファイル用意
echo '
[mysqld]
basedir = /opt/mysql/server-5.6
datadir = /data/mysql
' |sudo tee /etc/my.cnf

# サービスに登録
# 登録してもDocker起動時には動かないけど…
sudo update-rc.d mysql defaults
sudo /etc/init.d/mysql start

# 外部から接続するためのユーザ追加
/opt/mysql/server-5.6/bin/mysql -e "grant all privileges on *.* to root@'172.17.42.1' IDENTIFIED BY 'root';"

# MySQLとSSHDを起動するスクリプトを用意
echo ' /etc/init.d/mysql start
/usr/sbin/sshd -D
' |sudo tee /usr/local/bin/docker-start.sh

Packer の実行 ※Dockerを利用するのでsudo付きで…

sudo packer build mysql.json

Packerでのイメージ作成後Dockerにイメージを取り込みます

# " - ubuntu/mysql " で Dockerイメージ名の設定
cat mysqlimage.tar |sudo docker import - ubuntu/mysql

# 取り込まれたか確認
sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu/mysql        latest              3e60e3386690        3 days ago          1.729 GB
ubuntu              12.04               9cd978db300e        3 weeks ago         204.4 MB

「REPOSITORY」に「ubuntu/mysql」が表示されているので取り込みは成功です。 続いて起動

# bash /usr/local/bin/docker-start.sh はDockerで実行するコマンドを指定
# 内容はMySQLの起動とSSHDの起動
# 
sudo docker run -p 22 -p 3386 ubuntu/mysql bash /usr/local/bin/docker-start.sh

# 起動確認
sudo docker ps
CONTAINER ID        IMAGE                 COMMAND                CREATED             STATUS              PORTS                                            NAMES
78f1af31b25c        ubuntu/mysql:latest   bash /usr/local/bin/   3 days ago          Up 3 days           0.0.0.0:49161->22/tcp, 0.0.0.0:49162->3306/tcp   stoic_torvalds

# Dockerのport番号取得
# docker inspect を利用し起動したDockerの情報を取得し jq で整形してPort番号を取り出す
mysqlport=`sudo docker inspect \`sudo docker ps |grep ubuntu/mysql | cut -f 1 -d ' '\` | jq '.[].NetworkSettings.Ports["3306/tcp"][].HostPort | tonumber'`

# MySQLへの接続
mysql -h 127.0.0.1 -P $mysqlport -u root -p

以上でPakcerを利用したMySQLのDockerイメージの作成手順の終了です。 一応後で直接設定変更したいだとか、ログを確認したいだとかのためにSSH残してあります。 ログだけが心配ならFluentd等を一緒に導入して外出しにしてもいいのかなと思います。

あ、最後にファイル構成

tree
.
|--  mysql-install.sh
|--  mysql.json
|--  packer_cache
|--  ssh
|   |--  id_rsa
|   |--  id_rsa.pub

2 directories, 5 files

Fluentd Config DSL と fluent-plugin-forest

久しぶりにいつか書こう、書こうと思って放置してたやつ。 DSLの書き方もすぐ忘れるので、メモ代わりにしつつ…

会社のログ管理としてFluentd: Open Source Log Managementを利用しています。

Fluentdはプラグインを追加したり、作成したりして自分の好きなようにログや、アプリからのデータ等を加工できます。

しかし、いろいろできてしまう為設定ファイルがとても長くなってしまうことも…

しかし最近ではFluentdもDSLに対応し、Rubyのコードで設定ファイルが書けるようになりました。

Fluentd界隈ではバイブルのtagomorisさんのブログより

Fluentd Config DSLについての話 - tagomorisのメモ置き場

以下に適当なサンプルを用意しました、本気をだすとどんどん長くなります。 *1

# fluentd.conf

<source>
  type forward
</source>

<match *.nginx>
  type file
  path /var/log/fluentd/nginx-*
  time_slice_format %Y%m%d-%H
  time_format %Y%m%dT%H%M%S%z
  compress gzip
</match>
<match *.app>
  type file
  path /var/log/fluentd/app-*
  time_slice_format %Y%m%d-%H
  time_format %Y%m%dT%H%M%S%z
  compress gzip
</match>
<match *.syslog>
  type file
  path /var/log/fluentd/syslog-*
  time_slice_format %Y%m%d-%H
  time_format %Y%m%dT%H%M%S%z
  compress gzip
</match>

これをFluentd Config DSLで書くとこんな感じ

# fluentd.rb

source {
  type :forward
}

['nginx','app','syslog'].each do|tag|
  match("*.#{tag}") {
    type :file
    path "/var/log/fluentd/#{tag}-*"
    time_slice_format '%Y%m%d-%H'
    time_format '%Y%m%dT%H%M%S%z'
    compress 'gzip'
  }
end

そこそこ減ったんじゃないでしょうか?

次のように実行してやると「$ fluentd -c fluentd.rb --log /tmp/fluentd.log」実行でき、 /tmp/fluentd.log の中に fluentd.rbが展開されfluentd.confと同じ内容が出力されてるのが確認できます。

Config DSLで書いていて、思った通りの設定になってるか確認したいときは実行してログなど見ると良いかも、

テストだけなら次を実行すると「$ fluentd -c fluentd.rb --dry-run」Fluentdは起動しないで、展開された設定が標準出力されます。

と、DSLの話をしてきましたがタイトルにはもうひとつ fluent-plugin-forest とあります。 このFluentdプラグインまたしてもtagomorisさんのブログを引用します。

fluent-plugin-forest released! - tagomorisのメモ置き場

私もいろんな場所で利用させて頂いています。

しかしDSLでこのプラグインを利用すると…

実行した設定ファイルはこれ

# fluentd.rb

source {
  type :forward
}

match("**") {
  type :forest
  subtype :file
  template {
     time_slice_format '%Y%m%d-%H'
     time_format '%Y%m%dT%H%M%S%z'
     compress 'gzip'
  }
  ['nginx','app','syslog'].each do|tag|
    cases("*.#{tag}.*") {
      path "/var/log/fluentd/${tag_parts[1]}/${tag}-*"
    }
  end
}

これを実行してみると

$ fluentd -c fluentd.rb --dry-run
2013-12-10 02:45:06 +0900 [info]: reading config file path="fluentd.rb"
/home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/config_dsl.rb:28:in `instance_eval': ./fluentd.rb:16: syntax error, unexpected '{', expecting keyword_when (SyntaxError)
./fluentd.rb:19: syntax error, unexpected '}', expecting keyword_end
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/config_dsl.rb:28:in `eval'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/config_dsl.rb:14:in `parse'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/engine.rb:72:in `parse_config'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/supervisor.rb:285:in `run_configure'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/supervisor.rb:108:in `dry_run'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/supervisor.rb:83:in `start'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/lib/fluent/command/fluentd.rb:146:in `<top (required)>'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:53:in `require'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:53:in `require'
    from /home/momin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluentd-0.10.41/bin/fluentd:6:in `<top (required)>'
    from /home/momin/.rbenv/versions/2.0.0-p353/bin/fluentd:23:in `load'
    from /home/momin/.rbenv/versions/2.0.0-p353/bin/fluentd:23:in `<main>'

なんでかなーと思って、ちょこちょこ設定ファイル変更したりしてみても動かない…

よくよく考えてみると、fluent-plugin-forestの中には case が…はい、 つまり Fluentdの設定ではなくてRubyのコードして読み取られてたわけなんですよ… なのでちょこっとプラグインと設定ファイルを修正します

$ cd ~/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/fluent-plugin-forest-0.2.2/lib/fluent/plugin
$ diff out_forest.rb out_forest.rb.bk 
44c44
<       when 'cases'
---
>       when 'case'
$ cd -
$ diff fluentd.rb fluentd.rb.bk
16c16
<     cases("*.#{tag}.*") {  
---
>     case("*.#{tag}.*") {   

特に何も考えないでcaseの後ろにsをつけてみました…

そして再度実行…

$ fluentd -c fluentd.rb --dry-run
2013-12-10 02:56:51 +0900 [info]: reading config file path="fluentd.rb"
2013-12-10 02:56:51 +0900 [info]: gem 'fluent-plugin-forest' version '0.2.2'
2013-12-10 02:56:51 +0900 [info]: gem 'fluentd' version '0.10.41'
2013-12-10 02:56:51 +0900 [info]: using configuration file: <ROOT>
  <source>
    type forward
  </source>
  <match **>
    type forest
    subtype file
    <template>
      time_slice_format %Y%m%d-%H
      time_format %Y%m%dT%H%M%S%z
      compress gzip
    </template>
    <cases *.nginx.*>
      path /var/log/fluentd/${tag_parts[1]}/${tag}-*
    </cases>
    <cases *.app.*>
      path /var/log/fluentd/${tag_parts[1]}/${tag}-*
    </cases>
    <cases *.syslog.*>
      path /var/log/fluentd/${tag_parts[1]}/${tag}-*
    </cases>
  </match>
</ROOT>
2013-12-10 02:56:51 +0900 [info]: adding source type="forward"
2013-12-10 02:56:51 +0900 [info]: adding match pattern="**" type="forest"

するとちゃんと動作してくれました。

実際会社ではDSL使ってないのでこんな修正はいらないのですが、 もしDSL使ってfluent-plugin-forest利用される場合は考慮いただけたらなと思います。

*1:ファイルを分けてincludeする手もありますけど…

丸の内MongoDB勉強会 #10に参加してきました

先日はじめてMongoDBの勉強会に参加してきました。
 勉強会はこちら:丸の内MongoDB勉強会 #10

主な内容はMongoDBの初心者向けのハンズオンとZabbixとMMSを利用したMongoDBの監視の話とプラス懇親会でした。
私は勤め先のサービスでMongoDBの構築や運用をしているのですが、少しさわれるだけで、オペレーション等を全く勉強したことがないのです…。
すると今回初心者向けと私の心に響くハンズオンがあるということで、参加しようと思いました。
また、それだけではなくちょうどMMSを利用していたところなので、いろいろとTispを聞けたらいいなと思ってもいました。

ハンズオンの内容等は上に張り出しましたAtndから巡っていただくと資料はあるのでそちらで確認して下さい。

つづいてMongoDBの監視について

続きを読む