in AWS

Elastic Map Reduce(EMR)をaws-sdk for rubyでAPI経由で使うときのjob-flowの書き方

こんにちは。@srockstyleです。

今日は久々に技術ネタです。

AWS、使ってます?

いろんなサービスあって便利ですよね。

仕事でHadoopのクラウド版Elastic Map Reduce通称EMRを使う機会がありました。

ところがその中のAPI経由でのアクション定義であるjob-flowのRubyでの書き方がどこにもドキュメントがないんです。あっても古かったり動かなかったりして、かなり苦しみました。いちおうEMR CLIってのが提供されていて、そちらのほうがドキュメント豊富なんですがこれまた地雷でRuby 1.8でしか動きません。つまり日常的に2.1以上使っている僕らの選択肢はaws-sdkのみになるんです。1.8ってなんだよ……サポート終了してんじゃん……

というわけで答えをやっとこさ出したのでそれについて書きたいと思います。

ちなみにHadoopについての説明は省略しますね。

AWS Console上からの作業

実は事前でのAPI経由でのクラスタの作成〜ジョブ登録とかは必要ありません。API経由ですべて行うのでaws-sdk for ruby上の操作ですべて完結します。あ、もちろんアカウントとaccess_key周りは準備してあること前提ですよ!

S3にファイルを準備する

S3にバケットを作成し、その中にフォルダを作成してバッチごとにさらにフォルダをわけ、mapper.rbとreducer.rbを作りました。ログデータは事前にFluentdなどを使ってアップする処理をあらかじめ書いておくとよいと思います。ログの中身、mapper.rb、reducer.rbの中身については省略します。多分この辺りは同僚の歌野さんが詳しいので彼が記事を書いてくれるでしょう。(と、むちゃぶっておく)

Job-flowを書く準備

今回APIの処理はRailsで使うlib/tasksの下に書きました。ここに書いておけばとりあえずrails runnerで事前準備からジョブの起動までやってくれるので便利です。実運用に入るときはWheneverなどでschedule.rbに登録しておき、capistranoでデプロイするときにcronに登録&リフレッシュする処理を入れておけば定期的にジョブが立ち上がってくれるので便利です。

これで準備終了です。じゃあここに def self.job_createみたいな書き出しでメソッドを作ります、

Job-flowの書き方

Job-flowですがこれ、job_flowが正しいのかようわからんですがとりあえずJob-flowとしておきます。誰か正しい呼び方おしえてください。昔MySQLをmysqlと書いてしまって上司に怒られたことがありました。まあいいんですけど。

これを書くときは変数にハッシュを格納するようにして書いていきます。今回はRubyで実行するのでhadoop-streamingを使いました。

これでオッケーです。説明していきます。

EMRのオブジェクトの作成

まずはオブジェクトがないと始まらないわけなんですが、ここはエンドポイントを指定しないと動きません。

こちらの記事ではエンドポイントなしで動作したよーって言っているのですが僕のところではEMRのエンドポイントを指定しないと「getaddrinfo: nodename nor servname provided, or not known ( SocketError)」みたいなエラーがでて無理でした。自分の使っているリージョンと同じエンドポイントを指定してあげてください。

EMRのjob_flowをRubyで書く

emr_result = emr.job_flows.create〜ですが、ここからは順番として

  1. 作るクラスタのスペックと設定
  2. 実際に行ってほしい作業

といった順番で書きます。

設定は基本Rubyのハッシュなので、スネークケースが正解です。ですが、なんせあちこちで書かれているjob_flowの基本がjsonだったりしてキャメルケースで書かれていることが多くて、「InstanceType」なら「instance_type」だろうみたいな感じで書いたら通ることが多かったのでこのあたりは適当に試してみてください。

インスタンス作るときはスポットインスタンスも使えます。その場合は「:market => “SPOT” 」と「:bid_price => “0.6”」みたいな感じで値段とスポットインスタンスつかうよってことinstancesの中で指定してください。ただ、解析中に競売に負けて落とされるとこまるので今回は採用しませんでした。ありえるんだろうか。

さて肝心のstepsの中身ですが、単純にHadoopでのMapReduceを一度かましたいだけならこの中を一回かけばいいだけです。もし同じクラスタ、同じジョブの中で別設定のMapReduceを流したいのであればそれだけStepsの中身を増やせばよくて、別に一回ときまっているわけではありません。あとEMRではHiveとPigが使えるそうなので、試してみても良いでしょう。

今回は一回だけで良かったので、Stepsは一回になりました。RubyでMapperとReducerが書かれているので、Hadoop-streamingです。

ここで注意しなければならないのは「:hadoop_jar_step」の中で、hadoop-streamingのバイナリ指定とオプション指定のところです。バイナリのパスはEMRのインスタンスの中に準備されているようなので「:jar => ‘/home/hadoop/contrib/streaming/hadoop-streaming.jar’」で決めうちです。オプションはMapperスクリプト、Reducerスクリプト、Hadoopに渡すデータファイルが入ったフォルダ、Hadoopが結果を吐き出すフォルダを指定します。

ここはあちこちで「s3n://」みたいな書き方と「s3://」で書くやり方が混在しています。酷い記事だとこっちはnつけてあっちはnつけないみたいなのもあったりして酷かったんですが、公式によるとここはぶっちゃけどっちでもいいようです。僕は統一させたかったのと分かりやすくしたかったんでnなしを選びました。

Hadoopにログを渡すフォルダですが、ここはデータをいれてあらかじめ準備しなければならないので、最初から作っておきます。ファイルはすべて細切れにされて標準入力でMapperスクリプトに渡されるので、複数あっても大丈夫です。ここは必ずフォルダを指定してください。

出力も標準入力でReducerスクリプトに渡されますが、Reducerを通ったあと出力先ログフォルダに吐き出されます。フォルダは自動で作成されるので、こちらで作っておく必要はありません。逆に出力先フォルダを手で作ってしまうと「フォルダがあるから出力先を作れないよ!」って怒られてエラーになります。

スクリプトですが外部のモジュールを読み込むことは基本できないみたいです。ファイルパス指定してRequireすればいけるんだろうか。そこは試してないです。ただ、オプションとして指定するときに実行コマンドを指定してあげます。「ruby s3://****/mapper.rb」といった感じに書きますが、実行バイナリのパスは書かなくて大丈夫なようです。

オプション全体に言えることですが、これらの指定の仕方は「”-オプション名”,”値”」というやり方で指定するのが正解でした。はじめは「:mapper => “s3://*****/mapper.rb”」とか試してみたんですがどれもうまく行かず、最終的にはそちらのやりかたに。なので気をつけてください。書くときは「”-mapper”,”s3://*******/mapper.rb”」と書くのが正解です。

まとめ

いかがなもんでしょーか。

EMRをaws-sdk for rubyから叩くときの情報はネット上には非常に少なく、あっても間違ってるものばっかりで苦労しました。でもEMR自体は素敵なサービスだし、Hadoopの面倒くさいクラスタ作成を自分でやらなくてよいので、うまく使えれば工数はだいぶ減るでしょう。

ビッグデータという言葉がでて久しい昨今ですが、ようやく自分もこういうのに触れることができてよかったです。

以下の外人さんのブログが参考になりました!

HowTo: AWS CLI Elastic MapReduce – Streaming Job Flow :Dowd and Associates