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:
- 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.)
- 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.
- The images (logo and flags) are served via the Windows Azure CDN, again lowering latency for a global audience.
- 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:
- https://github.com/smarx/noderole – NodeRole itself
- https://github.com/smarx/nji – a Node package installer written in .NET
- https://github.com/smarx/SyncToContainer – a simple command-line tool to upload blobs to a container. (You could instead just drag/drop the files into a container.)
- “gem install waz-cmd” – a Ruby gem that gives you a command-line interface to the Windows Azure Service Management API. (You can instead do the deployment via the Windows Azure portal or using the PowerShell Cmdlets.)
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)