2011/09/27

るびまに寄稿しました。

ご無沙汰しております。意外と何も知らないことには定評のあるhiroshiです。

今回は簡便ながら、るびまにchefについての記事を寄稿しましたのでお知らせです。


Rubyist Magazine 0035 号


なんでも7周年記念だそうで、おめでとうございます!
こういった活動を7年続けるということはすごいことだと思います。

ちなみにDECOLOGはまだ5年目です。確か。

2011/06/27

第1回 DevOpsカンファレンスのお礼と話したことの補足



こんにちは。「ふいんき」と書いて「なぜか変換できない」の流れをやろうとしたらgoogle日本語入力だと余裕で変換できてしまい、沸き起こる複数の感情と戦っているhiroshiです。

去る2011/06/25(金)、DevOpsカンファレンスという勉強会でスピーカーをやらせてもらいました。そのお礼と、お話の補足をさせていただこうと思います。
勉強会での発表は社を代表した内容にしたつもりですが、このエントリは、けっこう個人的な想いを多めにしてますので、もし異論反論ありましたら@hiroshi19790209までください><。

去年末の「ZABBIX勉強会」以来のスピーカーで、あのときの参加者が60人くらい、今回も最初は80人くらいの募集だったから「実際くるのは応募人数の8掛けで60人くらいだろうから、このくらいの人数だったら経験済みだぜ!」と思って立候補したら、参加枠が120人まで伸びた上に当日もほとんどまんま120人近くの方が参加してきて、@marqsさん恐るべしな感じでした。

謝辞
@marqsさん、@kuwa_twさん、ぼくにしゃべらせてくれてありがとうございました!また呼んでください!
@さん、「こんな感じの内容であってるのかなぁ」、とビクビクしていたのですが、「カルチャー」の話のおかげで、気楽に話せました。気楽過ぎたかもしれません><
@riywoさん、僕もシャイボーイなのでちょっとしかお話できなかったです。またゆっくりお話したいです。
CAのみなさん、会場提供・設営などお世話になりました!
ご参加くださったみなさん、ご清聴ありがとうございました。あえて苦言を呈させていただくと、カイジをご存知のかたは「圧倒的閃き・・・!」のところでもっと笑ってもらいたかったです。


お話の補足
僕は今回、せっかくだから楽しいほうがよかろーと思ったのと、根がシャイボーイなのでああいう感じじゃないと恥ずかしくて話せないので、結果ネタっぽい感じになってしまいましたが、本筋を外したつもりはありません。

すべては
それはみんながよろこぶことなのか
というところにつながります。

そして、「みんな」とはユーザーのみなさん、クライアントさん、協業さんだけでなく、自分たちも含んでいます。「自分たち」です。「自分」じゃありません。でも「自分たち」の中には「自分」も含まれています。


僕たちは別に少数でやることがポリシーなわけでもなんでもないので、これはただの制約にしか過ぎません。
この制約の中でサイトを安定させるだけでなく、進化させていかなければなりません。

って、そんなのどこだって同じ、あたりまえの話だと思うんですよね。6人が多数か少数かは状況によって違うわけで、人手なんてどこももっと欲しいと思ってると思います。
だから(人手の募集はなんとかするとして)そんな中どうやったら一番アウトプットが最大化するか、っていう話なんだと思います。

それを僕達は、6人いるメンバーをDevに寄らせたりOpsに寄らせたりしてやっているつもりです。

アウトプットを最大化するためにはいろいろなアプローチがあると思います。
  • 作業の効率化(自動化)
  • タスクの取捨選択
  • アウトソース
  • 情報共有
  • etc..

それぞれの中で
  • やらなければいけないこと
  • やるべきなこと(ベキ論的な意味で)
を冷静に見極めなければいけないと思っています。

特にIT界はベストプラクティスと言われる「あるべき事例集」みたいなものがたくさん流通している気がします。
僕の心が汚れているせいだとわかっていますが、それらからはなんとなく「これをやらないから、おまえんところはデスマになるんだ!」的なオーラがただよっているように思えます。
※それを提唱している人や、推し進めようとしている人からでているオーラではなく、ただ単に僕の汚れた心がWeb上あたりからそういうオーラを感じているだけです。念のため。

が、それらはすべては考え方の話で、前提条件が揃わなければそれは最高の選択肢でありうることは少ないと思っています。
※提唱している人や、推し進めようとしている人は、考え方の話をしているはずです。

特に収益を得ることの手段としてのITであればなおさらだと思います。

たとえばテストは書くべきだと思います。テストは拡張性、保守性、可読性を高めます。確実に入るであろう今後の変更を考えたらやらない手はありません。でも、常に今後のことを考えた選択肢が正しいとは限らないと思います。「今、ここで、リリースできないと今後なんかない!」というノリの状況は少なくないと思っています。そんな状況ではTDDの採用は正しいとはいえない選択肢だと思います。(テストを書いても開発スピードが落ちないという人しかいない場合を除く)

「今、ここで、リリースできないと今後なんかない!」という考え方を理解しようせず「TDD!TDD!」とか言っている人は、ユーザーのよろこびに比べ、自分のよろこびにかなり重きを置いている、といえると思います。

そして、よろこびが偏った選択肢は、サービス全体として非効率な選択肢になります。


DevとOpsの対立みたいな話の具体例は残念ながら(?)うちにはありません。DevとOpsの分けがないから。
でもサービス開発にあたって、技術チームとディレクションチームといった分けはあります。この分けも、「同じものを作ってるけど、やっていることがちょっと違うチーム」といった意味ではDevsとOpsの分けとそう性質は変わらないと思います。

が、やっぱり対立みたいなものはありません。

きっとお互いのことを尊重できているからだと思います。

同じものを作っているのになぜ対立が起きるのか、はいろんなきっかけがあると思いますが、たぶん会話が足りないからじゃないかなと思います。

「わかるけど、そうコトはシンプルじゃないんだよ」と言う人もいるかもしれませんが、もうこれくらいしか推すアイデアがないんです。

同じサービスを支えているのに対立するって、おかしいじゃないですか。

会話といっても、酒場に行って飲むとかだけじゃないと思うんです。
なんだったら、DevsとOpsで短期交換留学みたいのしてもいいかもしれないです。

今回のDevOpsに参加された方しかわからないかもしれないですが、家族の理解の件ででてきたBBQの話もこれと同じノリだと思ってます。

ところで、家族の理解の話は、「アラートの監視体制は全員。深夜休日は当番制」といった体制から「家族の理解が得られるのか」といった質問がきたと記憶しており、BBQの提案をついしてしまいましたが、そもそもうちのアラート運用はそんなきっつい運用であると思っていません。

発信されるアラートの閾値や種類の出しワケなど、工夫を重ねていけば、少なくとも家族の理解の話になるような事態にはならないと思っています。
たとえば、ハード障害とかは無理ですけど、同種のアラートはきっちり対応していけば確実に減らせます。
@riywoさんもプレゼンの中でおっしゃっていましたが、それをきっちりやると、本当にメールを見ただけで障害の内容がだいたいわかるようになってきます。

なので、現在のDECOLOGではそのルールがキツイといった状況ではないと思っています。
少なくとも、うちのredmineの数あるタスクの中には「改善するべき」というチケットは切られていません。


冒頭で「みんなよろこぶの『みんな』には自分たちも含まれる」と言ったように、もし、このアラート運用が実際キッツいものであれば、全然ダメダメだと思います。
アラート受け取る側が多くの犠牲を払って成立するサービスなんてどうかと思います。

スライドでは「よろこびの最大化」といったことも書きましたが、これはみんなのよろこびを数値化したらもっとも大きい選択肢を採るということではないです。

最近マイケル・サンデル先生の「これからの「正義」の話をしよう」という本を読んでいますが、この中にオメラスという町の話がでてきます。
"オメラスではすべての人が飢え、貧困、病気もなく、幸せに暮らしているが、その幸せはたった一人の子供が窓もない地下に閉じ込められていることが前提となっている"
といった町です。

こんなの絶対ナシだと思っています。速攻で改善です。
あと、長期的な自己犠牲も禁止です。ダメ。


そんな選択肢を採らなくても、みんなの知恵で工夫を重ねることができれば、一時的にはつらいことはあっても、DevOps的な問題はだいたいなんとかなるもんだと思います。

そして、みんながよろこぶ選択肢を選び続けていれば、たった数人で数十億PVのサイトを運営していても、それなりにやりたいことができる環境が作れると思います。作れていると思います。今のところ。


ここまでの話の具体例とかは、どうしても自分が今WEBサービスをやっているので、その視点になってしまいますが、WEBサービスじゃなくても同じことだと思います。なんならITじゃなくても同じだと思います。

DevOpsのあとがき的な内容として、あっているのかだんだんよくわからなくなってきましたが、言いたいことは言った感はあります。ほんと、すみません。

最後に、会場設営のときのCAの方がたの若々しいふいんき(変換できるけど変換しない)はなんとも楽しそうな感じで「こういうふいんき(ry)が大事なんだよなーって思いました。今度飲みに誘ってください。ということをもって僕の挨拶とかえさせていただきます。
おわり。


2011/06/14

Chefでサーバのセットアップ・管理作業を楽チンにしよう~実践編その2~


前回紹介したスパムメールがさらに進化して「メアド変えたのでよろしく!」的な内容になってて、この人らPDCA実践してるわ―、と感心してしまったhiroshiです。

「3.レシピを書いたりする」の途中で終わった前回の続きです。

phpのインストールに必要なパッケージをインストールするところで終わっていましたので、次はphpのインストールです。

ここでphpをインストールする条件として
  • インストールされてない
  • 規定のバージョンと違う
  • 想定のコンパイルオプションと違う
場合にインストールし直してね、と定義したいと思います。

主なところはこんな感じです。

{chef-repo}cookbooks/php/recipes/default.rb
# バージョンのチェックを行い、バージョンが想定と違っていたらインストールを実行する
# サーバ稼働中の予期しないアップグレードを防ぐため、/usr/local/src/PHP_VERSION_CHECK_REQUIRED
# というファイルが存在する場合のみ処理が行われる。
buildtool_check_version "php" do
  action :check
  input '|/usr/local/bin/php -v'
  regex /^PHP ([0-9.]+)/
  version node[:php][:version]
  only_if "cat /usr/local/src/PHP_VERSION_CHECK_REQUIRED"
  notifies :create, "cookbook_file[/usr/local/src/#{node[:php][:tarball]}]", :immediately
  notifies :install, "buildtool_make_install[php]", :immediately
  pecl_packages.each do |package_name, install_name|
        if node[:php][:extension][package_name.to_sym] then
        notifies :run, "execute[pecl-install-#{package_name}-force]", :immediately
        end
  end
end

# PHPのコンパイルオプションが変わっている場合はインストールを実行する
# サーバ稼働中の予期しない再コンパイルを防ぐため、/usr/local/src/PHP_VERSION_CHECK_REQUIRED
# というファイルが存在する場合のみ処理が行われる。
# ただしPHPがインストールされていない場合は必ずインストールが実行される。
file "/usr/local/src/PHP_CONFIG_OPTION" do
  content node[:php][:config_option]
  only_if "cat /usr/local/src/PHP_VERSION_CHECK_REQUIRED || ! /usr/local/bin/php -v"
  notifies :create, "cookbook_file[/usr/local/src/#{node[:php][:tarball]}]", :immediately
  notifies :install, "buildtool_make_install[php]", :immediately
end

cookbook_file "/usr/local/src/#{node[:php][:tarball]}" do
  action :nothing
  source node[:php][:tarball]
  mode "0644"
end

buildtool_make_install "php" do
  action :nothing
  tarball node[:php][:tarball]
  srcdir "php-#{node[:php][:version]}"
  config_option node[:php][:config_option]
  notifies :restart, "service[httpd]"
end



4ブロックありますが、それぞれ以下の役割です。
  • バージョンチェック
  • コンパイルオプションチェック
  • インストールファイルの展開
  • インストール

最初のブロックのバージョンチェック部分からいってみます。
buildtool_check_version "php" do

とありますが、buildtool_check_versionは自前で定義したResourceです。
自前定義のResourceです。で終わるのもなんなので、ここでまた横道にそれてbuildtoolの説明をサックリしてみたいと思います。

buildtoolもcookbookのひとつです。
{chef-repo}/cookbook/buildtool/
に定義があります。

上述のbuildtool_check_versionについては、
{chef-repo}/cookbook/buildtool/resources/check_version.rb
actions :check
# バージョンチェックをする Resource の定義

attribute :input, :kind_of => String, :required => true
attribute :regex, :kind_of => Regexp, :required => true
attribute :version, :kind_of => String, :required => true
とresourcesに定義をし

{chef-repo}/cookbook/buildtool/providers/check_version.rb
action :check do
  begin
    versionstring = IO.read(new_resource.input)
    if new_resource.regex =~ versionstring
      version = $1
      if version != new_resource.version
        Chef::Log.info("Version check for #{new_resource.name}: expected=#{new_resource.version}, actual=#{version}")
        new_resource.updated_by_last_action(true)
      end
    else
      Chef::Log.info("Version check failed: '#{versionstring}' is not match #{new_resource.regex}")
      new_resource.updated_by_last_action(true)
    end
  rescue
    Chef::Log.info("Version check failed: #{$!}")
    new_resource.updated_by_last_action(true)
  end
end
とprovidersに実際の動作を記述します。

providersにあるnew_resourceにはresourcesで定義したattributeがあり、その中身は
buildtool_check_version "php" do
  action :check
  input '|/usr/local/bin/php -v' #ココ!
  regex /^PHP ([0-9.]+)/     #ココ!
  version node[:php][:version]  #ココ!
と、利用時に指定した値が入ります。

このあたりの構成が分かれば、check_version.rbが何しているかはrubyがわからなくてもなんとなくわかるかと思います。ぼくもrubyわかんないし。

ちなみに最後の
version node[:php][:version]


{chef-repo}cookbooks/php/attributes/default.rb
default[:php][:version] = "5.x.x"
default[:php][:tarball] = "php-" + default[:php][:version] + ".tar.bz2"
default[:php][:config_option] = "--with-libdir=lib64 \
~後略~
とここに書いた値が基本的には入ります。node[]で取れるの値ですが、設定できる場所は複数箇所あって優先順位があるので気をつけてください。詳しくは公式のこの辺を参照してみてください。

あと、気づいた人もいると思いますが、このbuildtool自体もRecipeの1つなのでbuildtool_check_versionをここで使うには、事前に
include_recipe "buildtool"
しておく必要があります。

と、横道にそれてたら、このブロックの最初の5行まで追えたので次は

only_if "cat /usr/local/src/PHP_VERSION_CHECK_REQUIRED"
ですね。

これはこのブロックのコメントに書いてあるとおりです。

いやー、コメントっていいですね!

ちなみにウチで「変数宣言」とかいうコメント書いたらグラウンド10週か腕立て200回させられるので注意してください。しかもグラウンド探すところからですからけっこうキツイです。


あっ、もうだめ!今日はここまででカンベンしてください!


では、またまた続きは次回に!

2011/05/30

Chefでサーバのセットアップ・管理作業を楽チンにしよう~実践編その1~


昨日1日ケータイでアラートメール以外に受け取った唯一のメールを紹介します。
「
この前はありがとう!!久々メッチャ楽しかった~
みんながいる前だったからちょっと恥らっちゃったけど、2人っきりだったら私も同じ気持ちだったょ(はぁと

恥ずかしいけど・・・

次は2人っきりで会えませんか??

返事楽しみに待ってます(はぁと
」

「え?誰?誰?これ?」ってどんなに心当たりがないシチュエーションでも、とりあえずその可能性を記憶に見出そうとするのが男なんだなあ、と、スパム道の奥深さを知ったhiroshiです。

前回に引き続きChefについてです。

今回は、DECOLOGで実際に使っているrecipeや運用を紹介しながらの実践編です。
※この記事の元になるChefのAPIのバージョンはv0.9.12です。

その前に、いろいろ準備します。クイックスタートあたりを読んでクイックに準備しましょう。
この辺は環境依存も多いと思うし、僕やってないから華麗に通り過ぎて次行きます。

あと、このくらいの英語は翻訳サイトとか使わずに読めるようになっておいたほうが幸せになれると思います。逃げちゃだめだ!

さて、クイックに準備できたら始めます。

前回の記事でもちょっと触れましたが、chefはソフトウェア単位で扱うイメージです。
なので、今回はphpを題材にしてみます。

phpをchefで扱うまでの手順をおおざっぱに書くと
  1. cookbookを作る
  2. 必要ファイルを調達する
  3. レシピを書いたりする
  4. chef-soloでテストする
  5. cookbookをchefサーバにアップする
  6. gitにコミットしとく
という感じでしょうか。ウチの場合。


1.cookbookを作る
まず、cookbookを作ります。chefで管理するソフトの数だけcookbookを作っていくことになります。

無理やり料理に当てはめると、
この料理人(chef)は食材(ソフトウェア)ごとにレシピ(recipe)をまとめた料理帳(cookbook)を管理しているようです。

さあ、うまいこといったつもりになったところでさっそく実行です。
rake new_cookbook COOKBOOK=php
※ただし「knife cookbook create」を使えと怒られます。が、knifeコマンドはあらかじめセットアップをしておかないと正しく動作しないので、今回はこれで。

とします。すると{chef-repo}/cookbook/php配下にじゃららーと以下のようなディレクトリとファイルが作られます。
attributes
definitions
files
libraries
providers
recipes
resources
templates
metadata.rb
README.rdoc
いらないものもたくさんあるので、いらないことに気づいたタイミングで消したらいいと思います。

ここはこれで終わりです。


2.必要ファイルを調達する
本当にphpだけだったら、php-5.3.x.tar.bz2とphp.iniだけあればいいかと思います。

php-5.3.x.tar.bz2は
{chef-repo}/cookbook/php/files/default
直下に置いておきます。

php.iniはphp.ini.erbという名前にして
{chef-repo}/cookbook/php/templates/default
直下に置いておきます。

いったん終わりです。

3.レシピを書いたりする
ここに書いていきます。
{chef-repo}/cookbook/php/recipes/default.rb

さっきから各所で「default」が目につきますが、細かいことはあとで気にすることにして、ここはおとなしくdefault.rbに書いていきます。

さて、どこから書いていくかですが、その前にchefがどういうものかおさらいします。

chefは
「手でやってたセットアップ手順をrubyで記述すると、定期的にその手順の結果の状態になっているかチェックして、そうなってなければその状態にしようとする」
ツールです。

なので、手でやってた手順をそのまま記述していくことになります。

chef導入以前、DECOLOGではphpをシェルスクリプトでインストールしてたのですが、その内容は大まかにいうと
  • apacheをソースからインストール
  • phpをソースからインストール
  • pearのライブラリいろいろいれる
  • peclのライブラリいろいろいれる
といった感じでした。apacheありきです。

なので、apacheのインストールからやることになりますが、apacheも1つのソフトウェアなのでcookbookから作っていくことになります。というわけで、今回はすでにapacheのcookbookはできあがっている前提で進めます。


まずはapacheありきのphpなのでapacheのセットアップを行います。
include_recipe "apache"
こんな感じでapacheのレシピを発動させます。

次に、phpのconfigオプションで使うことになるrpmパッケージをインストールしておきます。
package "curl-devel"
package "libjpeg-devel"
package "libpng-devel"
~後略~

ここででてくるpackageというのはchef側で用意してくれている便利コマンドみたいなもので、「Resource」と呼ばれています。

<Resource>
Recipe内で行う操作を抽象化して宣言文ぽくしたもの。パッケージ管理、デーモン管理、ファイル管理、コマンド実行などが用意されている。
前回のエントリ参照

Resourceはいろんなのがあるのでhttp://wiki.opscode.com/display/chef/Resourcesに目を通しておくとよいと思います。

DECOLOGの場合はOSがCentOS環境なので、特に何も指定してなければyum installしてくれることになります。



・・・ああっ、もうガッツが足りなくなってきました。まだまだ途中ですが、最後まで書こうとするとこの倍くらいになってしまうので続きはまた次回にさせてください><


よろしくお願い申し上げます。

2011/05/23

Chefでサーバのセットアップ・管理作業を楽チンにしよう~構成編~


どうもhiroshiです。こんなに更新が早くてすみません。

今回もChefについてです。

前回の選定編に続いて、今回はChefの構成を見てみます。もちろんこれも「tommy report in Oct. 2010」からの抜粋です。

5/26 追記)この記事はv0.9.12に基づいてます。

Chefの構成の概要図
こんな感じです。


ツール群
<chef-server>
情報を集約して管理するサーバプロセス。各ClientとはJSON/RESTスタイルで通信する。

<chef-client>
設定を適用する各マシンにインストールされるデーモン。定期的にchef-serverをポーリングして、その内容に従ってレシピを実行する。

<chef-solo>
サーバ無しでレシピを実行するツール。

<knife>
chef-serverに登録されている情報の取得・変更を行うツール。切り刻むよ。

<webui>
chef-serverのWebインターフェース。なんか上手くログインできないので無視。


ということでうちではwebui使ってません。「それは大いなる損失だよ!」ということがありましたら是非教えてください。


主な構成要素
<Cookbook>
Recipe,Template,Attributeなどをひとまとめにしたディレクトリ。ソフトの種類毎に作るのがお約束ぽい。

<Recipe>
実行内容の定義本体。RubyのDSLで記述する。

<Resource>
Recipe内で行う操作を抽象化して宣言文ぽくしたもの。パッケージ管理、デーモン管理、ファイル管理、コマンド実行などが用意されている。


<Template>
設定ファイルを生成するためのテンプレート。中身は eRuby。


<Attribute>
環境依存の処理を吸収するための変数。


<File>
バイナリファイルなど、単にコピーして使うためのファイルを置くところ。


<Metadata>
Cookbookの説明とかのメタ情報。


<Role>
複数のRecipeをまとめて「役割」として定義するためのもの。


<Node>
chef-serverから見た管理対象のマシンのこと。


<run_list>
Nodeに対して適用するRoleやRecipeを指定したリスト。


<Client>
chef-serverにアクセスするツール類のこと。


なんやらいっぱいありますが、すぐ慣れます。寄生獣の後藤も「何事も慣れだ」と言ってました。これ、言うの2回目ですね。
とにかくRecipeです。普通7割くらいRecipe書くのに時間使います。
TemplateとFileは使い分けが若干微妙なところがでてくるかもしれませんが、書き換え可能なファイル(つまりテキストファイル)はTemplate、そうじゃないのはFileというルールでやってます。

例えばapacheなら、http-2.2.xx.tar.bz2はFileだし、httpd.confはTemplateの下に置いとくって感じです。


Attributeについて
レシピで使える属性値は大きく2種類に分かれます。
  1. Nodeの属性値。Nodeから収集した様々なパラメータ(IPとか)がツリー状のデータとして取得できるので、レシピで条件分けに使う。またknifeコマンドでも参照できる。
  2. 自前で設定する属性値。
    1. デフォルト値はCookbookのattributes/*.rbで定義する。
    2. RoleやNodeに対して、デフォルトの設定値を上書きする値を設定できる。これで一部のマシンだけ別設定みたいなことが実現できる。
1についてですが、具体的にはこんな感じです。
$knife node show {domain}
~前略~
    "platform_version": "5.x",
    "fqdn": "hoge.example.jp",
~中略~
    "domain": "xxx.xxx.xx",
    "os": "linux",
    "idletime": "13 days 13 hours 28 minutes 20 seconds",
    "network": {
      "default_interface": "eth0",
      "interfaces": {
        "lo": {
          "flags": [
            "UP",
            "LOOPBACK",
            "RUNNING"
          ],
          "addresses": {
            "::1": {
              "scope": "Node",
              "prefixlen": "128",
              "family": "inet6"
            },
            "127.0.0.1": {
              "netmask": "255.0.0.0",
              "family": "inet"
            }
          },
          "mtu": "16436",
          "encapsulation": "Loopback"
        },
        "eth0:xxx.xxx": {
          "flags": [
            "UP",
            "BROADCAST",
            "RUNNING",
            "MULTICAST"
          ],
~後略~
該当ホスト上でコマンド打ったら取れるような情報は全部入ってるんじゃないでしょうか。

例えば以下はapacheのconfファイルですが、まいど自FQDN名を設定するところがありますが、そんなときにこんな感じで使えます。
# Server port for sending active checks
ServerRoot      "/usr/local/apache"
ServerName      <%= node[:fqdn] %> ←ココ!!

2について、たとえば基本的にapacheのバージョンは2.2.xxを利用する、ということにします。そういう場合、cookbook/apache/attributes/default.rbに
default[:apache][:version] = "2.2.xx"
と書いておいて、apacheのrecipeに
buildtool_make_install "apache" do
  action :install
  tarball node[:apache][:tarball]
  srcdir "httpd-#{node[:apache][:version]}" ←ココ!!
  config_option configure_option()
  not_if {check_compile_setting}
  notifies :create, "template[/etc/httpd/httpd.conf]"
  notifies :restart, "service[httpd]"
end
と書いておけば、基本的にはこの2.2.xxのapacheをインストールさせることができます。

「そんなんじゃ、わかんねーよ!」って思った方、、、、ですよねー。そうだと思います。。
とりあえず、現時点ではなんとなく、で通り過ぎていただければ幸いです。。

ちょっと今回はこのあたりで切り上げます。

次回は、適当なcookbookを例に取って説明ができたらいいな、という「実践編」を書きたいと思いますが、その際、またこの「構成編」の内容をちょくちょく振り返りつつになる気がします。

chefって、一回やってみないと全体像が見えにくいんですよねー。ぼくは3回くらいやってようやく見えてきました><


というわけで、最後に女子力上げたところでまた次回にさせてください~。

2011/05/16

Chefでサーバのセットアップ・管理作業を楽チンにしよう~選定編~



>>hiroshiの母でございます。この度は(ry

最近お会いしたエンジニアの方から「他社さんのブログとかは規模や難易度が凄過ぎて真似できないことが多いんですが、DECOLOG TECH BLOGは僕らでも利用できるレベルのネタが多いので助かってます!」と言われて今まで感じたことのないキモチになったhiroshiです。

今回はChefのお話です。

去年の10月くらいの話だったんですが、「あっちのwebサーバとこっちのwebサーバ、設定微妙に違くね?」みたいなことが発覚し(いや、前から知ってたけど)、それ自体はその時点で大きな問題ではなかったんですが、「このままだといつかなんかやるよね」と危機感を覚え、システムの構成管理ができるようなツール探しをしました。

つまり、Chefとは上記ような問題点が解決できるツールで、パッケージやミドルウェア(って同じ?)を定義した状態に保ってくれるツールです。「定義した状態に保つ」ということは、定義したソフト等が入っていなかったらインストールしてくれたりするのです!!

オープンソースなものだと他にはPuppetというのがあります。

かねてよりPuppetの存在は存じていたので「じゃあ、噂のPuppetがいけそうか調べてみるかー」といった感じでミツバチの銀の弾丸tommyが調査担当になりました。鉄砲作成を依頼するとキャノン砲ができあがるのがtommyさんです。Puppetを調べてもらったらChefを見つけてきてPuppetと比較してくれました。

以下、「tommy report in Oct. 2010」です。

共通点

  • サーバクライアント構成。構成情報をサーバマシンで一括管理して、各クライアントマシンに配信する。
  • マシンに役割(WebサーバとかDBサーバとか)を設定して、その役割に必要なアプリのインストールとか設定ファイルなどをひとまとめにして管理できる。
  • マシンごとの設定の差異などは変数として管理できる。
  • 設定ファイルはテンプレートから生成できる。マシンごとに一部の値を変えるとか。
  • LDAP連携。LDAP上で各マシンの役割を定義できるみたい。
  • Subversion/Git連携(詳細はまだよく調べてない)。
  • Ruby製。



比較表



※間違っている部分もあるかもだし、当時まとめたものなのでもう変わっているところもあるかもです。

なぜChefにしたか
機能的にはそれほど差はないようでした。
決定打となったのは
tommy「ぶっちゃけChefの方が設定書きやすそうなのでだんぜん好印象ですねー。」
というコメントでしょうか。

その当時の時点で管理対象サーバは400台を超えていたし、稼働しているミドルウェア等もたくさんありました。だから、設定が書きやすいという点は重要なところです。
また、最初にChefの調査導入というフロンティア役を担ったtommyが「設定書きやすそう」っていうんだからChefだね!という感じで決めました。


というわけで今回はChef導入のきっかけのお話でした。次回以降は、Chefの構造や実際の設定例などについて書きたいと思います。

では!

2011/04/11

arpingのススメ

>>hiroshiの母でございます。
このたびは、hiroshiがブログの更新を怠ってしまい、皆様には大変ご迷惑を おかけしております。
深くお詫び申し上げます。
~中略~
どうぞ皆様、hiroshiを暖かく迎えてやってくださいまし。
本当は良い子なんです。

というわけでご無沙汰しておりますhiroshiです。技術ネタは大変ご無沙汰な感じになっておりまして、これはよくないな、ということでtommyのネタ在庫から多忙な本人に代わり、まーひーな僕がちょいと拝借して代筆させていただきます。

さて、今回は最上層でリクエストを受け付けているリバースプロキシ用途のサーバを移動させたいケースなどにおけるTIPSです。

DNSだけ切り替えてもクライアントからリバースプロキシまでの経路のN/W機器のキャッシュはガガッと切り替わってくれないので、元サーバにはリクエストが来続けます。ならば、いっそIPアドレスを移動先のサーバに移動してしまいたくなります。

ReverseProxyA(xxx.xxx.xxx.xxx)→ReverseProxyB

そのときに、上流のルータの ARP キャッシュが残ってると、パケットの飛び先が rev-proxyA の MAC アドレスのままになってしまってしばらく通信ができなくなってしまうことが予想されます(数分〜数時間)。

一方、VRRP で IP を切り替えるとそんなことは起こらないので、VRRP がやってることを手でやれば即時に切り替わるはずです。

ということでちょいと調べてみたら、「Gratuitous ARP」という ARP パケットを投げてやるというのが解でした。


ぐぐると send_arp というコマンドを使った例がいろいろ出てきますが、標準コマンドではないのでインストールが必要です。でも最初から入っている /sbin/arping コマンドでも出来るらしいです。こっちの方がいいですね(こんなコマンドあったのか……)。

IP アドレスを移動した先のマシンでこれを実行すればいいわけですね。

# /sbin/arping -q -A -I eth0 -c 1 (IPアドレス)

ARP ならパケットフィルタで ping を落としてても使えるし、 IP 重複のチェックとかにもping よりこっちの方が有用な気がします。


では!



2011/03/02

仲間大募集中その2-募集要項

こんにちは。hiroshiことTECHチームの採用担当をしている諸富です。
お久しぶりのブログが技術的じゃないもので恐縮です。

かねてより僕たちはエンジニアを大募集していますが、募集要項をきちんと明示していなかったので、ここに募集要項を公開いたします。



募集要項

<勤務地>
東京本社 東京都渋谷区恵比寿南(転勤なし)


<勤務時間>
フレックスタイム制 コアタイム10:00~16:00(所定労働時間8時間)、休日:土日祝日、夏季休暇、年末年始休暇、有給休暇、その他特別休暇


<給与>
年収400万~1000万を想定(経験・能力・前職での給与を考慮の上、規定により応相談)、賞与年2回、昇給年1回


<福利厚生>
健康保険、厚生年金保険、雇用保険、労災保険、定期健康診断、社外研修・勉強会参加サポート(業務時間内、参加費負担)、業務にかかわる書籍代サポート、交通費支給(月額50,000まで)


<その他>
遠方にお住まいの就業希望の方については、引越し費用などのサポートを目的とし、入社支度金を支給(審査あり)、スマートフォン貸与


<雇用形態>
正社員


<応募職種>
プログラマー、インフラエンジニアがわかりやすいキーワードだと思っていますが、システムに関わるエンジニアであれば、まずはお話してみたいです。


<求める人材像>
こちらをご参照ください。


<応募方法>
こちらをご参照ください。



なお、上記は基本的に中途採用を意識していますが、エンジニアという職種は、優れたスキルを持つ新卒の方が少なくない職種だと考えています。これまでに培った「何か」がある方は新卒でもなんでも、ぜひアクセスをお待ちしています。







2011/02/08

redisの実用例。redis速いよredis


こんにちは!hiroshiです!

今回は、最近DECOLOG界隈で大ブームのredisについて、その利用用途や導入方法についてお話ししたいと思います。

今回のお話と関連する過去エントリに以下がありますので、こちらに目を通していただいた上で本エントリを読んでいただくと分かりやすいと思います。



…と、これまでのエントリからは実運用できてるのかどうか微妙なタッチになっているかもしれませんが、結論からいうと実運用できてます!

redis導入後にトラブル発生、そのレポート」ではTTLを設定した場合にうまくいかないケースがあったのですが、TTLなしのデータでは特に問題なく運用できました。



現在のredisの利用用途


今のところ以下の用途ですね。
  • カウンターデータ
  • 広告配信データ
  • ユーザーのアクティビティ統計データ


最初に導入してみたのは、広告配信データでした。

なぜこれを選んだか、ですが、MySQL以外のツールを探し始めたきっかけが、readよりもwrite負荷、つまりmaster負荷が原因でスループットがあがらない処理がいくつかあり、そこをなんとかしたかったんです。

shardingすれば済むし、実際にそうやって対応していましたが、処理内容(カウントアップするだけ)や保持しているデータ(前述のデータはkey-value形式なデータで、valueは数値のみ)を考えると、なんとも「サーバがもったいない。富豪エンジニアリングになってしまってるなあ。」という感を強く持っていました。
※「富豪エンジニアリング」は必ずしも悪だとは考えてないです。念のため。

余談ですが、金をかければだいたいのことが解決するわけです。なので技術陣は他所からの要求をただ実現するだけでなく「低コスト」でやってナンボだと思っています。「サーバ増やせばできるよ」という結論も多々あると思いますが、「サーバを増やさずできる」方法を常日頃から考えておきたいところです。

話を元に戻します。前述の課題となっていたウチのデータ群では広告配信データがもっとも更新負荷が高いデータだったので、「ここがイケれば全部イケル」ということからここを選びました。

更に広告配信データは「DECOLOGでのMySQL Archiveエンジンの使い方」と同じスキームでやっていました。つまり

  1. archiveエンジンを使ってとにかくinsert
  2. 定期的にテーブルスワップし、スワップしたテーブルに対して集計プログラムを走らせる
  3. で、その結果を集計DBに格納


といった按配です。

広告配信データは、1日に1回のテーブルスワップだと半端ないデータ量になってしまうのと、運用側としてもimpressionなどの集計データの本日分が参照できないのはリアルタイム性にかけすぎるので、テーブルスワップのサイクルは1時間に1回でやっていました。

なので、「ここがイケれば全部イケル」という理由に加えて、集計スパンを既存に合わせれば、仮に導入試験中にredisが原因で未集計のデータがぶっ飛ぶようなことがあっても「MAX1時間分のデータがパーになる」というのが想定される最大の被害規模であり、それだったら基本的に内部以外に迷惑かけずに済む、という算段もありました。



設計


設計ってほどのことでもないですが、1台でどこまでいけるのか?というのを試したかったのでredisサーバは1台のみです。仮にぶっ壊れたりしたとしても、プログラムをを2,3行コメントアウトしてリリースすればよいですし。

ところでredisにはdatabaseという概念があります。これは数字で指定する、ちょうど配列の添字みたいなものです。この単位でデータは管理されています。なので、データのflushもこの単位でできます。で、これは上限があるのかどうかわからないんですが、任意の数を指定できます。この添字的なものをdbidと言うっぽいです。

データ構造のイメージとしてはこんな感じ。
[0] {
array(key1 => value, key2 => value, key3 => value ・・・)
},
[1] {
 array(key1 => value, key2 => value, key3 => value ・・・)
}
:
:
[n] {
 array(key1 => value, key2 => value, key3 => value ・・・)
}

※設定ファイルに指定した数-1

広告は1時間ごとに集計したいので、この仕様を利用して、databaseの数を24にしておきます。こうするとDBは0~23の範囲を指定できます。

プログラムでも、この仕様に合わせる形を取っておきます。
<?php
class   My_Redis_AdViewLog extends My_Redis {
    const   KEY_PREFIX = "ad_view";

    function    __construct($last_hour=false) {
        // redisのDBは時間(h)と同期をとる。つまり0~23の範囲をとる。
        $time = strtotime(($last_hour ? "-1 hour" : "now"));
        $index = date("G", $time);

        parent::__construct(REDIS_SERVER_AD_VIEW, $index);
    }

    function logging($ad_id, $carrier) {
        $this->increment($ad_id."/".$carrier);
    }


これはいわゆるDBクラスですね。
parent::__construct(REDIS_SERVER_AD_VIEW, $index);

の$indexがdbidです。

現在時間(h)をdbidとして扱っているため、10時のデータはdbid=10のところに入っているわけです。

KEY_PREFIXは、keyの一部になります。ベースクラスで、このKEY_PREFIXを元に
{ENV}/{KEY_PREFIX}/{KEY}
というkeyを組み立てるようにしてあります。

$this->increment($ad_id."/".$carrier);

のincrementですが、redisにはincrというコマンドがあり、これがチョー便利です。
valueが数値だった場合に$deltaの数だけインクリメントしてくれる、というシンプルなものなのですが、何が便利って該当するkeyがない場合にエラーとしないで、key=$deltaを自動的に作ってくれるんです!

「キーが存在すればupdate、存在しなければinsert」みたいなコードを書かなくていいのはすごく気持ちがいいです。

こんな感じでデータを収集していきます。

24時間経つと、dbidが重複することになってしまいますが、1時間ごとにデータ集計バッチが走るので、そこで現在時間-nのDBをflushする、というコマンドを入れて対応しています。



導入に当たって


新しいものを導入するときのウチの基本なのですが、いきなり切り替えず、既存ロジックと平行させて導入します。
かつ、100分の1で新しい処理を通るようにする、みたいなコードを入れて、グラフを見ながら徐々に100%にあげていきます。

100%になれば、既存ロジックの集計結果と同じになるはずなので、それを確認します。
広告集計結果をストアするsummaryというテーブルがあるとすると、summary_testというまったく同じスキーマのテーブルを用意します。
redisで取得したデータの集計データは、summary_testに格納します。あとは、確認するだけです。

で、これがけっこうあっさりうまくいっちゃったので、苦労話とか特に無いです。いや、これをやること自体がそれなりに苦労するんですけど。

しかもこれまで7台でやっていたものを1台で受けきってかれこれ数ヶ月問題も起きていません。


感想


いやー、7台が1台になっちゃいました!
正確にいうと、7台で構成されていた既存DBは、actionlog、ランキングデータ、広告配信データが格納されており、いずれもそれぞれ別のredisサーバに分けられたので「7台が3台になった」が正しいですね。ただ、こう書くと約半分程度なので、ちょっとインパクトに欠けるかもしれません(?)が、この例ではないところで4台が1台にできたのは確認できています。

実際は、valueにオブジェクトも格納でき、それを利用しているところもあるのですが、がっちり使っているのはvalueに数値を持ち、カウントアップしていく性質のデータを対象としています。

っていうか、そういうデータだったらかなり最強の部類に入るのではないか、と思っています。

また、redisの開発はかなり活発です。あまりにも活発すぎて燃え尽きないか、と心配になるくらいです。
DECOLOGが使っているphpクライアントのphpredisの開発も活発です。

こういうのは多くの人が使えば、今後ももっともっと活発に活動し続けてくれると思うので、みなさん奮ってご利用ください!!

see also:「仲間大募集のお知らせ

ではでは!

2011/01/31

Hadoopのslaveの増やし方、減らし方の続き


xperiaにskypeをインストールしたらバッテリーが1日持たなくなってしまいました。
今は充電ケーブルに行動範囲を縛られてるleonです。

今回は、前回の続き「ご紹介したHadoopサーバーの増やし方、減らし方」のハマり話です。

ハマったのはincludeファイル、excludeファイルの記述する方法です。

答え、先言っちゃいますね。
includeファイルと、excludeファイルにはサーバーのFQDNを記述しなきゃ行けなかったんです。

最初、slavesファイルと同じホスト名を記述したんですが、Hadoopサービスを再起動するとslaveサーバーが接続されないんです。

「hadoop-hadoop-jobtracker-hdmaster.log」を見ると以下のようなログが出てました。
2010-10-20 14:05:35,769 INFO org.apache.hadoop.ipc.Server: IPC Server handler 15 on 9001, call heartbeat(org.apache.hadoop.mapred.TaskTrackerStatus@51c293f8, true, true, -1) from 192.168.0.xxx:44035: error: org.apache.hadoop.mapred.DisallowedTaskTrackerException: Tasktracker denied communication with jobtracker: tracker_hdslave1.domain.jp:localhost.localdomain/127.0.0.1:44034
org.apache.hadoop.mapred.DisallowedTaskTrackerException: Tasktracker denied communication with jobtracker: tracker_hdslave1.domain.jp:localhost.localdomain/127.0.0.1:44034
    at org.apache.hadoop.mapred.JobTracker.heartbeat(JobTracker.java:1246)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:481)
    at org.apache.hadoop.ipc.Server$Handler.run(Server.java:890)
はて?jobtrackerからクラスタへの接続が許可されないとな?includeファイルが読めてない?
Haddop設定ファイルのdfs.hostsプロパティとmapred.hostsプロパティの値を見直しても間違ってませんよ。って感じであれこれ試行錯誤しました。

で、ホスト名をFQDNにしてみたらなんとslaveサーバーと接続されるじゃないですか。

前回説明した通りmasters/slavesファイルとinclude/excludeファイルは役割が異なる。役割が異なるから設定も異なるってことなんでしょうね。
masters/slavesファイルはHadoop起動制御のシェルスクリプトが参照し、include/excludeファイルはHadoopのjavaプロセスが参照しています。

これ、わかんないですよねー。

あと、masterサーバーとslaveサーバーのネットワーク接続状態を確認したところ、hadoopのjavaプロセスはIPv6でバインドされてました。

$ sudo netstat -plten | grep java
tcp        0      0 ::ffff:192.168.0.5:9000     :::*                        LISTEN      220        2464200901 8483/java
tcp        0      0 ::ffff:192.168.0.5:9001     :::*                        LISTEN      220        2464201589 8766/java
(省略)

弊社のネットワークはすべてIPv4で構築してます。なので、HadoopもIPv4でバインドするように変更しました。具体的には、hadoopコマンドのスクリプトを編集して明示的にIPv4で接続するしました。

258 HADOOP_OPTS="$HADOOP_OPTS -Dhadoop.root.logger=${HADOOP_ROOT_LOGGER:-INFO,console}"
    259 if [ "x$JAVA_LIBRARY_PATH" != "x" ]; then
    260   HADOOP_OPTS="$HADOOP_OPTS -Djava.library.path=$JAVA_LIBRARY_PATH"
    261 fi
→  262 HADOOP_OPTS="$HADOOP_OPTS -Djava.net.preferIPv4Stack=true"
    263
    264 # run it
    265 exec "$JAVA" $JAVA_HEAP_MAX $HADOOP_OPTS -classpath "$CLASSPATH" $CLASS "$@"
262行目の1行が編集した部分です。

hadoopを再起動して、ネットワーク接続状態を確認すると・・・

$ sudo netstat -plten | grep java
tcp        0      0 192.168.0.5:9000            0.0.0.0:*                   LISTEN      220        2480561580 29797/java
tcp        0      0 192.168.0.5:9001            0.0.0.0:*                   LISTEN      220        2480562441 30112/java
(省略)

IPv4でバインドされてますね。OKです。

ではでは

2011/01/25

Hadoopサーバーの増やし方、減らし方



すべての地雷を踏む男leonです。

Hadoopカスタマイズをネタにブログを始めさせていただきました。
前回、Hadoopセットアップ手順を紹介させていただいたので、今回からカスタマイズ話をしたいと思います。

Hadoopのincludeファイル、excludeファイルにハマった・・・。
今日はそのお話をさせていただきます。

話を始める前にincludeファイル、excludeファイルとはなんですかを説明しないとですね。
このincludeファイルとexcludeファイルはサーバーのslave接続を終了させる際に、ノードからデータを退避して脱退させる為に使用します。

includeファイルはデータノードへの接続を許可されるデータノード、JobTrackerに接続可能なTaskTrackerのホスト名を記述するファイルです。includeファイルはdfs.hostsプロパティとmapred.hostsプロパティで指定します。

excludeファイルはデータノードへの接続を許可しないデータノード、JobTrackerに接続させないTaskTrackerのホスト名を記述するファイルです。excludeファイルはdst.hosts.excludeプロパティとmapred.hosts.excludeプロパティでシステムで指定します。

includeファイルとexcludeファイルを指定した場合のノード接続のルールは以下の通りです。

includeファイルexcludeファイル扱い
含まれていない含まれていないノードへ接続不可
含まれていない含まれているノードへ接続不可
含まれている含まれていないノードへ接続可
含まれている含まれているノードは接続可で、脱退させられる

JobTracker接続へのルールは以下の通りです。

includeファイルexcludeファイル扱い
含まれていない含まれていないTrackerへ接続不可
含まれていない含まれているTrackerへ接続不可
含まれている含まれていないTrackerへ接続可
含まれている含まれているTrackerへ接続不可

Hadoopにはmastersファイル、slavesファイルという設定ファイルがあり、slavesファイルとincludeファイルには同じような内容となりますが役割が異なります。

slavesファイルは、Hadoopのデーモン起動終了といったオペレーションの制御スクリプトで参照されます。

includeファイルは、Hadoopデーモンがノード接続、Tracker接続の許可不許可を確認するために参照しています。

ここまで偉そうに説明しましたが、includeファイル、excludeファイルのことは最近まで知らずデータの退避はできないと思っていました。
というのは、Hadoopにslave接続させるサーバーを増やしていく過程ではexcludeファイルはもちろん、includeファイルも必要なかったのです。

というのも、dfs.hostsプロパティ、mapred.hostsプロパティでincludeファイルを明示的に指定していなければ、ノード、Trackerへの接続はデフォルト許可だったんです。

うぅ~ん、まぁ、いい訳ですね。

さてさて、では、includeファイル/excludeファイルを指定してslavesサーバーを正しく脱退させる手順をご紹介していくことにします。

サーバー追加手順

  1. includeファイルに追加するslaveサーバーのホスト名を追記し、Hadoopサーバー全台に設定を同期します。
    $ echo "hdslave3.domain" >> /home/hadoop/hadoop/conf/hosts.include
    $ /home/hadoop/rsync.sh
    
    ※includeファイルにはサーバーのFQDNを記述します。
    ※rsync.shは前回のHadoopセットアップ手順を参照してください。

  2. hadoopコマンドでネームノードを更新します。
    $ /home/hadoop/hadoop/bin/hadoop dfsadmin -refreshNodes
    
  3. slavesファイルに追加するslaveサーバーのホスト名を追記し、設定を同期します。
    $ echo "hdslave3" >> /home/hadoop/hadoop/conf/slaves
    $ /home/hadoop/rsync.sh
    
  4. Hadoopシステムを再起動します。
    $ /home/hadoop/hadoop/bin/stop-all.sh
    $ /home/hadoop/hadoop/bin/start-all.sh
    

サーバー脱退手順

  1. excludeファイルにslave接続から脱退させるサーバーのホスト名を追記し、設定を同期します。
    $ echo "hdslave3.domain" >> /home/hadoop/hadoop/conf/hosts.exclude
    $ /home/hadoop/rsync.sh
    
  2. HadoopシステムのMapReduceクラスタを再起動します。
    $ /home/hadoop/hadoop/bin/stop-mapred.sh
    $ /home/hadoop/hadoop/bin/start-mapred.sh
    
  3. hadoopコマンドでネームノードを更新します。
    $ /home/hadoop/hadoop/bin/hadoop dfsadmin -refreshNodes
    
  4. 脱退中のデータノードからデータが退避されるのをひたすら(弊社の規模では1?2日)待ちます。
    データが退避できたかどうかは、以下のコマンドで確認します。
    $ hadoop/home/hadoop/bin/hadoop dfsadmin -refreshNodes
    
    StateがDecommission in progress から Decommissionedへ変更したらデータ退避完了です。

  5. includeファイル、excludeファイル、slavesファイルから脱退させるサーバーのホスト名を削除し、設定を同期します。
    $ grep -v "hdslave3" /home/hadoop/hadoop/conf/hosts.include >> /home/hadoop/hadoop/conf/hosts.include
    $ grep -v "hdslave3" /home/hadoop/hadoop/conf/hosts.exclude >> /home/hadoop/hadoop/conf/hosts.exclude
    $ grep -v "hdslave3" /home/hadoop/hadoop/conf/slaves >> /home/hadoop/hadoop/conf/slaves
    $ /home/hadoop/rsync.sh
    
  6. Hadoopシステムを再起動します。
    $ /home/hadoop/hadoop/bin/stop-all.sh
    $ /home/hadoop/hadoop/bin/start-all.sh
    
  7. hadoopコマンドでネームノードを更新します。
    $ /home/hadoop/hadoop/bin/hadoop dfsadmin -refreshNodes
    

なんと、手順説明までで時間切れになってしまいました。
ハマったお話は次回とさせていただきます。
ではでは

2011/01/17

Hadoopのカスタマイズ



はじめましてミツバチワークスleonです。
TECHチームの一員としてブログ連載をさせていただく機会をいただきました。

テーマはHadoopカスタマイズにします。

Hadoopは既にstoneさんが現在の運用で使っているテクニックを記事にされています。
そこで私はHadoopのカスタマイズについてご紹介させていいただくことにします。
私自身がHadoop初心者ということもあり、私は初歩からスタートさせていただこうと思います。お付き合いのほどよろしくお願いします。

Hadoopとは?から始めるのが礼儀かと思いますが、そこはwikipediaなどにお任せします。ご容赦ください。

何はともあれ立ち上がっているサービスがないと話を始められません。
そこで、最初の最初はHADOOPのセットアップ手順のご紹介です。

実際、弊社ではHadoopに十数台のサーバーを投入していますが、わかりやすいように最小限のサービス構成で紹介します。
数十台で運用していても基本は同じです。
master : hdmaster
slave : hdslave1, hdslave2
client : hdclient1

なお、いくつか前提条件があります。
・ユーザーはhadoopを用意(homedirは/home/hadoop)
・ホスト間のhadoopユーザーはパスフレーズでsshログインできる
・サーバーのスペック差分なし
・サーバーにはJava 1.4.2のJREがインストールされている

Hadoopはversion 0.18.3を使用しています。現在の最新versionは0.21.0でいまから紹介するにはずいぶん古いですね。
弊社がHadoopを導入した当時でも最新は0.18ではなかったんですが、あえて0.18.3を採用したんだそうです。
これにはちょっとした経緯があります。
stoneさんがHadoop記事でも触れてますが、我々はhadoop-streamingを利用してPerlで作成したmapperとreducerを使用しています。hadoop-streamingは当時、0.18.3が一番安定していたんだそうです。
なんでも新しいものがよいという訳ではないんですね。勉強になります。

では、Hadoopセットアップ手順です。どうぞ

1. HADOOPセットアップ

1.1 マスターセットアップ

$ ssh hdmaster
$ sudo su - hadoop
$ umask 002
$ wget http://ftp.kddilabs.jp/infosystems/apache/hadoop/core/stable/hadoop-0.18.3.tar.gz
$ tar xzf hadoop-0.18.3.tar.gz
$ mv hadoop-0.18.3 hadoop
これで/home/hadoop/hadoopに展開されています。

$ mkdir -p /home/hadoop/tmp
$ mkdir -p /home/hadoop/filesystem/dfs
$ mkdir -p /home/hadoop/logs
Hadoopで使用するディレクトの作成です。

$ vi /home/hadoop/hadoop/conf/masters
hdmaster
マスターのhostです。

$ vi /home/hadoop/hadoop/conf/slaves
hdslave1
hdslave2
スレーブのhost一覧です。スレーブを追加したらこのファイルにhostを追加していきます。

$ vi hadoop/conf/hadoop-env.sh
>>>
export JAVA_HOME=/usr/java/latest

export HADOOP_LOG_DIR=/home/hadoop/logs
>>>
Hadoop環境にJavaのパスを設定します。

$ vi hadoop/conf/hadoop-default.sh
>>>

<property></property>
  <name>hadoop.tmp.dir</name>
  <value>/home/hadoop/tmp/hadoop-${user.name}</value>
<description>A base for other temporary directories.</description>

>>>
Hadoopの一時ディレクトリのパスを設定します。

$ vi hadoop/conf/hadoop-site.sh
>>>
<configuration>

<property>
  <name>fs.default.name</name>
  <value>hdfs://hdmaster:9000</value>
</property>

<property>
  <name>mapred.job.tracker</name>
  <value>hdmaster:9001</value>
</property>

<property>
  <name>dfs.name.dir</name>
  <value>/home/hadoop/filesystem/dfs/name</value>
</property>

<property>
  <name>dfs.data.dir</name>
  <value>/home/hadoop/filesystem/dfs/data</value>
</property>

<property>
  <name>mapred.system.dir</name>
  <value>/home/hadoop/filesystem/mapred/system</value>
</property>

<property>
  <name>mapred.local.dir</name>
  <value>/home/hadoop/filesystem/mapred/local</value>
</property>

<property>
  <name>dfs.replication</name>
  <value>1</value>
</property>

<property>
  <name>mapred.job.tracker.handler.count</name>
  <value>20</value>
</property>

<property>
  <name>mapred.map.tasks</name>
  <value>50</value>
</property>

<property>
  <name>mapred.reduce.tasks</name>
  <value>10</value>
</property>

</configuration>
>>>
サービス起動に必要な最低限の設定です。

1.2スレーブ&クライアントのセットアップ

$ ssh hdmaster
$ sudo su - hadoop
$ rsync --delete --progress -av hadoop hdslave1:
$ rsync --delete --progress -av hadoop hdslave2:
$ rsync --delete --progress -av hadoop hdclient:
基本的にスレーブとクライアントは同じ設定です。今後、スレーブやクライアントを増えていくことが予想される場合、以下のようにスクリプト準備しておくと便利です。
$ vi /home/hadoop/rsync.sh
#!/bin/sh

HOST_LIST="hdslave1, hdslave2, hdclient1"

cd /home/hadoop
for host in $HOST_LIST ; do
  rsync --delete --progress -av hadoop $host:
done
$ chmod 755 /home/hadoop/rsync.sh

1.3 サービスの起動

$ ssh hdmaster
$ sudo su - hadoop
$ /home/hadoop/hadoop/bin/hadoop namenode -format
$ /home/hadoop/hadoop/bin/bin/start-all.sh
namenodeのフォーマットはセットアップの際に1度行っておけばOKです。

1.4 動作確認

$ ssh hdclient
$ sudo su - hadoop
$ /home/hadoop/hadoop/bin/hadoop jar hadoop-0.18.3-examples.jar sudoku src/examples/org/apache/hadoop/examples/dancing/puzzle1.dta

これで準備OKです。
なお、ここまでは先人の叡智です。
私には既に動いているHadoopが与えられました。

私がHadoopに初めて触ったのはスレーブ追加作業からでして、そこで初ハマりがありました。
HADOOPスレーブの追加はスレーブのセットアップ後、マスターでサービスを再起動するだけだと教えていただいたのですが、サービス停止のスクリプトを実行してもスレーブで立ち上がっているjavaプロセスが終了しなかったんです。
そのときは、javaプロセスを直接killしちゃったんです。もう時効ですよね。

このあともHadoopのカスタマイズで四苦八苦を繰り返しています。
次回からは実体験のカスタマイズでハマったこと、気がついたことをご紹介していきたいと思います。

それでは