grepコマンドをHadoop Streaming上で実行する

Amazon EMR

S3上に大量に存在するログファイルをgrepコマンドで検索したいと思ったことがある方は結構いらっしゃるのではないでしょうか。今回はHadoop Streamingを利用してS3上のファイル群に対してgrepコマンドを実行したいと思います。

Hadoop Streamingの概要については以下をご参照下さい。

EMRでHadoop Streamingジョブを実行する

実行環境

  • emr-5.5.0 でアプリケーションは Hadoop のみ
  • ハードウェア構成は m1.medium を 1 台(検証用なのでマスターノードのみ)
  • 東京リージョン

EMRクラスタの作成

まずはEMRクラスタを作成します。AWS CLIを利用する場合は以下のようなコマンドになります。SubnetId, log-uriを自身の環境に合わせて書き換えて下さい。

aws emr create-cluster --auto-scaling-role EMR_AutoScaling_DefaultRole --applications Name=Hadoop \
  --ec2-attributes '{"InstanceProfile":"EMR_EC2_DefaultRole","SubnetId":"subnet-dXXXX"}' \
  --service-role EMR_DefaultRole --enable-debugging --release-label emr-5.5.0 \
  --log-uri 's3n://aws-logs-XXXX-ap-northeast-1/elasticmapreduce/' --name 'grep' \
  --instance-groups '[{"InstanceCount":1,"InstanceGroupType":"MASTER","InstanceType":"m1.medium","Name":"Master Instance Group"}]' \
  --scale-down-behavior TERMINATE_AT_INSTANCE_HOUR --region ap-northeast-1

実際に実行すると作成したEMRクラスタのClusterIdが表示されます。ClusterIdは次のgrepジョブの実行する際に利用するので控えておいて下さい。

$ aws emr create-cluster ...
{
    "ClusterId": "j-XXXXXX"
}

grepジョブの実行

grepジョブを実行します。S3上に存在するELBのアクセスログに対してgrepコマンドを実行してみたいと思います。検索キーワードはhbaseにします。通常であればgrepジョブの処理対象となる入力データの用意してS3にアップロードを行う必要がありますが、今回はAWSが用意しているサンプルを利用したいと思います。

  • 入力データ : s3://ap-northeast-1.elasticmapreduce.samples/elb-access-logs/data/
  • Mapper : grepコマンド(grep hbase
  • Reducer : なし

AWS CLIを使ってgrepジョブを登録したいと思います。引数にすると改行が入れられず見づらいのでJSONファイルを利用したいと思います。まずは以下のJSONファイルを作成して下さい。ファイル名はgrep_step.jsonとし、出力先であるs3://mybucket/outputを自身の環境に合わせて書き換えて下さい。

[
  {
     "Name": "grep",
     "Type": "STREAMING",
     "ActionOnFailure": "CONTINUE",
     "Args": [
         "-D stream.non.zero.exit.is.failure=false",
         "-D mapreduce.job.reduces=0",
         "-D stream.map.output=keyonlytext",
         "-mapper",
         "grep hbase",
         "-input",
         "s3://ap-northeast-1.elasticmapreduce.samples/elb-access-logs/data",
         "-output",
         "s3://mybucket/output"]
  }
]

ではgrep_step.jsonを利用してgrepジョブを登録したいと思います。j-XXXXXXXを起動したEMRクラスタのClusterIdに書き換えて以下のコマンドを実行して下さい。

aws emr add-steps --cluster-id j-XXXXXXX --steps file://./grep_step.json --region ap-northeast-1

m1.mediumでマスターノード1台のみ構成なので、10分ほどするとS3に処理結果が出力されると思います。入力ファイルごとにgrepコマンドを実行した結果のファイルが出力されていることが分かります。

grep-on-hadoop-streaming_1

ソースの解説

まずは全体像です。入力であるs3://ap-northeast-1.elasticmapreduce.samples/elb-access-logs/data配下には28個のELBのログファイルが存在します。それぞれのファイルにgrepコマンドが実行され、その結果が part-00000 〜 part-00027 というファイル名で出力されます。

grep-on-hadoop-streaming_2

あとはgrep_step.jsonに記載されているオプションについての解説です。

  • -D stream.non.zero.exit.is.failure=falseはHadoop Streamingで実行したコマンドのexit statusが0ではない場合の挙動についてのオプションです *1。デフォルト値はtrueであり、exit statusが0ではない場合にジョブが失敗します。grepコマンドは検索キーワードに対応する単語が存在しない場合にexit statusが1となるためfalseを指定しています。
  • -D mapreduce.job.reduces=0はReducerの数を指定するオプションです。0を指定した場合はReduce処理を利用しないという意味になります *2。結果としてMap処理の出力結果がそのままジョブの出力結果としてS3に出力されるようになります。
  • -D stream.map.output=keyonlytextはMap処理の出力内容をKeyのみにするというオプションです *3。Hadoop Streamingの出力は通常KeyとValueが対になった形で出力されますが、今回はgrepコマンドを利用しているためKeyしか存在しません(検索結果はKey部分に設定されます)。そのため、このオプションで明示的にKey部分のみを出力するように指定しています。

まとめると以下になります。

  • Hadoop StreamingはPythonやRubyのようなスクリプト言語だけでなくOSのコマンドを利用できる
  • 利用するOSのコマンドが正常な処理をした際にexit statusとして0ではない値を返す場合は-D stream.non.zero.exit.is.failure=falseオプションを指定する
  • Map処理しか利用しない場合は-D mapreduce.job.reduces=0オプションを指定する。更に値がKey-Value形式ではない場合は-D stream.map.output=keyonlytextオプションを指定する

前後の行も抽出する

アプリケーションログを調査する際は調査対象行の前後の行も見たいことがありますよね。grepには検索対象行の前後の行も出力するオプションがあります。ということで次は-C 3というオプション付きでgrepコマンドジョブを実行したいと思います。なお、-C 3は検索対象行の前後の3行も出力するという意味です。grep_step2.jsonを作成して下さい。変更点はgrepコマンドのオプションを追加しただけです。出力先であるs3://mybucket/outputを自身の環境に合わせて書き換えて下さい。

[
  {
     "Name": "grep2",
     "Type": "STREAMING",
     "ActionOnFailure": "CONTINUE",
     "Args": [
         "-D stream.non.zero.exit.is.failure=false",
         "-D mapreduce.job.reduces=0",
         "-D stream.map.output=keyonlytext",
         "-mapper",
         "grep -C 3 hbase",
         "-input",
         "s3://ap-northeast-1.elasticmapreduce.samples/elb-access-logs/data",
         "-output",
         "s3://mybucket/output"]
  }
]

ファイルを作成したら以下のコマンドでジョブを登録してください。

aws emr add-steps --cluster-id j-XXXXXXX --steps file://./grep_step2.json --region ap-northeast-1

処理が終わったらファイルを開いてみて下さい。今回はpart-00000を開いてみます。-C 3オプションによって検索対象行とその前後の行が出力されていることが分かるかと思います。

2014-09-26T22:01:40.618808Z lb-demo 241.32.185.99:42634 243.6.147.57:8888 0.000057 0.04128 0.00007 200 200 0 1084 "GET http://www.abcxyz.com:80/jobsub/static/art/icon_jobsub_48.png HTTP/1.1"
2014-09-26T22:01:40.620010Z lb-demo 254.37.234.96:8391 247.156.57.215:8888 0.000041 0.04217 0.000049 200 200 0 614 "GET http://www.abcxyz.com:80/metastore/static/art/icon_metastore_48.png HTTP/1.1"
2014-09-26T22:01:40.618562Z lb-demo 250.202.133.106:7467 247.132.83.248:80 0.000076 0.048543 0.000046 200 403 0 2022 "GET http://www.abcxyz.com:80/pig/static/art/icon_pig_48.png HTTP/1.1"
2014-09-26T22:01:40.641132Z lb-demo 246.82.88.168:7984 254.40.246.24:8888 0.00008 0.02909 0.000035 200 200 0 331 "GET http://www.abcxyz.com:80/hbase/static/art/icon_hbase_48.png HTTP/1.1"
2014-09-26T22:01:40.687830Z lb-demo 244.123.16.98:52657 252.30.168.250:8888 0.000065 0.013382 0.000042 200 200 0 1119 "GET http://www.abcxyz.com:80/oozie/static/art/icon_oozie_dashboard_48.png HTTP/1.1"
2014-09-26T22:01:40.738068Z lb-demo 249.155.91.61:7467 253.221.67.7:80 0.000064 0.03898 0.000043 200 200 0 970 "GET http://www.abcxyz.com:80/oozie/static/art/icon_oozie_editor_48.png HTTP/1.1"
2014-09-26T22:01:40.741948Z lb-demo 249.155.91.61:7984 243.123.209.138:8888 0.000065 0.049368 0.000035 200 404 0 811 "GET http://www.abcxyz.com:80/static/art/home.png HTTP/1.1"
--
2014-09-26T22:24:42.819993Z lb-demo 254.48.89.244:33736 252.30.168.250:443 0.000049 0.013408 0.00004 200 200 0 2022 "GET http://www.abcxyz.com:80/pig/static/art/icon_pig_48.png HTTP/1.1"
2014-09-26T22:24:42.870757Z lb-demo 254.253.190.42:7467 247.132.83.248:8888 0.000045 0.01706 0.000041 200 404 0 1084 "GET http://www.abcxyz.com:80/jobsub/static/art/icon_jobsub_48.png HTTP/1.1"
2014-09-26T22:24:43.093337Z lb-demo 252.234.144.48:7467 247.115.255.20:8888 0.000085 0.014759 0.000059 200 403 0 614 "GET http://www.abcxyz.com:80/metastore/static/art/icon_metastore_48.png HTTP/1.1"
2014-09-26T22:24:43.164302Z lb-demo 251.97.187.147:33736 251.71.203.104:443 0.000049 0.024151 0.000041 200 404 0 331 "GET http://www.abcxyz.com:80/hbase/static/art/icon_hbase_48.png HTTP/1.1"
2014-09-26T22:24:43.166399Z lb-demo 252.99.57.64:64245 254.48.89.244:8888 0.000045 0.023397 0.000038 200 200 0 1119 "GET http://www.abcxyz.com:80/oozie/static/art/icon_oozie_dashboard_48.png HTTP/1.1"
2014-09-26T22:24:43.262516Z lb-demo 255.184.170.166:33736 244.51.248.95:443 0.000042 0.069881 0.000038 200 200 0 83760 "GET http://www.abcxyz.com:80/static/ext/fonts/fontawesome-webfont.woff?v=4.1.0 HTTP/1.1"
2014-09-26T22:24:43.265914Z lb-demo 250.134.153.126:7467 253.116.28.36:8888 0.000057 0.093497 0.000037 200 500 0 970 "GET http://www.abcxyz.com:80/oozie/static/art/icon_oozie_editor_48.png HTTP/1.1"
--

EMRクラスタの削除

作成したEMRクラスタは忘れずに削除しましょう。AWS CLIで削除する場合は以下のコマンドで削除して下さい。

aws emr terminate-clusters --cluster-id j-XXXXXXX --region ap-northeast-1

削除できているかの確認は以下のコマンドを利用して下さい。"TERMINATED"と出力されると削除済みという意味です。

aws emr describe-cluster --cluster-id j-XXXXXXX --query Cluster.Status.State --region ap-northeast-1

最後に

S3のログファイルを検索する方法は色々あると思いますが、普段利用しているgrepコマンドを直接実行できるのはHadoop Streamingの利点かと考えます。

脚注

  1. "User can specify stream.non.zero.exit.is.failure as true or false to make a streaming task that exits with a non-zero status to be Failure or Success respectively. By default, streaming tasks exiting with non-zero status are considered to be failed tasks." https://hadoop.apache.org/docs/r2.7.3/hadoop-streaming/HadoopStreaming.html#How_Streaming_Works
  2. https://hadoop.apache.org/docs/r2.7.3/hadoop-streaming/HadoopStreaming.html#Specifying_Map-Only_Jobs
  3. https://issues.apache.org/jira/browse/MAPREDUCE-5457