最近のNodeのdeployや運用まわりについて

Node.js Advent Calendarの6日目の記事です。

非常に申し訳ないのですが、あまりに衝撃的な出来事が起きたため、ちょっとここ数日余裕がありません。よって、このエントリは伝えたいことを伝えてサクッと終わらせていただきます。

日本ではあまり話題になっていないのですが、Phusion PassengerはNode.jsにも対応しています。チュートリアルはコチラです。ただし、最新バージョンではNodeプロジェクトのディレクトリ構成は固定ではなくカスタマイズ可能になっています。

PassengerはApacheだけじゃなくNginxにも対応している上、Passenger自身もイベント駆動I/Oに書きなおされているため、Nodeの利点を損なうことなく活用することが可能です。

前まではNodeのプロセス管理といえば、forever(やNode製ではないupstartrunitなど)を使うことが多かったのですが、最近はPM2が注目を集めてますね。PassengerとPM2は両者ともにGraceful Restartが可能なため(諸条件あり)、今後はこれらを活用した事例が増えていくのではないでしょうか。

「東京Node学園祭 2013」と「Node.js Knockout」が終わりました

東京Node学園祭 2013」はもう終わってから3週間経つというのに今さらです。すみません。
Node.js Knockout」はハッカソン期間が先週頭に終わって、審査期間が今日終わったところです。

1. 東京Node学園祭 2013について

直前まで台風直撃予報があったりと、ちょっと今までにない緊張感とバタバタがありましたが、なんとか無事に終えることができました。場所を変えての懇親会ではなく、その場にピザとビールを持ち込んでの懇親会に変更したことで、LTが無限に繋がるという事象が発生するなど、目指していた参加者が「参加」するイベントにより近づけたんじゃないかと思います。

個人的には基調講演をするという経験が初めてだったので、いい経験になりました。基調講演に相応しいテーマって難しいですね。ただの事例紹介だとなんか面白みがないというか有り難みが薄いというか。しかも自社の事例を紹介するとかになると、「自分が代表をしているコミュニティ」の「自分が実行委員長を務めているカンファレンス」において「自分が行う基調講演」の中で「自分が勤めてる会社の事例を紹介」し、さらに「自分が勤めてる会社の求人をする」とかいう状態になってしまうので、傍から見ると「コミュニティ(やカンファレンス)の私物化」と言われ兼ねないなという危機感を抱いておりました。

基調講演の中で、コミュニティの代表を辞する旨を宣言したのは、それも理由の一つです。もっとも大きい理由は「関係者が固定化することによるコミュニティの硬直化を避ける」という目的があってのことですが、僕自身がドワンゴにおいて優秀なエンジニアを獲得するための仕事をしていることもあり、一切しがらみのないコミュニティである「Node.js 日本ユーザグループ」がドワンゴの影響下にあるという変な誤解を与えたくないという目的もあります。

まあそんなわけで、もう少ししたら代表の立候補と推薦の受付を開始いたします。推薦を戴いた場合は、推薦があったことをご本人に伝え、ご本人にその意思があれば立候補していただきます。候補者が出揃ったあとは普通に投票をしていただこうと思っています。連絡は全てMLにて行うのでお待ちください。

2. Node.js Knockout について

今年もドワンゴ社内でチームを組んで参加しました。また、ドワンゴのカフェテリアを開放して東京からの参加者が集まれるTokyo Campを開設しました。僕たちのチームが作ったものは Walk Sharing というサービスで、要はGoogle Street Viewで動きまわってる様子(位置と視点)を他人とチャットしながらリアルタイムに共有できるというものです。タイムシフト機能もあり、誰かが過去に動きまわった様子をそのまま再現することもできます。
Google Maps APIのundocumentedなプロパティを使って作ってしまったため、残念ながら審査期間中に入ってそのプロパティの名称が変わりタイムシフト等にバグが発生してしまったのですが、審査が終わった今はバグをFixしましたので是非体験してみてください。

あとがき

ブログ書くの久しぶりすぎました。「Node.js Advent Calendar 2013」にもエントリしたし、これからはもうちょっとちゃんと書いていきます。

Node.jsの本が出ます

数年前に告知したと思うのですが、そこから紆余曲折あり(主にNodeのアップデートが早すぎて)時間が掛かってしまいましたが、ようやく書きあがりました。Nodeの内部構造を知りたい人から、Nodeを使って何ができるのか知りたい人までカバーできる内容となっています。是非一度お手に取ってご覧いただければ幸いです。

サーバサイドJavaScript Node.js入門

サーバサイドJavaScript Node.js入門

見本誌も届きました。実際に見てみると結構分厚いですね。
f:id:t_43z:20121026105534j:plain

早速ですが、訂正を。インストールの章に現在の開発版はv0.9系で次期安定版はv1.0系になる予定とか書いてますが、実際には次期安定版はv0.10系になることが発表されています。
他にも間違いはあると思いますが、正誤表は出版社のサイトに掲載予定ですのでお待ちください。

「東京Node学園祭 2012」を開催します。

詳しい話は
http://blog.nodejs.jp/2012/07/nodejs-node-2012.html
をご覧ください。

今回は、前回頂いた色々なご意見を参考に、運営の仕方も結構変えています。
今後、セッション登壇者/スポンサー/スタッフを公募していくことになりますが、皆さま是非とも協力のほどよろしくお願いいたします。

よく使われているパッケージ

このエントリは、「東京Node学園 4時限目」の資料その5です。

Nodeには様々なサードパーティ製のパッケージが存在しています。基本的にそれらはnpmでインストールすることになるので、http://search.npmjs.org/ で一覧を確認することができます。
また、同様のものをGithubのwatch数やfork数で見える可したものが http://toolbox.no.de/ です。

同様の目的のパッケージでも複数あってどれを選んだらいいか困ることも多いと思いますので、代表的によく使われているものを挙げていきます。

Socket.IOを用いたマウスカーソルの共有

このエントリは、「東京Node学園 4時限目」の資料その4です。

app.js

var io = require('socket.io').listen(80);

io.of('/index').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/speakers').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/session').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/sponsors').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/ticket').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

client.js

function cursorShare(ns) {
  var socket = io.connect('http://example.com/' + ns);
  socket.on('location', function(data) {
    var cursor = $('#' + data.id);
    if (!cursor.attr('id')) {
      cursor = $('<img>');
      cursor.attr('class', 'cursor');
      cursor.attr('id', data.id);
      cursor.attr('src', 'images/cursor.png');
      cursor.css('position', 'absolute');
      cursor.css('width', '18px');
      cursor.css('height', '24px');
      $('#wrapper').append(cursor);
    }
    cursor.css('left', data.x + 'px');
    cursor.css('top', data.y + 'px');
    cursor.show();
    setTimeout(function() {
      cursor.hide();
    }, 10000);
  });
  $('#wrapper').mousemove(function(e) {
    socket.emit('location', {
      x: e.pageX,
      y: e.pageY
    });
  });
}

各ページ

  <script type="text/javascript">
    $(function() {
      cursorShare('session');
    });
  </script>

見たらだいたい分かるべ

ExpressとSocket.IOを使ったカウンターのサンプル

このエントリは、「東京Node学園 4時限目」の資料その3です。

Expressを使ったWebアプリケーションの動きが大体つかめたところで、次はSocket.IOを使ったリアルタイムWebアプリケーションを作ってみましょう。
Socket.IOとはWebSocketをNodeで扱うためのサーバ及びクライアントのライブラリです。WebSocketが使える環境であればWebSocketを、そうでなければ自動的にXHR-polling等に切り替えてくれるのが最大の特徴です。

まずは、Socket.IOをインストールします。package.jsonを以下の様に書き換えてください。

 {
     "name": "application-name"
   , "version": "0.0.1"
   , "private": true
   , "dependencies": {
       "express": "2.5.8"
     , "jade": ">= 0.0.1"
+    , "socket.io": "*"
   }
 }

socket.ioの記述を追加しました。"*"は最新バージョンを意味します。それではsampleディレクトリで再度 npm install をしましょう。

$ npm install

Socket.IOがインストールされたはずです。このように、使用するパッケージをpackage.jsonに記述し管理することで、node_modules ディレクトリをバージョン管理システムの対象外にしておいても同じ環境をコマンド一発で再現することができます。
それでは実際にSocket.IOを使って、現在ページを見ている人数をリアルタイムに表示するプログラムを書いてみましょう。まず、app.jsでSocket.IOを読み込みます。

 var express = require('express')
+  , socketio = require('socket.io')
   , routes = require('./routes');

またファイルの末尾に以下のコードを追加します。

var io = socketio.listen(app);
var count = 0;
io.sockets.on('connection', function(socket) {
  //connect
  count++;
  io.sockets.emit('count change', count);
  socket.on('disconnect', function() {
    //disconnect
    count--;
    socket.broadcast.emit('count change', count);
  });
});

クライアント(ブラウザ)から接続がきたらconnectに書かれた処理が、切断されたらdisconnectに書かれた処理がそれぞれ実行されます。今回の場合、

  1. countの初期値を0にしておく
  2. 接続がきたらcountを+1して、その値を自分を含めた接続している全員に'count change'というイベントとして通知する
  3. 切断したらcountを-1して、その値を自分以外の全員に通知する

という動作になります。

クライアント側にも処理を記述しましょう。./views/layout.jadeを以下のように修正します。

 !!!
 html
   head
     title= title
     link(rel='stylesheet', href='/stylesheets/style.css')
+    script(type='text/javascript', src='http://code.jquery.com/jquery.min.js')
+    script(type='text/javascript', src='/socket.io/socket.io.js')
+    script(type='text/javascript')
+      var socket = io.connect();
+      socket.on('count change', function(count) {
+        // event
+        $('#count').text(count);
+      });
 body!= body

jQueryとSocket.IOのクライアントライブラリを読み込んでいます。Socket.IOのクライアントライブラリは、app.js内でlisten()したことによって自動的に上記のパスに提供されます。
io.connect()をした段階で、サーバ側の'connection'イベントが発生します。また、'count change'というイベントがきたら、eventに記述された処理を実行します。今回の場合、人数が変わるたびにjQueryを用いてcountというidの要素のテキストを書き換えています。
今回の場合、記述するJavaScriptのコード量が少ないため直接layout.jadeに記述しましたが、JavaScriptは本来別ファイルに切り出して、public/javascripts/内に置くほうが良いでしょう。

最後に views/index.jade を以下のように修正します。

 h1= title
 p Welcome to #{title}
+p 現在このページを見ている人は
+  span#count
+  人います。

それでは実行してみましょう。

$ node app.js

で起動し、http://localhost:3000/ にアクセスすると、「現在このページを見ている人は1人います。」と表示されるはずです。現在のタブを開いたまま他のタブでもアクセスしてみましょう。「現在このページを見ている人は2人います」と表示されるはずです。
さらに、最初に「1人」と表示されていたタブも、リロードしなくてもすでに「2人」になっているはずです!タブを増やすたびに人数が増えて行き、閉じるたびに減っていくことを確認してください。

今回は、接続イベントと切断イベント以外のイベントを発生させるのはサーバ側でイベントを受信するのはクライアント側のみという作りになっていますが、もちろん、クライアント側で任意のイベントを発生(emit)させることや、サーバ側で受信(on)することができます。また、Socket.IOにはnamespaceという概念があり、特定のnamespaceに属する相手にのみ通知することも可能です。その例として、その4では昨年の「東京Node学園祭」のWebサイトで用いたマウスカーソルの共有を紹介します。