2013年6月7日金曜日

AWSのpython client "boto" を用いて特定のタグのインスタンス情報だけ取得する

botoは↓
http://aws.amazon.com/jp/sdkforpython/

インストールはpipで出来ます。
pip install boto

import boto.ec2

conn = boto.ec2.connect_to_region("ap-northeast-1", aws_access_key_id=<access_key_id>,aws_secret_access_key=<secret_access_key>)
reservations = conn.get_all_instances(filters={'tag-key':'Name', 'tag-value': 'hoge'})
for reservation in reservations:
  print reservation
上記のように書くと、Nameというタグの値に'hoge'と指定されているインスタンスのみ取得します。

応用としては、fabricとの組み合わせがあります。
fabricは、pythonで作られた、複数のサーバー群に対して手軽にコマンド実行を行う事ができるツールです。

例えばec2インスタンスに”Group”といったタグを割り当て、値を設定しておき、fabric側では当該グループの情報を取得するようにしておくと、手軽に特定サーバー群へのコマンド一括実行ができますね。
こういった管理手法は、Cloud Design Patternでは”Cloud DI”と呼ぶようです。

2013年6月5日水曜日

JedisでRedis2.6のevalコマンドを使用する

JedisはJavaによるRedisクライアント実装です。
Redisは2.6よりevalコマンドによるServer Side Scriptingに対応しました。この機能は非常に便利なので使用したいのですが、現在のJedis(2013年6月5日現在)の最新版:2.1.0ではevalコマンドにバグが存在するようです。

https://github.com/xetorthio/jedis/issues/340
こちらのissueを見る限りだと2.2.0では修正予定で、master branchには既に修正が反映されているようです。
今すぐにJedisでevalコマンドを使用したい場合は、ソース一式を取得し、自分でmavenコマンドを実行し、 packageを行う必要があります。
git clone https://github.com/xetorthio/jedis.git
cd jedis
mvn package
上記の結果、targetディレクトリに"jedis-2.2.0-SNAPSHOT.jar"が作成されますので、こちらを任意の場所に置き、クラスパスを設定してあげればOKです。


なお、Jedisでのevalコマンドは以下のような感じで使用出来ます。
Jedis jedis = new Jedis("localhost");
Object ret = jedis.eval("return 'hello redis lua world.'");
System.out.println(ret == null ? "(null)" : ret.toString());
実行結果は以下。
hello redis lua world.





nginxでltsv形式のaccess.logを出力する

人間による可読性の良さと、parse処理のしやすさ、1行中の出力内容の順序にたいしてロバスト(項目の順番が変わったり追加されてもparseに失敗することは無い)という点でltsv(Labeled Tab-Separated Value)というファイルフォーマットが一時期流行りました。

もちろん一時の流行ではなく、非常に実用的な手法なので、このたびnginxの出力ログをltsv形式に変えてみました。

◯設定
naoyaさんのブログ(http://d.hatena.ne.jp/naoya/20130205/1360088927)を引用すると、以下のような形になります。
http {
    …
    log_format  ltsv  'time:$time_local\t'
                      'host:$remote_addr\t'
                      'request:$request\t'
                      'status:$status\t'
                      'size:$body_bytes_sent\t'
                      'referer:$http_referer\t'
                      'ua:$http_user_agent\t'
                      'reqtime:$request_time\t'
                      'upsttime:$upstream_response_time';

    access_log  /var/log/nginx/access.log  ltsv;   

nginxで定義されている変数を用いて、タブ区切りでフォーマットを記述していく感じですね。

個人的な環境では上記では項目が不足していたので、以下のように項目を追加しています。
  log_format  ltsv  'time:$time_local\t'
                    'msec:$msec\t'
                    'host:$remote_addr\t'
                    'forwardedfor:$http_x_forwarded_for\t'
                    'req:$request\t'
                    'method:$request_method\t'
                    'uri:$request_uri\t'
                    'status:$status\t'
                    'size:$body_bytes_sent\t'
                    'referer:$http_referer\t'
                    'ua:$http_user_agent\t'
                    'reqtime:$request_time\t'
                    'upsttime:$upstream_response_time\t'
                    'cache:$upstream_http_x_cache\t'
                    'runtime:$upstream_http_x_runtime\t'
                    'vhost:$host';

  • アクセス時間は秒単位では情報として不足しているので、milliseconds($msec)まで出力するようにした。
  • $request は "GET /hoge" のようにrequest methodとrequest pathがスペース区切りで出力されますが、後々の集計の際に一緒になっていると面倒なので、$request_methodと$request_uriをそれぞれ個別に出力するようにした。
  • reverse proxyとして動作した際に有用な情報を追加($upstream~)
あたりが追加した動機です。

なお、WEB+DB Pressの74号で「LTSVでログ活用」という記事が書かれています。こちらに、「推奨ラベル一覧」という表があるので、各項目のlabel名をどのようにするか、参考にすると良いでしょう。
http://www.amazon.co.jp/dp/4774156078


◯Fluentdで読み込み
おそらく多くの現場で、出力したログはFluentd経由で読み込み、各種metricsの可視化や、解析サーバーへの転送などを行なっていると思います。

apacheのログを読み込む際は、たとえばin_tailプラグインなどを用いて、正規表現でparseをする形になります。commonsやcombined形式であればFluentdに標準パーサーがありますが、独自の拡張が行われたログの場合は自分でparseのフォーマットを記述する必要がありました。

ltsvの場合、最新(<= 0.10.32 )のFluentdであればltsv用の標準パーサーがありますので、非常に手軽に取り込めます。
<source>
  type tail
  format ltsv
  (snip.)
</source>
出力項目が変更になってもparse処理の部分は特に気にする必要が無いので、そういう意味では楽ですね。


ltsvは若干出力サイズが冗長になると思いますが、それを補って余りあるメリットがあるので、それほど大規模でないサイトやアプリのロギング方式としては適切だと思います。



2013年6月3日月曜日

munin pluginの動作確認を行う

muninをインストールしたり、pluginを作成した際、pluginの動作確認をしたい時があります。
その際の手法を幾つか。

あらかじめ、動作確認したいpluginについては /etc/munin/plugins/ 配下に設置した上で、munin-nodeの再起動をしておく必要があります。

◯munin-run 
munin-nodeの動作する環境で、ローカルに設定されているpluginの動作確認をする場合はこちらでしょう。
$ munin-run cpu
user.value 11097897
nice.value 129809178
system.value 6680422
(snip.)
$ munin-run cpu config
graph_title CPU usage
graph_order system user nice idle iowait irq softirq
graph_args --base 1000 -r --lower-limit 0 --upper-limit 300
(snip.)

◯muninwalk
muninサーバーから、各munin-nodeがインストールされているサーバーに接続して、正しく値が収集できるか確認したいケースがあります。
その際の便利ツールとして muninwalk というperl製のツールが公開されています。
ref. http://pocketstudio.jp/log3/2012/04/13/muninwalk_and_muninget/


◯ncコマンド
muninwalkは便利なのですが、すべてのサーバーにインストールして回るのは大変なので、多くのLinux OSにインストールされているncコマンドを使用して確認するのが、個人的には楽です。
ncコマンドを実行後、コンソールで " fetch (plugin name)" と入力すると、当該pluginのメトリクスの値が取得出来ます。
$ nc munin-node 4949
# munin node at munin-node
fetch cpu
user.value 11104466
nice.value 129888815
system.value 6684441
(snip.)
munin-nodeのポートに接続出来ない場合はネットワークの設定か、接続先munin-nodeのmunin-node.confの設定に問題がある可能性があります。

pluginの値が取得出来ず"# Unknown service" という文字列が返される場合は当該pluginが正しくmunin-nodeに認識されていない可能性があります。
また"#Bad exit" と返される場合は当該pluginが正しく動作していない可能性があります。

こういう感じで、動作確認や問題の切り分けを行なっていく、という形になります。




2013年5月25日土曜日

2013年5月17日金曜日

muninの監視通知をpushoverで受信する

”muinin"と書いていますが、特にmuninに限った話ではなく、pushoverの提供しているAPIを経由して何かしらアラート的な情報をiPhone/Android端末にpush通知しましょう、という話です。


◯Pushover
https://pushover.net/

iPhoneもしくはAndroidに手軽にPushメッセージを通知する事のできるサービスです。
会員登録すると、各ユーザーに固有のtokenが割り当てられます。それを使用してAPI経由でPushoverにメッセージを送ると、tokenに紐付いたユーザーの携帯端末宛に、pushメッセージが送信されます、
なお、pushの受信には専用のアプリをダウンロードする必要があり、アプリは有料になっています。


◯Pushoverにメッセージを送る
手順としては以下。

  1. 会員登録を行う。https://pushover.net/ より
  2. 会員登録完了後、あらためて https://pushover.net/ にログインすると、画面右側にユーザーに割り当てられたtoken(user key)が表示されます。こちらを何かしらメモに控えておきます。
  3. 同画面の一番下の「You Applications」より、「Edit」をクリックし、アプリケーション登録画面に進みます。ここで「Create a New Application」をクリックし必要事項を入力します。
    ひと通り完了すると、アプリケーションごとのtoken(API Token/Key)が発行されるので、こちらを何かしらメモに控えておきます。
  4. 入手したtoken情報を使用してAPIにメッセージを送るプログラムを用意します。
    各種言語での操作方法がpushoverのページの掲載されていますが、ここでは一番手軽なshell scriptsによるものを例示します。こちらを"pushover.sh"として保存します。
    #!/bin/sh
    
    API_TOKEN="(API Token/Key)"
    USER_TOKEN="(user key)"
    MESSAGE=`< /dev/stdin`
    
    echo "send notify message : '${MESSAGE}'"
    
    curl -s -F "token=${API_TOKEN}" -F "user=${USER_TOKEN}" -F "message=${MESSAGE}" https://api.pushover.net/1/messages.json
  5. 以下の様な形で標準入力でshell scriptsに文字列を与えてあげると、その内容をpushover APIに送信します。
    echo "help me!!" | ./pushover.sh

◯muninとの連携
muninのエラーメッセージ通知先としてpushoverを使用する場合は、muninがインストールされているサーバー上に上記shellを設置した上で、"munin.conf"に以下のように記述をすればOKです。
一般的なメール通知の方法も参考程度に併記しておきます。また、"max_messages" "always_send"の設定は任意です。下記だとcriticalエラーのみpush通知されます。
contacts admin push
contact.admin.command        mail -s "Munin ${var:group}::${var:host}" alert@hoge.com
contact.admin.max_messages      1
contact.admin.always_send       warning critical
contact.push.command         /var/lib/pushover.sh
contact.push.max_messages       1
contact.push.always_send        critical


◯使用してみての感想
メール受信にありがちな配送遅延が少なく、かなり迅速にエラーが検知できるようになると思います。
pushoverではアラートの取得のみ行うという使い分けにしておけば取得漏れも極力減らせますし、通知音のカスタマイズなども行いやすいので、運用的にもメールと分離するのは便利な事が多いです。

ただ、pushoverはメッセージの保存件数に上限があり、かつ検索機能も貧弱なため、速報的な通知はpushover経由でpushで受け取り、過去の履歴についてはメールや他ツールで管理する、というやり方が望ましいのではないかと思います。

多少の制約はあれども、個人的にはいまさら「メールでのアラート受信」の時代には戻れない感があります。便利な世の中になりましたね。
ただ、pushoverはひと月あたり7,500件のメッセージまでしか送れないっぽいので(以降は10,000件追加あたり$50必要?)、送りすぎには注意。



2013年5月1日水曜日

crontabの中で"%"を使用したい場合はエスケープする必要がある

知らなかった…
The "sixth" field (the rest of the line) specifies the command to be run. The entire command portion of the line, up to a newline or % character, will be executed by /bin/sh or by the shell specified in the SHELL variable of the cronfile. Percent-signs (%) in the command, unless escaped with backslash (\), will be changed into newline characters, and all data after the first % will be sent to the command as standard input.

(ref. http://linux.die.net/man/5/crontab)

たとえば
`date --date '1 day ago' +'%Y-%m-%d'`
みたいにdateコマンドの出力書式を指定したい時などに”%"を使用しますが、crontabに記載したい場合は以下のようにエスケープしてあげる必要があります。
`date --date '1 day ago' +'\%Y-\%m-\%d'`