fbpx

Cloud Foundryを使ってみよう[5]

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

node.jsとRabbitMQの連携

RabbitMQは、Advanced Message Queuing Protocolを使用する、メッセージ指向のミドルウェアです。

本項では、Using RabbitMQ with Node.jsを参考に、RabbitMQに接続するnode.jsで作成されたアプリケーションを、Cloud Foundry上にデプロイします。

RabbitMQのインストール

RabbitMQサーバをローカルにインストールします。

root@debian:~# aptitude install rabbitmq-server
	:
Setting up erlang-base (1:14.a-dfsg-3) ...
Searching for services which depend on erlang and should be started...none found.
Setting up erlang-syntax-tools (1:14.a-dfsg-3) ...
Setting up erlang-asn1 (1:14.a-dfsg-3) ...
Setting up erlang-mnesia (1:14.a-dfsg-3) ...
Setting up erlang-runtime-tools (1:14.a-dfsg-3) ...
Setting up erlang-crypto (1:14.a-dfsg-3) ...
Setting up erlang-public-key (1:14.a-dfsg-3) ...
Setting up erlang-ssl (1:14.a-dfsg-3) ...
Setting up erlang-inets (1:14.a-dfsg-3) ...
Setting up erlang-corba (1:14.a-dfsg-3) ...
Setting up erlang-xmerl (1:14.a-dfsg-3) ...
Setting up erlang-edoc (1:14.a-dfsg-3) ...
Setting up erlang-docbuilder (1:14.a-dfsg-3) ...
Setting up erlang-erl-docgen (1:14.a-dfsg-3) ...
Setting up erlang-eunit (1:14.a-dfsg-3) ...
Setting up erlang-ic (1:14.a-dfsg-3) ...
Setting up erlang-inviso (1:14.a-dfsg-3) ...
Setting up libltdl7 (2.2.6b-2) ...
Setting up erlang-snmp (1:14.a-dfsg-3) ...
Setting up erlang-os-mon (1:14.a-dfsg-3) ...
Setting up erlang-parsetools (1:14.a-dfsg-3) ...
Setting up erlang-percept (1:14.a-dfsg-3) ...
Setting up erlang-ssh (1:14.a-dfsg-3) ...
Setting up erlang-webtool (1:14.a-dfsg-3) ...
Setting up erlang-tools (1:14.a-dfsg-3) ...
Setting up libsctp1 (1.0.11+dfsg-1) ...
Setting up lksctp-tools (1.0.11+dfsg-1) ...
Setting up odbcinst (2.2.14p2-1) ...
Setting up odbcinst1debian2 (2.2.14p2-1) ...
Setting up unixodbc (2.2.14p2-1) ...
Setting up erlang-odbc (1:14.a-dfsg-3) ...
Setting up erlang-nox (1:14.a-dfsg-3) ...
Setting up rabbitmq-server (2.6.1-1) ...
Adding group `rabbitmq' (GID 105) ...
Done.
Adding system user `rabbitmq' (UID 103) ...
Adding new user `rabbitmq' (UID 103) with group `rabbitmq' ...
Not creating home directory `/var/lib/rabbitmq'.
Starting rabbitmq-server: SUCCESS
rabbitmq-server.

root@debian:~#

RabbitMQを利用するnode.jsのサンプルアプリケーションの作成

サンプルアプリケーションのディレクトリを作成します。

cf@debian:~$ mkdir rabbitmq-node
cf@debian:~$ cd rabbitmq-node
cf@debian:~/rabbitmq-node$

node.jsパッケージをインストールするための、依存関係を記述したファイルを作成します。

cf@debian:~/rabbitmq-node$ cat > package.json
{
    "name":"node-amqp-demo",
    "version":"0.0.1",
    "dependencies": {
        "amqp":">= 0.1.0",
        "sanitizer": "*"
    }
}
cf@debian:~/rabbitmq-node$

npmコマンドでインストールを行います。

cf@debian:~/rabbitmq-node$ npm install
npm WARN publish-everything amqp@0.1.1 Adding entire directory to tarball. Please add a
npm WARN publish-everything amqp@0.1.1 .npmignore or specify a 'files' array in the package.json
npm ok
cf@debian:~/rabbitmq-node$

サンプルアプリケーションを作成します。

cf@debian:~/rabbitmq-node$ cat > app.js
var http = require('http');
var amqp = require('amqp');
var URL = require('url');
var htmlEscape = require('sanitizer').escape;

function rabbitUrl() {
  if (process.env.VCAP_SERVICES) {
    conf = JSON.parse(process.env.VCAP_SERVICES);
    return conf['rabbitmq-2.4'][0].credentials.url;
  }
  else {
    return "amqp://localhost";
  }
}

var port = process.env.VCAP_APP_PORT || 3000;

var messages = [];

function setup() {

  var exchange = conn.exchange('cf-demo', {'type': 'fanout', durable: false}, function() {

    var queue = conn.queue('', {durable: false, exclusive: true},
    function() {
      queue.subscribe(function(msg) {
        messages.push(htmlEscape(msg.body));
        if (messages.length > 10) {
          messages.shift();
        }
      });
      queue.bind(exchange.name, '');
    });
    queue.on('queueBindOk', function() { httpServer(exchange); });
  });
}

function httpServer(exchange) {
  var serv = http.createServer(function(req, res) {
    var url = URL.parse(req.url);
    if (req.method == 'GET' && url.pathname == '/env') {
      printEnv(res);
    }
    else if (req.method == 'GET' && url.pathname == '/') {
      res.statusCode = 200;
      openHtml(res);
      writeForm(res);
      writeMessages(res);
      closeHtml(res);
    }
    else if (req.method == 'POST' && url.pathname == '/') {
      chunks = '';
      req.on('data', function(chunk) { chunks += chunk; });
      req.on('end', function() {
        msg = unescapeFormData(chunks.split('=')[1]);
        exchange.publish('', {body: msg});
        res.statusCode = 303;
        res.setHeader('Location', '/');
        res.end();
      });
    }
    else {
      res.statusCode = 404;
      res.end("This is not the page you were looking for.");
    }
  });
  serv.listen(port);
}

console.log("Starting ... AMQP URL: " + rabbitUrl());
var conn = amqp.createConnection({url: rabbitUrl()});
conn.on('ready', setup);

// ---- helpers

function openHtml(res) {
  res.write("<html><head><title>Node.js / RabbitMQ demo</title></head><body>");
}

function closeHtml(res) {
  res.end("</body></html>");
}

function writeMessages(res) {
  res.write('<h2>Messages</h2>');
  res.write('<ol>');
  for (i in messages) {
    res.write('<li>' + messages[i] + '</li>');
  }
  res.write('</ol>');
}

function writeForm(res) {
  res.write('<form method="post">');
  res.write('<input name="data"/><input type="submit"/>');
  res.write('</form>');
}

function printEnv(res) {
  res.statusCode = 200;
  openHtml(res);
  for (entry in process.env) {
    res.write(entry + "=" + process.env[entry] + "<br/>");
  }
  closeHtml(res);
}

function unescapeFormData(msg) {
  return unescape(msg.replace('+', ' '));
}
cf@debian:~/rabbitmq-node$

サンプルアプリケーションをローカルで実行します。

cf@debian:~/rabbitmq-node$ node app.js
Starting ... AMQP URL: amqp://localhost

別シェルからwgetコマンドで接続します。

cf@debian:~$ wget -q -O - http://localhost:3000/
<html><head><title>Node.js / RabbitMQ demo</title></head><body><form method="post"><input name="data"/><input type="submit"/></form><h2>Messages</h2><ol></ol></body></html>cf@debian:~$

次に、データをPOSTしてみます。

cf@debian:~$ wget -q --post-data="data=TEST" -O - http://localhost:3000/
<html><head><title>Node.js / RabbitMQ demo</title></head><body><form method="post"><input name="data"/><input type="submit"/></form><h2>Messages</h2><ol></ol></body></html>cf@debian:~$

POSTしたデータが表示されることを確認します。

cf@debian:~$ wget -q -O - http://localhost:3000/
<html><head><title>Node.js / RabbitMQ demo</title></head><body><form method="post"><input name="data"/><input type="submit"/></form><h2>Messages</h2><ol><li>TEST</li></ol></body></html>cf@debian:~$

以上のように動作が確認できたら、Cloud Foundry上にサンプルアプリケーションをデプロイします。この際、RabbitMQと関連付けを行っておきます。

cf@debian:~/rabbitmq-node$ vmc push
Would you like to deploy from the current directory? [Yn]:

y

Application Name:

rabbitmq-node

Application Deployed URL [rabbitmq-node.cloudfoundry.com]:

rabbitmq-node-creationline.cloudfoundry.com

Detected a Node.js Application, is this correct? [Yn]:

y

Memory Reservation (64M, 128M, 256M, 512M, 1G, 2G) [64M]:
Creating Application: OK
Would you like to bind any services to 'rabbitmq-node'? [yN]:

y

The following system services are available
1: mongodb
2: mysql
3: postgresql
4: rabbitmq
5: redis
Please select one you wish to provision:

4

Specify the name of the service [rabbitmq-ec036]:
Creating Service: OK
Binding Service [rabbitmq-ec036]: OK
Uploading Application:
  Checking for available resources: OK
  Packing application: OK
  Uploading (1K): OK
Push Status: OK
Staging Application: OK
Starting Application: .......Error:
Application 'rabbitmq-node's state is undetermined, not enough information available.

cf@debian:~/rabbitmq-node$

アプリケーションの起動に失敗したので、ログの確認を行います。

cf@debian:~/rabbitmq-node$ vmc crashlogs rabbitmq-node
====> logs/stderr.log <====

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Cannot find module 'amqp'
    at Function._resolveFilename (module.js:326:11)
    at Function._load (module.js:271:25)
    at require (module.js:355:19)
    at Object.<anonymous> (/var/vcap/data/dea/apps/rabbitmq-node-0-f77585e3130e2d0644c1004455eaf707/app/app.js:2:12)
    at Module._compile (module.js:411:26)
    at Object..js (module.js:417:10)
    at Module.load (module.js:343:31)
    at Function._load (module.js:302:12)
    at Array.<anonymous> (module.js:430:10)
    at EventEmitter._tickCallback (node.js:126:26)

cf@debian:~/rabbitmq-node$

node.jsとMongoDBを連携させたときと同じく、RabbitMQ用のnode.jsモジュールをアプリケーションと一緒にデプロイしなければいけないようです。

サンプルアプリケーションのディレクトリに、モジュールを格納するためのnode_modulesディレクトリを作成し、そこに必要なモジュールをコピーします。

cf@debian:~/rabbitmq-node$ mkdir node_modules
cf@debian:~/rabbitmq-node$ cp -a ../.node_libraries/.npm/amqp/active/package node_modules/amqp
cf@debian:~/rabbitmq-node$ cp -a ../.node_libraries/.npm/sanitizer/active/package node_modules/sanitizer
cf@debian:~/rabbitmq-node$

サンプルアプリケーションを更新し、必要なモジュールをCloud Foundry上に転送します。

cf@debian:~/rabbitmq-node$ vmc update rabbitmq-node
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (1K): OK
Push Status: OK
Stopping Application: OK
Staging Application: OK
Starting Application: OK                                                        

cf@debian:~/rabbitmq-node$

Webブラウザで http://rabbitmq-node-creationline.cloudfoundry.com/ にアクセスします。

[          ] [送信]

Messages

以上のようなフォームが表示されれば動作しています。例えば「TEST」とフォームに入力して「送信」ボタンを押すと、

[          ] [送信]

Messages

  1. TEST

となります。

Author

Chef・Docker・Mirantis製品などの技術要素に加えて、会議の進め方・文章の書き方などの業務改善にも取り組んでいます。「Chef活用ガイド」共著のほか、Debian Official Developerもやっています。

Daisuke Higuchiの記事一覧

新規CTA