Mon, 31 Oct 2011

Deploying Node.js Applications to Windows Azure via Blobs or Git Sync

A few weeks ago at the Future of Web Apps London (great conference, by the way!), I gave a presentation about how to get the most out of a cloud platform. At the end of the talk, I showed a brief demo of http://twoenglishes.com (source on GitHub), a Node.js app deployed in Windows Azure. The demo was interesting in a few ways:

  1. It’s kind of entertaining. It translates between American and British English. (Try typing “color” or “aluminum” on the left side, or type “colour” or “aluminium” on the right.)
  2. It’s deployed to two data centers (one in the US and one in Europe), and Traffic Manager routes incoming traffic to the nearest data center. This has a noticeable effect in terms of latency.
  3. The images (logo and flags) are served via the Windows Azure CDN, again lowering latency for a global audience.
  4. I updated the app live on stage by pushing changes to GitHub. Those changes were synchronized live onto the running instances in the cloud, meaning that changes were reflected in the running app within a few seconds (rather than the minutes it would take to deploy an update to Windows Azure).

That last part I accomplished with my new NodeRole project (available at https://github.com/smarx/noderole). If you’ve been following my work in this area, this is similar to the SmarxRole I published earlier this year to run Ruby, Python, and Node apps in Windows Azure. The key difference is that this new NodeRole uses native Windows builds of Node and the iisnode module (the best way to run Node apps under IIS).

How It Works

The NodeRole project is a Windows Azure application consisting of a single web role. The web role contains startup tasks that install native Windows Node.js binaries and the iisnode module for running Node.js under IIS. It doesn’t, however, contain any Node.js code. Instead, the app itself is pulled down from either blob storage or a public git URL. Where the app comes from is configured in ServiceConfiguration.*.cscfg, and it defaults to deploying https://github.com/smarx/twoenglishes.

The WebRole points IIS at a local storage directory, and it pulls your application bits down from the location specified in configuration. It adds a web.config file that configures IIS to use the iisnode module to serve UI from server.js. This results in your application running as the web role and serving web requests. Every five seconds (configurable), new bits are pulled down, and those changes are immediately propagated. (By default, iisnode will restart the node processes when server.js changes on disk.)

To handle package dependencies for your code, nji is executed to pull dependencies from the npm repository.

How to Use It

Below is a console session where I demonstrate some of the features of the NodeRole project. To replicate these steps, acquire the following prerequisites first:

The session below is a bit long, but it should be clear how everything works. Note that there’s a significant amount of time (about ten minutes) between the original waz deploy command and the curl command, since it of course takes some time to do the Windows Azure deployment. Subsequent changes are just synchronized via blob storage, so they happen within a few seconds.

c:\progs\nodetest>waz create application noderoletest "South Central US"
Waiting for operation to complete...
Operation succeeded (200)

c:\progs\nodetest>waz create storage noderoletest "South Central US"
Waiting for operation to complete...
Operation succeeded (200)

c:\progs\nodetest>waz cs noderoletest
DefaultEndpointsProtocol=https;AccountName=noderoletest;AccountKey=HZJK88Zc6ndMw8Aw8bHoyRbgR2cISOFokujmMOXaaaklUS
YvKkbH/0kK6cAVsVxHeA23XIklTHSkHgZ9RR3JIg== c:\progs\nodetest>notepad ServiceConfiguration.Cloud.cscfg c:\progs\nodetest>type ServiceConfiguration.Cloud.cscfg <?xml version="1.0" encoding="utf-8"?> <!-- ********************************************************************************************** This file was generated by a tool from the project file: ServiceConfiguration.Cloud.cscfg Changes to this file may cause incorrect behavior and will be lost if the file is regenerated. ********************************************************************************************** --> <ServiceConfiguration serviceName="NodeRole" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceCon
figuration" osFamily="2" osVersion="*"> <Role name="WebRole"> <Instances count="2" /> <ConfigurationSettings> <Setting name="DataConnectionString" value="DefaultEndpointsProtocol=https;AccountName=noderoletest;AccountK
ey=HZJK88Zc6ndMw8Aw8bHoyRbgR2cISOFokujmMOXaaaklUSYvKkbH/0kK6cAVsVxHeA23XIklTHSkHgZ9RR3JIg==" /> <Setting name="PollingIntervalInSeconds" value="5" /> <Setting name="GitUrl" value="" /> <Setting name="ContainerName" value="code" /> </ConfigurationSettings> <Certificates></Certificates> </Role> </ServiceConfiguration> c:\progs\nodetest>waz deploy noderoletest production NodeRole.cspkg ServiceConfiguration.Cloud.cscfg Waiting for operation to complete... Operation succeeded (200) c:\progs\nodetest>md code c:\progs\nodetest>cd code c:\progs\nodetest\code>copy con server.js require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain' }); res.end('Hello, World!\n'); }).listen(process.env.PORT || 3000); ^Z 1 file(s) copied. c:\progs\nodetest\code>SyncToContainer . noderoletest HZJK88Zc6ndMw8Aw8bHoyRbgR2cISOFokujmMOXaaaklUSYvKkbH/0kK6cAVs
VxHeA23XIklTHSkHgZ9RR3JIg== code Uploading server.js c:\progs\nodetest\code>curl http://noderoletest.cloudapp.net Hello, World! c:\progs\nodetest\code>notepad server.js c:\progs\nodetest\code>type server.js require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain' }); res.end('Hello, World again!\n'); }).listen(process.env.PORT || 3000); c:\progs\nodetest\code>SyncToContainer . noderoletest HZJK88Zc6ndMw8Aw8bHoyRbgR2cISOFokujmMOXaaaklUSYvKkbH/0kK6cAVs
VxHeA23XIklTHSkHgZ9RR3JIg== code Uploading server.js c:\progs\nodetest\code>curl http://noderoletest.cloudapp.net Hello, World again! c:\progs\nodetest\code>notepad package.json c:\progs\nodetest\code>type package.json { "name": "noderoletest", "version": "1.0.0", "dependencies": { "express": "2.4.6" } } c:\progs\nodetest\code>notepad server.js c:\progs\nodetest\code>type server.js var app = require('express').createServer(); app.get('/', function (req, res) { res.send('Hello from Express!'); }); app.listen(process.env.PORT || 3000); c:\progs\nodetest\code>SyncToContainer . noderoletest HZJK88Zc6ndMw8Aw8bHoyRbgR2cISOFokujmMOXaaaklUSYvKkbH/0kK6cAVs
VxHeA23XIklTHSkHgZ9RR3JIg== code Uploading server.js Uploading package.json c:\progs\nodetest\code>curl http://noderoletest.cloudapp.net Hello from Express! c:\progs\nodetest\code>waz delete deployment noderoletest production Waiting for operation to complete... Operation succeeded (200) c:\progs\nodetest\code>waz delete application noderoletest Waiting for operation to complete... Operation succeeded (200) c:\progs\nodetest\code>waz delete storage noderoletest Waiting for operation to complete... Operation succeeded (200)