ruby-amqpはEventMachineに依存しているので、unicornからRabbitMQに接続する場合は少し手間がかかります。EventMachineで実装されているThinなどはそのまま動くようですが。。。
下記コードをconfig/unicorn.rbに追記します。
config/amqp.ymlを用意します。
開発環境(WebRickなど)からもRabbitMQに接続出来るようにします。
コントローラからパブリッシュする場合は下記のようにします。
ruby-amqpはEventMachineに依存しているので、unicornからRabbitMQに接続する場合は少し手間がかかります。EventMachineで実装されているThinなどはそのまま動くようですが。。。
下記コードをconfig/unicorn.rbに追記します。
config/amqp.ymlを用意します。
開発環境(WebRickなど)からもRabbitMQに接続出来るようにします。
コントローラからパブリッシュする場合は下記のようにします。
EC2 で動かしている Ubuntu Server 11.10 に RabbitMQ クラスタを構築したのでその時の手順をブログに残しておきます。ホスト名の設定で若干手こずりました…。
オフィシャルで配布されているパッケージを使うのがお手軽です。
% cd /tmp % wget wget http://www.rabbitmq.com/releases/rabbitmq-server/v2.8.1/rabbitmq-server_2.8.1-1_all.deb % sudo apt-get install erlang-nox % sudo dpkg -i rabbitmq-server_2.8.1-1_all.deb
下記コマンドでエラーが出なければ、正常にRabbitMQが起動しています。
% sudo rabbitmqctl status
RabbitMQはデフォルトでノード名がインストールしたサーバのhostname -sになっています。このノード名を変更するには /etc/rabbitmq/rabbitmq-env.conf でNODENAMEを指定すれば良いのですが、何故かNODENAMEにはFQDNが使えません。
このままだと、別のRabbitMQサーバをクラスタに追加する時にノード名の不一致が起き、正常に追加出来ないという罠が待っています。まぁ、/etc/hosts に全サーバのホスト名を書いていけば問題無いのですが、EC2だとインスタンスの再起動でIPとホスト名が変わったりするので、あまり現実的ではありませんね。極力ドメイン名で処理したいところです。
そこで /etc/rabbitmq/rabbitmq-env.conf でFQDNが使えるようにRabbitMQ本体に若干手を加えます。といってもオプションを書き換えるだけです。
書き換える前にサーバを停止しておきます。
% sudo /etc/init.d/rabbitmq-server stop
/usr/lib/rabbitmq/lib/rabbitmq_server-2.8.1/sbin/rabbitmq-server
/usr/lib/rabbitmq/lib/rabbitmq_server-2.8.1/sbin/rabbitmqctl
/etc/rabbitmq/rabbitmq-env.conf は下記のようにします。
NODENAME=rabbit@rabbit1.foo.bar.internal
これでRabbitMQを再度起動させてエラーが出なければ設定完了です。
rabbit1.foo.bar.internal と rabbit2.foobar.internal に対して、上記手順に則ってRabbitMQをインストールしたと仮定します。
rabbit1を初期化する
rabbit1% sudo rabbitmqctl stop_app rabbit1% sudo rabbitmqctl reset
rabbit1上でrabbit2をクラスタに参加させる
rabbit1% sudo rabbitmqctl cluster rabbit2.foo.bar.internal rabbit1% sudo rabbitmqctl start_app
クラスタに追加されているか確認する
rabbit1% sudo rabbitmqctl cluster_status
Cluster status of node 'rabbit@rabbit1.foo.bar.internal' ...
[{nodes,[{disc,['rabbit@rabbit2.foo.bar.internal']},
{ram,['rabbit@rabbit1.foo.bar.internal']}]},
{running_nodes,['rabbit@rabbit2.foo.bar.internal',
'rabbit@rabbit1.foo.bar.internal']}]
...done.
追加されてますね。rabbit2からも確認してみます。
rabbit2% sudo rabbitmqctl cluster_status
Cluster status of node 'rabbit@rabbit2.foo.bar.internal' ...
[{nodes,[{disc,['rabbit@rabbit2.foo.bar.internal']},
{ram,['rabbit@rabbit1.foo.bar.internal']}]},
{running_nodes,['rabbit@rabbit1.foo.bar.internal',
'rabbit@rabbit2.foo.bar.internal']}]
...done.
これでrabbit1, rabbit2どちらに接続してもキューをpublish, subscribeすることが出来ます。クラスタ化自体はそこまで難しくないと思います。
詳しくはオフィシャルドキュメントに全部書いてあるので、そちらを参照してください。
MongooseでSchemaを定義する時に、Shard keyの情報を渡してあげる事で insert, update, remove等の処理がTargetedオペレーションで実行されるようになります。便利ですね。
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.ObjectId;
var Footprint = new Schema({
user_id: { type: ObjectID },
visitor_id: { type: ObjectID },
seconds: { type: Number, default: (new Date()).getSeconds() },
created_at: { type: Date, default: Date.now }
}, {
shardkey: {
user_id: 1,
seconds: 1
}
});
※ シャーディングの設定自体は予めMongoDB側で済ませておく必要があります。
node-uuid というモジュールを使うことで手軽に生成出来ます。Node.js で分散ネットワークを構築したい時に活躍しそうです。
npm でインストールできます。
npm install node-uuid
サンプル。
var uuid = require('node-uuid');
console.log(uuid.v1()); // => eb6b8030-3b57-11e1-9c04-c9b9178cf34e
console.log(uuid.v4()); // => 4d9a29f8-8993-40cd-819b-862b9c7b78b2
UUIDの本来の目的とは違いますが、加工して32文字のランダムな文字列としても使えますね(パフォーマンス的にどうなのかは未検証です)
var uuid = require('node-uuid');
var rand = uuid.v4().split('-').join(''); // 661708030ec74627a12d3f6c6f8f5dd2
前回に引き続き Chef に関するエントリです。このエントリではChef Serverのインストールと初期設定、更にNodeの接続までを書いていこうと思います。自分のメモ書きを整理して書き出しているので、何かおかしな箇所があったら是非指摘してください!
さて、作業を進めていく上で複数のマシンが出てくるので、便宜上、下図のようなネットワーク構成にしようと思います。192.168.50.100はグローバルIPだと思ってください。また、Chef Server, 各Nodeは全てUbuntu 11.10 Serverと仮定します(Debian squeezeでも大丈夫)。

前回の「Chefの仕組み」でも書いたように、自力で Chef Server をセットアップするのは骨が折れます。サーバの構築を自動化したいのに肝心のChefで手間取るなど本末転倒ですね。なるべくならディストリビューション付属のパッケージシステムで導入したいところです。
有り難いことに開発元の Opscode が、Debina/Ubuntu 用の APT Repository を提供して下さっていますので迷わず利用しましょう。…CentOSは…美味しいんでしょうか…
作業手順は下記の通り。まずChef ServerにSSHでログインし、KeyとRepositoryを登録して apt-get で chef-server をインストールします。
# Ubuntu chefserver:$ echo "deb http://apt.opscode.com/ `lsb_release -cs`-0.10 main" | sudo tee /etc/apt/sources.list.d/opscode.list # Debian chefserver:$ echo "deb http://apt.opscode.com/ squeeze-0.10 main" | sudo tee /etc/apt/sources.list.d/opscode.list chefserver:$ sudo apt-get update chefserver:$ sudo apt-get install opscode-keyring chefserver:$ sudo apt-get update chefserver:$ sudo apt-get install chef-server
インストールの途中で3回質問が出ますので入力が必要になります。
これはChef Server内にインストールされるChef ClientからみたChef Server APIのURIを入力します(分かりにくいw)。同一サーバなのでここではhttp://localhost:4000と入力してください。
AMQP Server(RabbitMQ)のパスワードを入力します。任意の文字列を入力してください。ここで入力したパスワードは /etc/chef/solr.rb 内に記述されます。
WebUIの初期ユーザー(admin)のパスワードを設定します。任意の文字列を入力してください。
インストールが無事終わるとChef Serverが自動的に立ち上がります。結構な量のパッケージが入りますので多少時間がかかると思います。
Chef Serverのインストールが終わったら管理用Clientを登録します。ここで登録するClientはChef Server内でのみ使用します。
まずホームディレクトリに.chefディレクトリを作成し、鍵二種類を/etc/chefからコピーしてきます。
chefserver:$ mkdir -p ~/.chef chefserver:$ sudo cp /etc/chef/validation.pem /etc/chef/webui.pem ~/.chef chefserver:$ sudo chown -R $USER:$USER ~/.chef
そのあとにknifeコマンドで初期設定を行います。
chefserver:$ knife configure -i WARNING: No knife configuration file found Where should I put the config file? [~/.chef/knife.rb] [ENTER] Please enter the chef server URL: [http://chefserver:4000] http://localhost:4000 Please enter a clientname for the new client: [username] master Please enter the existing admin clientname: [chef-webui] [ENTER] Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem] /home/username/.chef/webui.pem Please enter the validation clientname: [chef-validator] [ENTER] Please enter the location of the validation key: [/etc/chef/validation.pem] /home/username/.chef/validation.pem Please enter the path to a chef repository (or leave blank): [ENTER] Creating initial API user... Created client[master] Configuration file written to /home/username/.chef/knife.rb
これでmasterという管理用Clientが登録されました。試しに下記コマンドを叩いてみましょう。
chefserver:$ knife client list
次のように返ってくれば成功です。
chefserver:$ knife client list chef chef-validator chef-webui master
Clientの詳細をみることも出来ます。
chefserver:$ knife client show master _rev: 1-2901b7c2eb6d33b01f8f12951933b709 admin: true chef_type: client json_class: Chef::ApiClient name: master public_key: -----BEGIN RSA PUBLIC KEY----- snip -----END RSA PUBLIC KEY-----
今後、Chefの操作はほぼKnifeコマンドで行う事になるのですが、毎回サーバにログインして作業するのは微妙です。Cookbookなどは別の環境で作成してGitなどのSCMで管理したいですよね。
ですので別の環境からでもアクセス出来る管理用Clientを作成したいと思います。
下記コマンドを実行しましょう。これはakatsukaという管理用Clientを作成するコマンドになります。Client名は適宜置き換えて下さい。
chefserver:$ knife client create akatsuka -n -a -f /tmp/akatsuka.pem Created client[akatsuka]
ちゃんと作成出来ているか確認します。
chefserver:$ knife client list akatsuka chef chef-validator chef-webui master
あとは上記コマンドで生成された/tmp/akatsuka.pemと~/.chef/validation.pemの二つの鍵をSCPなどでローカル環境にコピーしてください。
ローカル環境側はknifeコマンドを使うだけですので、RubyとRubygemsが入っていればgem installだけで済みます(Rubyのインストールは割愛します)。
workstation:$ gem install chef
インストールが終わったら先ほどSCPで持ってきた akatsuka.pem と validation.pem を ~/.chef にコピーして、下記コマンドを実行します。
workstation: $ knife configure WARNING: No knife configuration file found Where should I put the config file? [~/.chef/knife.rb] [ENTER] Please enter the chef server URL: [http://workstation:4000] http://192.168.50.100:4000 Please enter an existing username or clientname for the API: [username] akatsuka Please enter the validation clientname: [chef-validator] [ENTER] Please enter the location of the validation key: [/etc/chef/validation.pem] /Users/username/.chef/validation.pem Please enter the path to a chef repository (or leave blank): [ENTER] ***** You must place your client key in: /Users/username/.chef/akatsuka.pem Before running commands with Knife! ***** You must place your validation key in: /Users/username/.chef/validation.pem Before generating instance data with Knife! ***** Configuration file written to /Users/username/.chef/knife.rb
ローカル環境からでも接続出来るか確認してみましょう。
workstation:$ knife client list akatsuka chef chef-validator chef-webui master
Chef Server APIのプロトコルは普通のHTTPなので、フロントエンドに nginx を置いてHTTPSに対応させます。
chefserver:$ sudo apt-get install nginx
OpenSSLを使って鍵を生成します。
chefserver:$ cd /etc/nginx chefserver:$ sudo mkdir ssl chefserver:$ sudo openssl req -new -key ssl/server.key -out ssl/server.csr chefserver:$ sudo openssl x509 -in ssl/server.csr -out ssl/server.crt -req -signkey ssl/server.key -days 365 chefserver:$ sudo chmod 400 ssl/server.*
nginxのデフォルトファイルを削除します。
sudo rm -rf /etc/nginx/sites-enabled/default
/etc/nginx/conf.d/proxy.confを作成します。
# /etc/nginx/conf.d/proxy.conf proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https;
/etc/nginx/sites-available/sslを作成します。
# /etc/nginx/sites-available/ssl
server {
listen 443;
server_name localhost;
ssl on;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:4000/;
}
}
上で作ったファイルのシンボリックリンクを/etc/nginx/sites-enabled内に作成します。
chefserver:$ sudo ln -s /etc/nginx/sites-available/ssl /etc/nginx/sites-enabled/ssl
nginxを起動させます。
chefserver:$ sudo /etc/init.d/nginx start
これでChef ServerはHTTPS対応になりました。ローカル環境のほうもHTTPSで繋がるように変更しておきましょう。~/.chef/knife.rbを開いてchef_server_urlの値を修正します。
-chef_server_url 'http://192.168.50.100:4000' +chef_server_url 'https://192.168.50.100'
うーん・・・。書いていて思ったのはやはり手間ですね!もう少し何とかならないものか・・・。あと完全にWebUIをスルーしておりますが、今後もスルーする方向で行こうと思っています。理由は、WebUIだけだと出来ない事がある、何故かOpenIDを使ってログイン出来てしまう(致命的なような…)、そもそも使い勝手的にどうなんだ、といったところです。あと設定ファイルもローカルに残らないのでそれもマイナスですね。
さて、次はいよいよNodeを登録して実際にレシピを書いていこうと思います。年内に書ければいいなぁ