Dec 21, 2011

Cloud Foundry の Services について

この記事は Cloud Foundry JP Advent Calendar 21日目の記事として投稿です.えっ,投稿時間が過ぎているって?GMT ではまだ 23 時ですよ :)

概要
Cloud Foundry では,MySQL や RabbitMQ などの,デーモンとして起動するソフトウェアを "Service" として定義している.本稿では,Service の構成と概要について説明する,

何故 Service が必要なのか
アプリを立ち上げる毎に,PCのリソース確認して, MySQL を立ち上げる場所を決めて,テーブル作って,アクセス権限して,っていうルーチンワークを作成するのは面倒ですよね.その場所を自動化するのが Servicesである.

Service の実態について
1つのServiceは,2つのパーツから構成されている.
  1. Service Proxy
  2. Service Node
Service Proxy は,Cloud Controller からのリクエストを受け付けて,Service Node とのひも付けを行う.Service Node は,Service Proxy からのリクエストを受け付けて,実際にService インスタンスの割り当てを行う.全体像を把握するために,vmc client,CloudController,Service Proxy,Service Node,Message Bus (NATS)の関係を以下に示す.



vmc コマンドと挙動をのマッピングを示すと,
  1. vmc create-service : Service Proxy 経由で,自分用の Service を Service Node から割り当てる.
  2. vmc bind-service : Service と app をひもづける.
  3. vmc delete-service : Service を削除する.
といった具合になる.なお,create と bind の順序を別コマンドにすることで,複数アプリから 1つの Service (たとえば,DB)に対してアクセスする,といったことが可能になる.もし,RabbitMQ を複数アプリから共有すればアプリ間の連携が可能になるだろし,MySQL を複数アプリから共有すればユーザ情報を共有する,といったことが可能になるだろう.

まとめ

Services について,必要な理由とその仕組みについて大まかに説明した.Cloud Foundry ではデプロイだけでなく,アプリ間の連携もできるように設計されている.そのため,Cloud Foundry ユーザは煩わしいデプロイや管理の手間を最小化し,アプリを作成するのに集中できる環境を手に入れることができるだろう.

Dec 6, 2011

RabbitMQ の高可用構成について

本稿は CloudFoundry Advent Calendar の 6日目の記事として投稿させて頂いています.

本当はどの辺りをいじったら CloudFoundry から RabbitMQ を cluster setup できるのか調査…したかったのだが,そこまで終わらなかったので,VMware つながり && 昨日の y_wakai さんによる RabbitMQ の記事つながりで, RabbitMQ のクラスタセットアップについてまとめておく.

RabbitMQ は 2.6.x から複数台による Active-Active Standby 構成をとることができる.以前は DRBD による Active-Stanby 構成しかとれなかったので,2.6 以降で,より柔軟性が上がっている.

前準備
最新の RabbitMQ をインストールするには,http://www.rabbitmq.com/install-debian.html が参考になる.インストールが終了し,RabbitMQ がマシン h1, h2, h3 で動作していると仮定する.
まず,高可用構成をとるには,RabbitMQ のインスタンス識別子である cookie を同一にしておく必要がある.Debian の場合,cookie ファイルは /var/lib/rabbitmq/.erlang.cookie にある.もし編集していない場合は,

h1 $ echo "cookie" > /var/lib/rabbitmq/.erlang.cookie
h2 $ echo "cookie" > /var/lib/rabbitmq/.erlang.cookie
h3 $ echo "cookie" > /var/lib/rabbitmq/.erlang.cookie

などとして,クラスタに参加させたいノードの cookie を併せておく.

前知識
高可用構成のセットアップに入る前に,いくつかクラスタセットアップに必要なクラスタのノードの種類について説明する.クラスタのノードの種類には RAM ノードと Disk ノードの2種類がある.その名前の通り,RAM ノードのキューの状態はメモリの中にのみ保存され永続化は行われない.一方,Disk ノードのキューの状態は外部記憶に永続化される.ただし,複数台 Disk モードで動作させたとしても,複製はディスクに書くことを保証しないので注意.DRBD の Bモードで動作させている状態だと思えば良い.高可用構成のクラスタ内には,最低でも1台の Disk ノードが必要なので注意.

高可用構成のセットアップ
高可用構成のセットアップを行うには. rabbitmqctl コマンドを用いる.

h2 $ rabbitmqctl stop_app          # Erlang は起動させたまま RabbitMQ プロセスを停止
h2 $ rabbitmqctl reset             # RabbitMQ の保持している状態,および DB を初期化
h2 $ rabbitmqctl cluster rabbit@h1 # rabbitmq@h1 をディスクノード,rabbitmq@h2 をRAMノードにしてセットアップ
h2 $ rabbitmqctl start_app          # RabbitMQ プロセスを再開

これで,h1 をDiskノード, h2 を RAMノードにしてセットアップが完了した.
ポイントとしては, rabbitmqctl cluster で選択したノードが Disk ノードになることだ.
クラスタの状態を調べるには rabbitmq cluster_status を使う.
h1 $ rabbitmqctl cluster_status # クラスタ設定を調べる
[{nodes,[{disc,[rabbit@h1]},{ram,[rabbit@h2]}]},
 {running_nodes,[rabbit@h1,rabbit@h2]}]

h2 $ rabbitmqctl cluster_status
[{nodes,[{disc,[rabbit@h1]},{ram,[rabbit@h2]}]}, {running_nodes,[rabbit@h1,
rabbit@h2
]}]


disc,となっているのが Disk ノード,ram となっているのが RAM ノードである.
3台構成でも同様に行ける.

h3 $ rabbitmqctl stop_app          # Erlang は起動させたまま RabbitMQ プロセスを停止
h3 $ rabbitmqctl reset             # RabbitMQ の保持している状態,および DB を初期化
h3 $ rabbitmqctl cluster rabbit@h1 # rabbitmq@h1 をディスクノード,rabbitmq@h2 をRAMノードにしてセットアップ
h3 $ abbitmqctl start_app          # RabbitMQ プロセスを再開

h1 $ rabbitmqctl cluster_status 
h2 $ rabbitmqctl cluster_status
h3 $ rabbitmqctl cluster_status
h1 と h2 など,複数ノードを disc ノードにしたい場合は,
rabbitmqctl cluster rabbit@h1 rabbit@h2
とすれば良い.

ついでに,昨日の記事にもあった Bunny のライブラリについての補足もしておく.
Bunny のドキュメント中には,リモートの RabbitMQ の接続を行う方法が記述されていない.その方法を知るには,コードを読んで追いかける必要がある.以前調査を行ったので,以下に,リモートの RabbitMQ に接続して enqueue, deque するサンプルを示す.ポイントは,Bunny.new する箇所で :host でホストネームを渡してあげる部分.
#!/usr/bin/env ruby
require "bunny"

if ARGV.size < 3
  puts "Arguments Error! "
  exit(1)
end

hostname = ARGV.shift.to_s
num = ARGV.shift.to_i
msgsize = ARGV.shift.to_i

b = Bunny.new(:host => hostname, :persistent=>false, :immediate=>true)
val = "a" * msgsize
# start a communication session with the amqp server
b.start
b.tx_select
# declare a queue
q = b.queue("test1")
# declare default direct exchange which is bound to all queues
e = b.exchange("")


def push(b, e, num, val)
  num.times do |num|
    # publish a message to the exchange which then gets routed to the queue
    e.publish(val, :key => 'test1')
    b.tx_commit
  end
end

def pop(q)
  while msg = q.pop(:ack => true)[:payload]
    if msg == :queue_empty
      break
    else
      q.ack
    end
  end
end

push(b, e, num, val)
pop(q)
というわけで,次回こそは RabbitMQ と vcap のについて追いかけて説明したい.