2013年1月30日水曜日

Amazon Elastic Transcoderってなんじゃ?

Amazon Elastic TranscoderというサービスがBeta版でリリースされていました。
S3をつかったVideoの変換サービスのようです。

それでは実際に触ってみたいと思います。
AWSコンソールをみると、「Amazon Elastic Transcoder」という新しい項目が増えているのがわかります。



開くと、Transcoder画面が表示され、使用開始申請をするとサブスクリプション認証が始まります。



使用できるようになると、再びConsoleを訪れた時このような初期画面が表示されます。
入力と出力をpipelineとして定義して、ジョブを作成する流れのようです。



「Create new pipeline」をクリックすると、Pipelineの定義画面になります。
Pipelineでは変換したいビデオファイルがある入力元バケットと変換したビデオの出力先のバケットを指定します。

  • Pipeline Name:適当に名前をつけます。
  • Input Bucket:変換元のビデオファイルが置いてあるS3のバケット名。
  • Output Bucket:変換後のビデオファイルを保存したいS3のバケット名。
  • IAM Role:TranscoderがアクセスするためのIAMロール


ここでは、以下のように入力してみました。

  • Pipeline Name:myfist-converter
  • Input Bucket:memorycraft-video-input(S3に予めバケットを作成しておきます。)
  • Output Bucket:memorycraft-video-outpu(S3に予めバケットを作成しておきます。)
  • IAM Role:指定なし

オプションで通知方法など選べるようですが、ここでは割愛します。
「Create Pipeline」をクリックして登録します。



すると、Pipelineの一覧画面に作成したものが表示されます。



ここで、Input Bucketに指定したバケットに変換したいビデオファイルをアップします。




次に、Create Jobのボタンをクリックして、変換ジョブを作成します。


Jobでは、Pipelineの中で、変換元のファイル名と保存先のファイル名を定義し、利用したい変換方式のプリセット(Preset)を選びます。


  • Pipeline:どのPipelineを使うかを選びます。
  • Preset:変換方式をプリセットの一覧から選べます。プリセットはAWSが用意したもの以外に、自分で定義もできます。
  • Input Key:PipelineのInput Bucketにある、変換したいビデオファイルのパスを入力します。フォルダの中にあるものは、フォルダも含めたパスにします。
  • Output Key:PipelineのOutput Bucketにある、変換後に保存したファイルパスを入力します。フォルダの中に保存する場合は、フォルダも含めたパスにします。



ここではiPhone4Sで再生するため、3つのジョブを作成し、以下のように指定しました。

  • Pipeline:myfist-converter
  • Preset:System Preset : iPhone4S -System
  • Input Key:Caravan-Palace-Dramophone.mp4
  • Output Key:Dramophone.mp4


下に変換オプションなどもありますが、ここでは割愛します。
「Create Job」をクリックするとジョブが開始され、Job一覧画面に移ります。


おそらく大量のジョブが実行されることを想定しているのか、最初は検索画面の状態です。
ここで、Pipelineを指定して「Search」をクリックすると、先ほど実行したJobが一覧に表示されます。
まだ実行中のものは「Processing」となっており、完了すると「Complete」になります。


完了したら、S3のバケットを見てみます。


無事出力されていました。
これをダウンロードしてみると、正常に再生されました。
iPhoneにいれても再生されているようです。




今回はシステムプリセットを使用しましたが、既存のプリセットを元に自分で定義することもできます。




おそらく本来は、大量のビデオをAPI経由などでバッチ的に変換するような使い方が思いつきますが、現在以下の制限があります。

  • Pipelines:1アカウントにつき4つまで
  • Active jobs:1Pipelineにつき1,000まで
  • Presets:1アカウントにつき、ユーザー定義プリセットは50つまで


変換したファイルをCloudFrontでストリーミング配信したり、Glacierにバックアップしたりなどの動画関連のサービスアプリケーションが作りやすくなるかもしれません。

Amazonってほんとにリリースペース速いですね。
以上です。

2013年1月28日月曜日

EMRってなんじゃ?(HiveのDynamicPartitionでファイルを分割してS3に出力)

S3にある数年分のデータが1つのファイルに含まれていて、EMRで年や月単位で分割したいという要望が一部であったので、やってみました。

例として、以下のようなCSVがあるとします。

test.csv
2012,01,12,title1,body1
2012,01,22,title2,body2
2012,02,02,title3,body3
2012,03,01,title4,body4
2012,03,11,title5,body5
2012,03,18,title6,body6
2012,04,04,title7,body7
2012,05,02,title8,body8
2012,05,05,title9,body9
2012,06,13,title10,body10
2012,06,14,title11,body11
2012,06,29,title12,body12
2012,07,03,title13,body13
2012,07,05,title14,body14
2012,07,14,title15,body15
2012,07,30,title16,body16
2012,08,12,title17,body17
2012,09,23,title18,body18
2012,10,10,title19,body19
2012,11,23,title20,body20
2012,12,22,title21,body21
2013,01,04,title22,body22
2013,01,05,title23,body23
2013,01,06,title24,body24
2013,01,17,title25,body25
2013,01,18,title26,body26
2013,01,23,title27,body27
2013,02,03,title28,body28
2013,02,04,title29,body29
2013,02,05,title30,body30
2013,02,06,title31,body31
2013,03,03,title32,body32
2013,03,16,title33,body33
2013,04,03,title34,body34
2013,04,04,title35,body35
2013,04,09,title36,body36
2013,04,12,title37,body37
2013,04,14,title38,body38
2013,04,17,title39,body39


まず、入力用のバケットと出力用のバケットを用意します。

入力
 csv-origin(上記のtest.csvをアップしておきます。)
出力
 csv-archive (rsltというフォルダを作成しておきます。)





EMRでジョブフローを作成します。今回はインタラクティブジョブフローを使います。
これはEMRクラスタにSSHで入って、hiveコンソールで直接クエリを実行する方式です。

まず、EMRで「Create New Job Flow」をクリックします。
ダイアログが立ち上がるのでJob FlowにHive Programを選択します。




今回HiveScriptは使用せずインタラクティブなので、「Start as Interactive Hive Session」を選択肢ます。




インスタンス数やサイズなどはとりあえずそのままでOKです。




SSHでログインするときのキーを選択して




以降はそのまま「Continue」を続けます。




すると、以下のようにジョブフローが起ち上がり、マスターノードのエンドポイントが記されます。




このエンドポイントに対して、指定したキーを使ってhadoopユーザーでSSHログインします。
ssh -i ./memorycraft.pem hadoop@ec2-175-41-212-129.ap-northeast-1.compute.amazonaws.com

hiveコマンドを打つとhiveコンソールに接続されます。
$ hive
Logging initialized using configuration in file:/home/hadoop/.versions/hive-0.8.1/conf/hive-log4j.properties
Hive history file=/mnt/var/lib/hive_081/tmp/history/hive_job_log_hadoop_201301280945_987980527.txt
hive>


ここでまず入力用バケットを表すテーブルを作成します。
  • LOCATION句で入力用S3バケットのtest.csvのパスを指定します。
hive> CREATE EXTERNAL TABLE IF NOT EXISTS csv_origin (yyyy string, mm string, dd string, title string, body string)
      > ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'
      > LOCATION 's3://csv-origin/test.csv';



次に、出力用のバケットを表すテーブルを作成します。
  • 年ごと月ごとにフォルダ分けされるようにyyyyとmmでPARTITIONED BYを指定します。
  • テーブルの項目にはyyyy, mm以外の項目を指定します。
  • 出力ファイルがテキストファイルになるようSTORED AS TEXTFILEを指定します。
  • LOCATION句で出力用S3バケットのパスを指定します。
hive> CREATE EXTERNAL TABLE IF NOT EXISTS csv_archive (dd string, title string, body string) PARTITIONED BY (yyyy string, mm string)
      > ROW FORMAT DELIMITED
      > FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'
      > STORED AS TEXTFILE
      > LOCATION 's3://csv-archive/rslt/';



あとはINSERT ~ SELECTなのですが、普通にやると、PARTITION(yyyy='2012', mm='02')のようにパーティションを決め打ちで指定しないといけなくなるので、DynamicPartitionというものを使います。
(通常のパーティションは別の記事で改めて書いてみたいと思います。)

DynamicPartitionを有効にするには、hive.exec.dynamic.partitionとhive.exec.dynamic.partition.modeというパラメータをセットしつつ、以下の様な構文を使用します。
hive> set hive.exec.dynamic.partition=true;
hive> set hive.exec.dynamic.partition.mode=nonstrict;
hive> FROM csv_origin co
      > INSERT OVERWRITE TABLE csv_archive PARTITION (yyyy, mm)
      > SELECT
      >  co.dd,
      >  co.title,
      >  co.body,
      >  co.yyyy,
      >  co.mm
      > DISTRIBUTE BY co.yyyy, co.mm;


このクエリにより、Map/Reduceが行われ、完了すると出力用のバケットに以下のようにフォルダが作成され、それぞれのフォルダに該当データが集約されたファイルが出力されます。



たとえば、yyyy=2013/mm=03というフォルダに出力されたファイルには以下の様に2013/03のデータだけが記述されています。

03,titile32,body32
16,titile33,body33

無事に年月単位でデータが分割されるようになりました。
今回のサンプルは数がすくないですが、大量にバラバラなデータが入ったファイルを規則的に分割する場合はこういった方法が有効かもしれません。

以上です。


2013年1月27日日曜日

nagiosってなんじゃ?(nrpeの出力が途中で切れる問題)

nagiosnrpeでリモートサーバーのチェックコマンドの出力を受け取りメールなどで確認すると、プラグインによっては出力内容が途中で途切れることがあります。今回はその原因を調べて直すまでの経緯を書いてみます。


現象


例としてチェックコマンドは以下で紹介されているcheck_log3.plというログ監視プラグインをベースに、ログの該当行の前後を出力に含めるように改変したものを使用しています。
Nagiosのログ監視で複数の除外キーワードや正規表現を使う(check_log3.pl)

前後10行分のログを取得するようにしてあるのですが、nagiosのWEBインターフェースなどで確認すると、以下のように途中で途切れていました。






調査


そこでまず、リモート側でこのチェックコマンドを直接たたいてみました。
# /usr/lib/nagios/plugins/check_log3 -l /etc/httpd/logs/access_log -s /etc/nagios/seeks/check_log3.httpd.seek -p hoge -w 1 -c 5
CRITICAL: Found 7 lines (limit=1/5): 219.117.233.241 - - [27/Jan/2013:14:53:35 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:35 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:35 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17 219.117.233.241 - - [27/Jan/2013:14:53:35 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:36 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:36 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:37 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:37 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:38 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:38 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:39 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:39 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:39 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 54.248.239.80 - - [27/Jan/2013:14:53:40 +0900] "GET / HTTP/1.0" 200 1 "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)" 219.117.233.241 - - [27/Jan/2013:14:53:40 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:41 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:41 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:41 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:42 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:42 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:42 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:14:53:43 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17"

どうやらチェックコマンド自体は必要な長さで正常に出力してくれているようです。




次に、監視サーバー側で直接check_nrpeコマンドを叩いてみます。
# /usr/lib64/nagios/plugins/check_nrpe -H xxx.xxx.xxx.xxx -c check_log3 -a '/etc/httpd/logs/access_log' '/etc/nagios/seeks/check_log3.httpd.seek' 'hoge' 1 5
CRITICAL: Found 5 lines (limit=1/5): 219.117.233.241 - - [27/Jan/2013:18:27:43 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:27:44 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:27:44 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 54.248.235.241 - - [27/Jan/2013:18:27:45 +0900] "GET / HTTP/1.0" 200 1 "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)" 219.117.233.241 - - [27/Jan/2013:18:27:45 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Ja

こちらは途切れているようです。つまりnagios本体でもなく、どちらかのnrpeかcheck_nrpeに問題がありそうです。




また、nrpe.cfgをみる限り、それらしき設定は見つかりませんでした。
受信or送信側のnrpeが内部で固定長でカットオフしている可能性が高そうですが、この環境では、nrpeはyumでインストールされているため何をしているのかわかりません。

そこで、nrpeのソースをダウンロードしてソースをさらってみました。
まず、リモートサーバーにnrpeを落としてみます。
# cd /usr/local/src
# curl -L -o nrpe-2.13.tar.gz http://prdownloads.sourceforge.net/sourceforge/nagios/nrpe-2.13.tar.gz
# tar nrpe-2.13.tar.gz
# cd nrpe-2.13


固定長でカットしているとしたらどうせ定数で、MAXが付いているだろうということで探してみます。
# find . -print | xargs grep "define MAX"
./include/common.h:#define MAX_INPUT_BUFFER 2048 /* max size of most buffers we use */
./include/common.h:#define MAX_FILENAME_LENGTH     256
./include/common.h:#define MAX_HOST_ADDRESS_LENGTH 256 /* max size of a host address */
./include/common.h:#define MAX_COMMAND_ARGUMENTS   16
./include/common.h:#define MAX_PACKETBUFFER_LENGTH 1024 /* max amount of data we'll send in one query/response */
./contrib/nrpe_check_control.c:#define MAX_CHARS 1024
./configure:#define MAX(x, y) ((x) > (y) ? (x) : (y))
./src/nrpe.c:#define MAXFD                   64
./src/snprintf.c:#define MAX(p,q) (((p) >= (q)) ? (p) : (q))


MAX_INPUT_BUFFERとMAX_PACKETBUFFER_LENGTHというのが怪しそうです。
cat ./include/common.h
#define MAX_INPUT_BUFFER 2048 /* max size of most buffers we use */
.....
#define MAX_PACKETBUFFER_LENGTH 1024 /* max amount of data we'll send in one query/response */


まずは、MAX_PACKETBUFFER_LENGTHを長くしてみます。
vim ./include/common.h
~
//#define MAX_PACKETBUFFER_LENGTH 1024 /* max amount of data we'll send in one query/response */
#define MAX_PACKETBUFFER_LENGTH 10240 /* max amount of data we'll send in one query/response */
~

そしてこのソースからインストールし直します。
(既存のcfgファイルなどは適宜バックアップしておきます。)
# mkdir /tmp/nagios
# cp -R /etc/nagios/* /tmp/nagios/# ./configure --enable-command-args
# make all
# make install
# mkdir /usr/local/nagios/etc
# cp ./init-script /etc/init.d/nrpe
# chmod 755 /etc/init.d/nrpe
# cp /tmp/nagios/* /usr/local/nagios/etc/nagios/
# /etc/init.d/nrpe restart

これでもう一度監視サーバーからcheck_nrpeを叩いてみます。
# /usr/lib64/nagios/plugins/check_nrpe -H xxx.xxx.xxx.xxx -c check_log3 -a '/etc/httpd/logs/access_log' '/etc/nagios/seeks/check_log3.httpd.seek' 'hoge' 1 5
CHECK_NRPE: Received 0 bytes from daemon.  Check the remote server logs for error messages.

エラーになりました。メッセージ通りリモートサーバー側のログを見てみます。
Jan 27 18:40:26 ip-10-132-10-146 nrpe[13812]: Error: Request packet had invalid CRC32.
Jan 27 18:40:26 ip-10-132-10-146 nrpe[13812]: Client request was invalid, bailing out...

CRC32ハッシュが間違っていると言われます。CRCは送信側と受信側のデータ整合性をチェックするために使われることが多いので、どうやら受信側(監視サーバー)でも同じパケット長に設定する必要があるようです。



そこで、監視サーバー側もソースからインストールし、同じくMAX_PACKETBUFFER_LENGTHを合わせてみます。
# cd /usr/local/src
# curl -L -o nrpe-2.13.tar.gz http://prdownloads.sourceforge.net/sourceforge/nagios/nrpe-2.13.tar.gz
# tar xzvf nrpe-2.13.tar.gz
# nrpe-2.13
# vim ./include/common.h
~
//#define MAX_PACKETBUFFER_LENGTH 1024 /* max amount of data we'll send in one query/response */
#define MAX_PACKETBUFFER_LENGTH 10240 /* max amount of data we'll send in one query/response */
~
# yum install make openssl openssl-devel -y
# cd /usr/local/src
# ./configure
# make all
# make install
# /usr/local/nagios/libexec/check_nrpe -H xxx.xxx.xxx.xxx -c check_log3 -a '/etc/httpd/logs/access_log' '/etc/nagios/seeks/check_log3.httpd.seek' 'hoge' 1 5
CRITICAL: Found 5 lines (limit=1/5): 219.117.233.241 - - [27/Jan/2013:18:32:19 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:20 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:20 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:21 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:21 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:21 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:22 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:22 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:22 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:18:32:23 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel M

うーん、、これでも途切れるようです。


次に、MAX_INPUT_BUFFERも長さを変えてみます。長さは念のためMAX_PACKETBUFFER_LENGTHと同じ倍率で変更します。

まずは、リモートサーバーから
vim ./include/common.h
~
//#define MAX_INPUT_BUFFER  2048    /* max size of most buffers we use */
#define MAX_INPUT_BUFFER    20480
~
# /usr/local/nagios/libexec/check_nrpe -H xxx.xxx.xxx.xxx -c check_log3 -a '/etc/httpd/logs/access_log' '/etc/nagios/seeks/check_log3.httpd.seek' 'hoge' 1 5
CRITICAL: Found 5 lines (limit=1/5): 219.117.233.241 - - [27/Jan/2013:19:12:40 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:41 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:41 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:42 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:42 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:42 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:43 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:43 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:43 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:44 +0900] "GET /test/hoge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:44 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:45 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 54.248.235.241 - - [27/Jan/2013:19:12:45 +0900] "GET / HTTP/1.0" 200 1 "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)" 219.117.233.241 - - [27/Jan/2013:19:12:45 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:45 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:47 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:47 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:48 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:48 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:49 +0900] "GET /test/moge HTTP/1.1" 404 286 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17" 219.117.233.241 - - [27/Jan/2013:19:12:49 +0900] "GET /favicon.ico HTTP/1.1" 404 288 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17"

おぉ、全部取得出来ました!




結果


./src/nrpe.cや./src/check_nrpe.cの処理も合わせ見ると、どうやら、長い出力を得るには、

リモートサーバー側
  • MAX_INPUT_BUFFER(内部で貯めこむバッファ長)
  • MAX_PACKETBUFFER_LENGTH(nrpeが送信するバッファ長)
監視サーバー側
  • MAX_PACKETBUFFER_LENGTH(chech_nrpeが受信するバッファ長)

の変更が必要なようです。

また監視サーバー側はcheck_nrpeコマンドの直叩きだったので、うまくいけましたが、既存のnagiosがyumのnrpeを使おうとしているので、check_nrpeは失敗します。




そこで、コマンドの定義でソースからインストールしたcheck_nrpeを使用するように変更しておきます。
#vim /etc/nagios/objects/commands.cfg
~
define command {
    command_name    check-nrpe
#    command_line    $USER1$/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$
   command_line  /usr/local/nagios/libexec/check_nrpe/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$
}
~
# /etc/init.d/nagios restart


すると、nagiosから接続した場合でも以下のように必要な長さのログが取得できました。





まとめ


nrpeがこの項目を設定ファイルではなくハードコードしているのはおそらく受信側と送信側を個別に変えてエラーになることを防ごうとしているのかもしれませんが、そのために意外と面倒なことになってしまいました。

また、今回は、yumと動作を比較しやすくするためにyumのパッケージを残したままソースインストールをしましたが、
本来はyumを削除してから入れなおしたほうが安全ですし、--prefixなどでyumと同じ位置に配置するようにするともっとスマートかも知れません。

今回は以上です。



2013年1月22日火曜日

Redisってなんじゃ?(レプリケーションとクラスタリング)

Redisはとても高速ですが、メモリ量の圧迫や負荷によっては分散が必要な場合があります。
分散の種類としては、レプリケーションとクラスタリングがあります。

レプリケーションは、マスタに書き込んだデータをスレーブにコピーすることで、書き込みはマスタ、読み取りはスレーブと担当を分けることで負荷を分散させます。読み取りにはマスタも参加することもあります。



クラスタリングは、キーのハッシュ値などによって保存するサーバーが決まり、書き込みと読み取りの両方を同時に負荷分散します。





レプリケーション


レプリケーションはRedisの基本機能として提供されているので、すぐに利用できます。
設定も簡単で、スレーブサーバーの設定ファイルに1行追記するだけです。

それでは試してみます。ここではRedis1をマスター、Redis2をスレーブとします。


インストール&設定

●Redis1(10.0.0.10), Redis2(10.0.0.20)

インストールします。
# cd /usr/local/src
# curl -OL http://redis.googlecode.com/files/redis-2.6.4.tar.gz
# tar xzf redis-2.6.4.tar.gz
# cd redis-2.6.4
# make
# make install
# mkdir /etc/redis /var/lib/redis
# cd /usr/local/src/redis-2.6.4/
# cp redis.conf /etc/redis/
2013/01/22追記:redis-2.6.3だとスムーズに行かない環境があるようなので、記事中のバージョンをredis-2.6.4に変更しました。


●Redis2

スレーブ側の設定ファイルでマスタを指定します。
# vim /etc/redis/redis.conf
---
slaveof 10.0.0.10 6379
---


●Redis1, Redis2

起動します。
# nohup redis-server /etc/redis/redis.conf &



確認

●Redis1

書き込みます。
# redis-cli
redis 127.0.0.1:6379> set hello "world"
OK
redis 127.0.0.1:6379> get hello
"world"


●Redis2

マスタで書き込んだキーを読みとります。
# redis-cli
redis 127.0.0.1:6379> get hello
"world"

ちゃんと取得出来ました。



クラスタリング


現時点での最新安定バージョン2.6.9ではまだクラスタリングはサポートされておらず、unstable版に含まれていす。
この機能はまだ不安定なため、正式サポートを待つ形になります。
別の方法として、twemproxyというmemcached, redisプロトコルに対応したtwitter製のプロキシを使うことで簡単なクラスタリングを実現できます。

ちなみにmemcachedプロトコルでのtwemproxyの使用はこちらが参考になります。
suz-lab:TwemproxyからElastiCacheに分散(同じキーは同じElastiCacheへ)してみる



インストール&設定

●Redis1(10.0.0.10), Redis2(10.0.0.20)

redisは普通にインストールします。
# cd /usr/local/src
# curl -OL http://redis.googlecode.com/files/redis-2.6.4.tar.gz
# tar xzf redis-2.6.4.tar.gz
# cd redis-2.6.4
# make
# make install
# mkdir /etc/redis /var/lib/redis
# cd /usr/local/src/redis-2.6.4/
# cp redis.conf /etc/redis/
# nohup redis-server /etc/redis/redis.conf &
2013/01/22追記:redis-2.6.3だとスムーズに行かない環境があるようなので、記事中のバージョンをredis-2.6.4に変更しました。


●Proxy(10.0.0.30)

twemproxyと必要なautoconfツール群をインストールします。
yumのデフォルトで入るバージョンは古いためエラーになるのでソースから入れます。
# cd /usr/local/src
# curl -OL http://redis.googlecode.com/files/redis-2.6.4.tar.gz
# tar xzf redis-2.6.4.tar.gz
# cd redis-2.6.4
# make
# make install

# cd ../
# curl -OL http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
# tar xvzf autoconf-2.69.tar.gz
# cd autoconf-2.69
# ./configure
# make
# make install

# cd ../
# curl -OL http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
# tar xvzf automake-1.12.tar.gz
# cd automake-1.12
# ./configure
# make
# make install

# cd ../
# curl -OL http://ftp.jaist.ac.jp/pub/GNU/libtool/libtool-2.4.2.tar.gz
# tar xvzf libtool-2.4.2.tar.gz
# cd libtool-2.4.2
# ./configure
# make
# make install

# cd ../
# curl -OL https://github.com/twitter/twemproxy/archive/v0.2.2.zip
# unzip v0.2.2.zip
# cd twemproxy-0.2.2/
# /usr/local/bin/autoreconf -fvi
# ./configure
# make
# make install
2013/01/22追記:redis-2.6.3だとスムーズに行かない環境があるようなので、記事中のバージョンをredis-2.6.4に変更しました。


twemproxyの設定ファイルを作成します。
# cd ~/
# vim nuckcracker.yml
redis:
  listen: 0.0.0.0:22222
  hash: fnv1a_64
  distribution: ketama
  auto_eject_hosts: true
  timeout: 400
  redis: true
  servers:
   - 10.0.0.10:6379:1 twemredis1
   - 10.0.0.20:6379:1 twemredis2

  • listen:twemproxyのリスンポートの指定です。 
  • distribution:分散方式(ketama=ConsistentHashingのことです)
  • hash:ハッシュ方式(fnv系はmd5などと比べて短い値でも分散率が高いですが、衝突率はmd5より高いようです) 
  • auto_eject_hosts:失敗が多かったホストを自動的にリングから外されます。 
  • timeout:分散先サーバーとの通信タイムアウト時間(ミリ秒)です。 
  • redis:trueならredisプロトコル、falseならmemcachedプロトコルになります。 
  • servers:分散先のサーバー群です。


起動します。
# nutcracker -c nutcracker.yml -d


確認

●Proxy

いくつかキーとバリューをsetします。
# redis-cli
redis 127.0.0.1:22222> set hoge "moge"
OK
redis 127.0.0.1:22222> get hoge
"moge"
redis 127.0.0.1:22222> set hello "world"
OK
redis 127.0.0.1:22222> get hello
"world"


●Redis1

セットしたキーを取得します。
いくつかは取得できません。
# redis-cli
redis 127.0.0.1:6379> get hoge
"moge"
redis 127.0.0.1:6379> get hello
(nil)


●Redis 2

セットしたキーを取得します。
Redis1で取得できなかったキーは取得でき、Redis1 で取得できたキーは取得出来ません。
# redis-cli
redis 127.0.0.1:6379> get hoge
(nil)
redis 127.0.0.1:6379> get hello
"world"


●Proxy

Proxy側からは両方取得できます。
# redis-cli
redis 127.0.0.1:22222> get hoge
"moge"
redis 127.0.0.1:22222> get hello
"world"

きちんと分散されているようです。



再ハッシュ

自動再ハッシュはサポートされていないので、SLAVEのONとOFFを切り替えを行なったりして、データをコピーしながら再構成していくような工夫が必要なようです。

たとえば、1つのRedisサーバーからもう1台追加してクラスタリングする場合、まずSLAVEOFで1台目をマスタにしてデータをコピーします。その後twemproxyを起動して、2台目をSLAVE解除します。
2台目にコピーされたデータのうち、2台目に割り振られるハッシュキーだけが使用され、約半分は1台目に割り振られるため、使われなくなります。


このようにしてレプリケーションやクラスタリングで負荷を分散させたり、ホットスタンバイをつくることができます。

レプリケーションとクラスタリングを組み合わせたり、シングルポイントとなるtwemproxyを冗長化させたりすることも可能ですね。


以上です。


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というとても便利なグラフ化ツールで、いろいろなリソースを可視化してみると、運用の負荷軽減だけではなく、ユーザーの動向などもわかってお得ですね。
以上です。


2013年1月9日水曜日

SESってなんじゃ?(突然postfixでSMTPリレー)

先日、突然EC2からのメール送信が必要ということが発覚したときの顛末を書いてみます。

公開日当日に、突然以下のような内容のメールが届きました。

Dear EC2 Customer,
You recently reached a limit on the volume of email you were able to send out of SMTP port 25 on your instance:xxxxxxx

EC2内のMTAからメールを送信するプログラムでは、そのままだと、上記のようなメールが届き、メールの送信制限に引っかかります。

そのため、メールの上限解除申請フォームから解除申請とメールドメインの逆引き申請を行う必要がありました。

この時点で例えば以下の内容(メールアドレスやサーバー名などはサンプルです)で、メールを送ってみると、
# php -a
> mail("memorycraft@gmail.com", "Subject1", "Body", "From: server@memorycraft.jp");

この時点ではメールヘッダの内容は、例として以下のように、なっていました。
Return-Path: <root@ip-10-132-10-146.localdomain>
Received: from ip-10-132-10-146.localdomain (ec2-54-248-82-123.ap-northeast-1.compute.amazonaws.com. [54.248.82.123])
        by mx.google.com with ESMTP id qc4si62972240pbb.326.2013.01.08.08.20.17;
        Tue, 08 Jan 2013 08:20:17 -0800 (PST)
Received-SPF: neutral (google.com: 54.248.82.123 is neither permitted nor denied by best guess record for domain of root@ip-10-132-10-146.localdomain) client-ip=54.248.82.123;
Authentication-Results: mx.google.com; spf=neutral (google.com: 54.248.82.123 is neither permitted nor denied by best guess record for domain of root@ip-10-132-10-146.localdomain) smtp.mail=root@ip-10-132-10-146.localdomain
Received: by ip-10-132-10-146.localdomain (Postfix, from userid 0)
 id E5D5C1E1D; Wed,  9 Jan 2013 01:20:16 +0900 (JST)

Received-SPFとAuthentication-Resultsがneutralなのがまずそうです。
これらがpassになっていないと、メーラーによっては迷惑メールとして扱われてしまうことがあるようです。


一応申請したものの、すぐに解除されるわけではないので、次善策としてSESを利用するのはどうかという意見がありました。
そこで、まずプロダクション使用申請フォームからSESのプロダクション申請を行いました。
ただし、これもすぐには通らないので、既にプロダクションモードになっている別の社内アカウントのSESを一時的に使うことにしました。

まず、SESで送信元のメールアドレスを認証させるために、メールアドレスを登録します。



確認メールが送られてくるので、リンクをクリックして認証します。すると以下のような画面が現れます。



また、SESのAPIを使うようなプログラムの変更はなるべく避けたいのでメールを送信しているEC2内のpostfixからリレーしないといけません。
ここで、suz-labブログの記事「PostfixからSESにリレー(stunnel使わない編)」が助けてくれました。

それにしたがって設定していきます。
/etc/ssl/certs/ca-bundle.crtがあることを確認して、/etc/postfix/main.cfの末尾に以下を追記します。
relayhost = email-smtp.us-east-1.amazonaws.com:25
smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt


次に、sasl_passwordファイルを作ります。
SMTP用のIAMを作るためにAWS ConsoleのSESのSMTP Settingsから「Create My SMTP Credentials」ボタンでアクセスキーとシークレットキーを取得します。
muramasaさんによると、ここではIAMで普通に作成してもダメで、SESのSMTP Settingsから作成しないと行けないそうです。



そして、取得したアクセスキー、シークレットキーを元に、sasl_passwordファイルを作成します。
# echo "email-smtp.us-east-1.amazonaws.com:25 xxxxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyy" > /etc/postfix/sasl_password


sasl_passwordファイルを検索テーブルに設定します。
# postmap hash:/etc/postfix/sasl_passwd

再起動します。
# /etc/init.d/postfix restart

これでメールを送ってみます。
# php -a
> mail("memorycraft@gmail.com", "Subject2", "Body", "From: server@memorycraft.jp");


メールが届きません。
/var/log/mailogを見てみると、、
Jan  9 03:06:09 ip-10-132-134-107 postfix/smtp[11252]: warning: SASL authentication failure: No worthy mechs found
Jan  9 03:06:09 ip-10-132-134-107 postfix/smtp[11252]: 9CA2413E5: SASL authentication failed; cannot authenticate to server email-smtp.us-east-1.amazonaws.com[54.243.73.188]: no mechanism available

となっており、SASL認証に必要なライブラリが無いようです。
以下のように、ライブラリをインストールします。
# yum -y install cyrus-sasl-plain cyrus-sasl-md5

もう一度送信してみますが、メールは届きません。。

maillogには以下のようになっていました。
Jan  9 04:04:51 ip-10-132-10-146 postfix/qmgr[31664]: CE9C11E25: from=<root@ip-10-132-10-146.localdomain>, size=362, nrcpt=1 (queue active)
Jan  9 04:04:54 ip-10-132-10-146 postfix/smtp[31726]: CE9C11E25: to=<memorycraft@gmail.com>, relay=email-smtp.us-east-1.amazonaws.com[23.21.84.203]:25, delay=2.5, delays=0.03/0/1.6/0.87, dsn=5.0.0, status=bounced (host email-smtp.us-east-1.amazonaws.com[23.21.84.203] said: 554 Message rejected: Email address is not verified. (in reply to end of DATA command))

Email address is not verified. となっており、メールアドレスが認証されていないようです。 
これは、Fromヘッダでしか送信元を設定していないためで、Return-Path(エンベロープFrom)に送信元メールアドレスをセットしてあげる必要があるようです。


 この場合、送信コマンド側でMTAにReturnPathを渡します。 
PHPのmailコマンドの場合は以下のように第4引数で-fオプションを渡します。
# php -a
> mail("memorycraft@gmail.com", "Subject30", "Body", "From: server@memorycraft.jp", "-fserver@memorycraft.jp");

すると、正常に送信され、届いたメールのヘッダも以下のようにpassになりました。
Received-SPF: pass (google.com: domain of 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com designates 199.255.194.183 as permitted sender) client-ip=199.255.194.183;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com designates 199.255.194.183 as permitted sender) smtp.mail=0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com
Return-Path: 0000013c1b944998-1b5393d9-728a-496a-8c9d-2bbf57427150-000000@amazonses.com


ちなみに、

cakePHPの場合
$config['additionalParameters'] = '-f '.$from_address;
$email = new CakeEmail();
$email->transportClass()->config($config);
$email->from(array($from_address=>'Information'))
->to($target)
->subject($mail_title)
->send($mail_message);


FuelPHPの場合
$email = Email::forge();
$email->from($from_address);
$email->to($target);
$email->subject($mail_title);
$email->return_path($from_address);
$email->body($mail_message);
$mail->send();

のようにするようです。
ひとまず解決し、ほっとしました。

意外と見落としがちなメール送信ですが、いざというとき、どの方法をとっても新規からではなんらかの申請が必要になり、即時反映は難しそうでした。
こういった場合のために、常に一時的なリレー用のSESを確保しておくのは有りかも知れません。

以上です。