10. Deployment and Development I: Rolling Your Own

With the ability to build nontrivial Node.js applications firmly under your belt, you can now turn your attention to some additional topics on deployment and development of these apps. In this chapter, you start by looking at some of the various ways that people deploy and run their Node apps on production servers, looking at both UNIX/Mac and Windows options. You also see how you can take advantage of multiprocessor machines, despite the fact that Node.js is a single-threaded platform.

You then move on to look at adding support for virtual hosts on the server, as well as securing your applications with SSL/HTTPS support. Finally, you take a quick look at some of the issues with multiplatform development when you have people working on a Node app with both Windows and Linux/Mac machines.

Deployment

To run your Node.js applications thus far, you’ve just been using the following from the command line:

node script_name.js

This approach works fine for development. However, for deploying your applications to live servers, you might want to add in an extra layer of reliability to help in those cases when they crash. Although you, of course, strive to avoid bugs in your servers, some will eventually creep in, so you’d like to have your servers recover as quickly as possible from such problems.

Let’s look at some of the options now.

Level: Basic

At the most basic level, you can just use an infinite loop to run your scripts. This loop just automatically restarts the node process whenever it crashes. On UNIX and Mac computers running bash, the script can be as simple as:

#!/bin/bash
while true
do
    node script_name.js
done

On Windows, you can use batch files to do the job for you, as follows (save this to run_script.bat or something similar and run it):

: loop
node script_name.js
goto loop
: end

These scripts make sure that your Node process is always restarted if it crashes or is terminated accidentally.

You can actually go one step further and use a command called tee to write the standard output of the node process to a log file. You usually pipe the output of your process to the tee command using the | (“pipe”) operator in the command line, and tee then reprints the output on the standard output in addition to writing it to the specified log file, as follows:

node hello_world.js 2>&1 | tee -a /var/server/logs/important.log

The -a flag to tee tells it to append the output to the specified log file instead of simply overwriting it. The 2>&1 is a bit of shell wizardry to redirect the stderr (standard error) output to the same channel as stdout (regular output) so they both get sent to tee.

Thus, you can improve your deployment one step further by running your scripts as follows:

bash ./run_script.sh 2>&1 | tee –a /var/server/logs/important.log

Or, on Windows, you can use the following:

run_script.bat 2>&1 | tee –a /var/server/logs/important.log

See the sidebar “Tee for Windows” for more information on actually making this technique work.

Using screen to Monitor the Output

Although you are always logged in on Windows and can easily log back in to the desktop to see what is going on with your Node servers, on UNIX servers such as Linux or Mac OS X, you frequently want to log out and just leave the servers running. The problem is that when you do this, you lose your standard output (stdout) handle, so the output is lost unless you are also writing to a log file. Even then, the log file sometimes is missing important information printed out by either Node or the OS regarding problems they encounter.

To get around this issue, most UNIX systems support a magical little utility called screen, which lets you run programs as if they constantly have a controlling terminal (also known as a tty), even if you are logged out. Installation is specific to your platform, but Mac OS X has it by default, and most Linux platforms can get it by doing something similar to apt-get screen.

Now, before you run your Node server, you can run screen and then run Node within that. To detach from screen, you press the key sequence Ctrl+A Ctrl+D. To get back into the screen, you run the command screen -r (for resume). If you have multiple screen sessions running, they each have a name and you also have to type in the name of the session you want to resume.

You use screen for every single Node server you have running. It’s a great way to come back every once in a while and browse how things are running.

Level: Ninja

The previous scripts for running applications are pretty useful, and you can use them for low-traffic sites that don’t crash very often. They do have a couple of serious limitations, however:

1. If an app gets into a state where it is constantly crashing, even when restarted, these scripts blindly keep restarting it, resulting in what is known as a “run-away process.” You never get it to start properly, and your server will work itself to death constantly trying.

2. Although these scripts can keep your processes up and running, they don’t do a good job of telling you when they’re in trouble, specifically if they’re using up too much memory. There are still instances where a Node process can consume a bit too much memory or otherwise get into trouble with system resources. It would be great if there were a way to detect this and terminate them and let them start again from scratch.

Thus, in the following sections, you look at a couple of more advanced deployment options, which start to deviate quite greatly for UNIX and Windows at this point.

Linux/Mac

The previous version of your script (when viewed as pseudo-code) was effectively

while 1
      run node
end

You can upgrade this script now to do something more along the following lines:

PID = run node
while 1
      sleep a few seconds
      if PID not running
         restart = yes
      else
        check node memory usage
        if using too much memory
           restart = yes

      if restart and NOT too many restarts
         PID = restart node
end

There are two key tasks you need to perform to make this work:

1. Get the process ID (pid) of the last-started node server.

2. Figure out how much memory the node process is using.

For the first, you use the pgrep command, available on Mac and most UNIX platforms. When used with the -n flag, it gives you the process ID of the latest instance of the given process name. You can also use the -f flag to tell it to match the name of the script, in case your computer is running multiple Node servers. Thus, to get the pid of the latest created Node process running script_name.js, you use

pgrep -n -f "node script_name.js"

Now, to get the amount of memory that your process is using, you need to use two commands. The first is the ps command, which, when given the wux flags (wup on some platforms—check your documentation), tells you the total virtual memory consumption of the process, as well as the current resident size (the one you want). The output of ps wux $pid is as follows:

USER    PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
marcw  1974   6.6  3.4  4507980 571888   ??  R    12:15AM  52:02.42 command name

You want the sixth column (RSS) of the second line of this output. To get this, you have to first run the awk command to get the second line only, and then you run it again to get the value in the sixth column, as follows:

ps wux $PID | awk 'NR>1' | awk '{print $6}'

When you know the amount of memory this process is consuming, you can make a decision as to whether it’s using too much and then terminate and restart it if necessary.

I won’t print out the full code of the shell script here, but you can see it in the code for Chapter 10 in the GitHub repository, as node_ninja_runner.sh. To use it, you can run it as

node_ninja_runner.sh server.js [extra params]

You do, of course, need to make sure it’s elevated to superuser privileges if you’re going to listen on a port such as 80 or 443.

Windows

On Windows, the best way to run your apps in a reliable way that can be monitored would be to use a Windows Service. Node.js itself is not set up to be a Windows Service, but fortunately you can use some tricks to get it to behave like one.

Through the use of a nice little program called nssm.exe (the Non-Sucking Service Manager) and an npm module called winser, you can install your Node web applications as services and manage them fully through the Windows Management Console. To set this up, you need to do two things:

1. Install a new custom action in package.json for the web application.

2. Download and install winser and have it do its work.

For the first step, you just need to add the following to the package.json file:

{
  "name": "Express-Demo",
  "description": "Demonstrates Using Express and Connect",
  "version": "0.0.2",
  "private": true,
  "dependencies": {
    "express": "4.x"
  },
   "scripts": {
    "start": "node 01_express_basic.js"
  }
}

For the second step, you can just do the following:

C:UsersMarkLearningNodeChapter10> npm install winser

(Or you can add winser to your dependencies.) Then, when you are ready to install your app as a service, you can go to the project folder holding the package.json for the app and run

C:UsersMarkLearningNodeChapter10> node_modules.binwinser -i
The program Express-Demo was installed as a service.

To uninstall the service, just run winser -r, as follows:

C:UsersMarkLearningNodeChapter10> node_modules.binwinser -r
The service for Express-Demo was removed.

You can then go to the Windows Management Console and start, stop, pause, or otherwise manage the service to your heart’s content!

Multiprocessor Deployment: Using a Proxy

I’ve mentioned many times that Node.js effectively operates as a single-threaded process: all the code that your scripts are executing is in the same thread, and you use asynchronous callbacks to get as much efficiency out of your CPU as possible.

However, you must then ask these questions: What do you do on a system with multiple CPUs? How can you extract maximal functionality out of your servers? Many modern servers are nice, powerful 8–16 core machines, and you’d like to use all of them if possible.

Fortunately, the answer is reasonably simple: just run one node process per core you want to utilize (see Figure 10.1). You can choose one of a number of strategies to route incoming traffic to the various different node processes, as suits your requirements.

Image

Figure 10.1 Running multiple node processes for the same app

The problem you are now faced with is that you have not one, but n Node processes running, all of which must listen on a different port (you can’t have multiple people listening on the same port number). How do you get traffic to mydomain:80 to these servers?

You can solve this problem by implementing a simple round-robin load balancer that is given a list of node servers running your particular web application. You then redirect incoming requests to your domain to these other servers, one at a time. You can, of course, experiment with more advanced strategies that monitor load, availability, or responsiveness.

To implement the load balancer, you first compile a list of servers that you are running. Say you have a server with four cores; you give three of them over to the app (and leave the fourth for running other system services). You run these app servers on ports 8081, 8082, and 8083. Thus, your list of servers is

{
    "servers": [
        "http://localhost:8081",
        "http://localhost:8082",
        "http://localhost:8083"
    ]
}

The code for these simple servers is shown in Listing 10.1. It is the simplest web server that you’ve seen in this book, with an additional bit of code to get the port number on which it should listen from the command line (you learn more about command-line parameters in Chapter 12, “Command-Line Programming”).

Listing 10.1 Trivial HTTP Server


var http = require('http');

if (process.argv.length != 3) {
    console.log("Need a port number");
    process.exit(-1);
}

var s = http.createServer(function (req, res) {
    res.end("I listened on port " + process.argv[2]);
});

s.listen(process.argv[2]);


You can launch your three servers on UNIX/Mac platforms by typing

$ node simple.js 8081 &
$ node simple.js 8082 &
$ node simple.js 8083 &

On Windows platforms, you can launch them by simply running the following three commands in different command prompts:

node simple.js 8081
node simple.js 8082
node simple.js 8083

You can now build your proxy server using the npm module http-proxy. The package.json looks as follows:

{
  "name": "Round-Robin-Demo",
  "description": "A little proxy server to round-robin requests",
  "version": "0.0.2",
  "private": true,
  "dependencies": {
    "http-proxy": "1.x"
  }
}

The code for the proxy is quite simple, as shown in Listing 10.2. It basically maintains an array of available servers, as shown previously, and then for each request coming to the service, cycles through those available servers and forwards the requests to them.

Listing 10.2 A round-robin Proxy Load Balancer (roundrobin.js)


var http = require("http"),
    httpProxy = require('http-proxy'),
    fs = require('fs');

var servers = JSON.parse(fs.readFileSync('server_list.json')).servers;

// 1. Create the proxy server.
var proxy = httpProxy.createProxyServer({});

// 2. Create a regular HTTP server.
var s = http.createServer(function (req, res) {
    var target = servers.shift();            // 3. Remove first server
    proxy.web(req, res, { target: target }); // 4. Re-route to that server
    servers.push(target);                    // 5. Add back to end of list
});

s.listen(8080);


To put all this in action, you now run node roundrobin.js, and you can start to query it as follows:

curl -X GET http://localhost:8080

As you run this command multiple times, you should see the output showing the actual port number that is being listened to for the current processing server.

Multiple Servers and Sessions

Running multiple servers on different CPUs and servers is a great way to spread the load and give you a cheap path to scalability. (Servers under heavy load? Just add more!) But they create one small complication that you need to address before you can use them effectively: the session data is currently stored per process using local MemoryStore objects. (See “POST Data, Cookies, and Sessions” in Chapter 7, “Building Web Applications with Express.”) This creates a rather serious problem, as shown in Figure 10.2.

Image

Figure 10.2 Multiple instances of the servers and session data

Because each Node server stores its own record of sessions in its own memory store, when two requests from the same client go to two different node processes, they have different ideas of current state of the session data from the client and quickly get horribly confused. You have two obvious ways to solve this problem:

1. Implement a more advanced router that remembers to which Node server requests from a particular client are being sent and ensure that all traffic continues to be routed in the same way for the same client.

2. Somehow pool the memory stores that the session data uses so that all Node processes can access it.

I prefer the second solution because it is quite simple to implement and much less complicated.

To set up this solution, you need to first choose a pooled memory store. The obvious candidates for this are memcached and Redis, both of which are effectively large memory-based key/value stores that can be spread across different computers. You work with memcached in this chapter because it’s entirely adequate for your needs and extremely lightweight. Your setup should look similar to that shown in Figure 10.3.

Image

Figure 10.3 Using memory-based key/value stores to implement sessions

Installation on Windows

For Windows users, you can install one of the many binary installers available for memcached that can be found with a simple Internet search. You don’t need the latest and greatest version; anything in the 1.2.x series or later is fine. To install it as a service, you run

c:memcachedmemcached -d install

You can then run the memcached service by typing

net start memcached

And finally, you can edit HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/memcached Server and modify the ImagePath to be

"C:/memcached/memcached.exe" -d runservice -m 25

This sets the amount of memory available to memcached to be 25MB, which is usually more than enough for development (you can choose whatever values you want here, of course). It listens on port 11211.

Installation on Mac and UNIX-like Systems

If you’re on a system with some sort of packaging manager, you can often use something like (you might need to run sudo for this)

apt-get install memcached

and you’re good to go. If you want to build from source, you first need to get libevent from http://libevent.org. Download, build, and install (as superuser) the library. It should place the service in /usr/local/lib.

Next, visit memcached.org and download the latest source tarball (.tar.gz file) and configure, build, and install it as well. Again, do so as superuser.

To run memcached after it is installed, run the following command:

/usr/local/bin/memcached -m 100 -p 11211 -u daemon

This command tells the service to run using 100MB of memory and listen on port 11211, running as the user daemon. It typically refuses to run as root.

Getting memcached into express

Now that you have memcached installed and running (assume localhost on port 11211), you need to get a MemoryStore object for it so that you can use it for your session data. Fortunately, the Node.js community is so active that there’s already an npm module called connect-memcached. So, let’s add the following to your dependencies in package.json:

"connect-memcached": "0.x"

Then you can modify your session creation code as follows:

var express = require('express'),
    cookieParser = require('cookie-parser'),
    morgan = require('morgan'),
    session = require('express-session');

var port_number = 8080;
if (process.argv.length == 3) {
    port_number = process.argv[2];
}


// pass the session object so it can inherit from MemoryStore
var MemcachedStore = require('connect-memcached')(session);
var mcds = new MemcachedStore({ hosts: "localhost:11211" });

var app = express()
    .use(morgan('dev'))
    .use(cookieParser())
    .use(session({ secret: "cat on keyboard",
                   cookie: { maxAge: 1800000 },
                   resave: false,
                   saveUninitialized: true,
                   store: mcds}))
    .use(function (req, res) {
        var x = req.session.last_access;
        req.session.last_access = new Date();
        res.end("You last asked for this page at: " + x);
    })
    .listen(port_number);

Now all the Node servers can be configured to use the same MemoryStore for session data and will be able to fetch the correct information, no matter where the request is processed.

Virtual Hosting

Running multiple websites on one server has been a major requirement of web application platforms for many years now, and fortunately Node.js offers a couple of very workable solutions for you when you build your apps with express.

You implement virtual hosting by adding the Host: header to the incoming HTTP request, one of the major features in HTTP/1.1 (see Figure 10.4).

Image

Figure 10.4 The basic idea behind virtual hosting

Express Support

The ability to run multiple virtual hosts used to be built directly into express but is now in a module called (wait for it ...) express-vhost. To make it work, you create one express server for each host you want to support and then another “master” server that is given the other virtual servers and routes requests to them as appropriate. Finally, you use the vhost connect middleware component to put this all together. The code is shown in Listing 10.3.

Listing 10.3 Virtual Hosts in Express (vhost_server.js)


var express = require('express'),
    evh = require('express-vhost'),
    morgan = require('morgan');

var one = express();
one.get("/", function (req, res){
    res.send("This is app one!")
});


// App two
var two = express();
two.get("/", function (req, res){
    res.send("This is app two!")
});

// App three
var three = express();
three.get("/", function (req, res){
    res.send("This is app three!")
});


// controlling app
var master_app = express();
master_app.use(evh.vhost());
master_app.use(morgan('dev'));

evh.register("app1", one);
evh.register("app2", one);
evh.register("app3", one);

master_app.listen(8080);


Securing Your Projects with HTTPS/SSL

For those parts of your application that handle sensitive data such as user passwords, personal data, bank accounts, or payment information, or indeed anything else somewhat private in nature, you should secure your applications with SSL-encrypted HTTPS traffic. Although there is some overhead in creating the SSL connection and encrypting the traffic, the security benefits are, of course, of paramount importance.

To add support for SSL/HTTPS to your applications, you need to first generate some test certificates and then add support for the encrypted traffic to the application. For the latter, you again have two ways of doing this: either through built-in support in express or by using a proxy server.

Generating Test Certificates

To work with encrypted traffic on your development machines, you need to generate some test certificates. You need to generate two files—privkey.pem and newcert.pem—the private key and certificate, respectively. All UNIX/Mac machines come with a tool called openssl that you can use to generate these two files.

For Windows users, you can download a Win32 version of openssl.exe by visiting http://gnuwin32.sourceforge.net/packages/openssl.htm. Then the commands you run are the same as on the other platforms.

To generate the two certificate files, run the following three commands:

openssl genrsa -out privkey.pem 1024
openssl req -new -key privkey.pem -out certreq.csr
openssl x509 -req -days 3650 -in certreq.csr -signkey privkey.pem -out newcert.pem

With these two files in hand, you can work on using them in your apps. Note that you should never use them in production sites; if you attempt to view a website in a browser secured with these certificates, you get a horribly loud and dangerous-sounding warning about how they are not to be trusted at all. You typically have to purchase production certificates from a trusted certificate authority.

Built-in Support

Not surprisingly, Node.js and express provide support for SSL/HTTPS streams right out of the box, via the built-in https module provided by Node. You can actually run an https module server to do the listening on the HTTPS port (by default 443, but for development, you can use 8443 to avoid having to elevate the permissions of your Node process), and then it routes traffic to the express server after the encrypted stream has been negotiated and created.

You create the HTTPS server and pass in as options the locations of the private key and certificate file you plan to use to sign the site. You also give it the express server to which it can send traffic after encryption is established. The code for this support is shown in Listing 10.4.

Listing 10.4 Express/https Module SSL Support (https_express_server.js)


var express = require('express'),
    https = require('https'),
    fs = require('fs');

// 1. Load certificates and create options
var privateKey = fs.readFileSync('privkey.pem').toString();
var certificate = fs.readFileSync('newcert.pem').toString();

var options = {
    key : privateKey,
    cert : certificate
}

// 2. Create express app and set up routing, etc.
var app = express();
app.get("*", function (req, res) {
    res.end("Thanks for calling securely! ");
});


// 3. start https server with options and express app.
https.createServer(options, app).listen(443, function () {
    console.log("Express server listening on port " + 443);
});


You can view these encrypted pages in a web browser by entering https://localhost:443 in the address bar. The first time you view them, you get a scary warning about how insecure your test certificates are.

Proxy Server Support

As with many of the other things you’re investigating in this chapter, you can also use the remarkably powerful http-proxy module to handle SSL/HTTPS traffic for you. Using this module has a couple of key advantages over using built-in support for HTTPS in that you let the actual app servers run as regular HTTP servers, leaving them “hidden” behind the HTTPS proxy server and freeing them from tedious encryption work. You can then also run as many as you want using round-robin load balancing seen previously or look at other creative ways to set up your configuration.

The methodology for this support isn’t terribly different from that used in the previous example with express; you create an instance of Node’s built-in HTTPS server class and also create an instance of a proxy server that knows to route traffic to a regular HTTP server (the app server). You then run the HTTPS server, and when it has established the secure connection, it passes the request to the proxy server, which in turn passes it to the app server(s). The code for this support is shown in Listing 10.5.

Listing 10.5 http-proxy SSL Support (https_proxy_server.js)


var httpProxy = require('http-proxy'),
    https = require('https'),
    fs = require('fs');

// 1. Get certificates ready.
var privateKey = fs.readFileSync('privkey.pem').toString();
var certificate = fs.readFileSync('newcert.pem').toString();

var options = {
    key : privateKey,
    cert : certificate
}

// 2. Create an instance of HttpProxy to use with another server
var proxy = httpProxy.createProxyServer({});

// 3. Create https server and start accepting connections.
https.createServer(options, function (req, res) {
    proxy.web(req, res, { target: "http://localhost:8081" });
}).listen(443);


This may seem like a lot of routing and redirecting, but the Node development group is almost fanatical about performance, and these components are sufficiently lightweight that they shouldn’t add much perceptible delay to the processing of requests for your web applications.

Multiplatform Development

One of the great strengths of Node.js is not only its strong support for UNIX and Mac-like computers but also its ability to run on Windows machines. As I was writing this book, I played around with all the samples and ran things on Windows without much trouble at all.

Indeed, you can have people on your projects developing on whatever platforms they want, as long as you take care to prepare for a couple of minor issues that will crop up: differences in configurations and path differences.

Locations and Configuration Files

Windows and UNIX-like operating systems naturally keep things in different places. One way that you can mitigate this problem is to use a configuration file to store these locations. Then your code can be made flexible enough to know where to find things without having to handle each platform separately.

You actually first used this technique in Chapter 8, “Databases I: NoSQL (MongoDB),” and Chapter 9, “Databases II: SQL (MySQL),” where you put database configuration information in a file called local.config.json. You can continue to follow this technique expand on it, and generally put any information that affects the running of your application into that file. Indeed, you don’t need to restrict it simply to file locations or paths, but you can configure port numbers or build types from there as well:

{
  "config": {
    "db_config": {
        "host": "localhost",
        "user": "root",
        "password": "",
        "database": "PhotoAlbums",

        "pooled_connections": 125,
        "idle_timeout_millis": 30000
    },

    "static_content": "../static/",
    build_type: "debug"
  }
}

What you normally do is actually check this file in to the version control system (such as GitHub) as local.config.json-base and not have a local.config.json in the source tree. To run the application, you then copy this base file over to local.config.json, update the values as appropriate for your local running setup, and then run it that way. Any time you want to use these local configuration variables, you just add

var local = require('local.config.json');

Then, in code, you’re free to refer to local.config.variable_xyz as you need.

Handling Path Differences

The other major difference between Windows and UNIX-like operating systems is paths. How do you code for things such as require when you have handlers in subfolders of your current project directory and so on?

The good news is that a vast majority of the time, you’ll find that Node accepts the forward slash (/) character. When you require modules from relative paths (such as “../path/to/sth”), it just works as you would expect it to. Even for cases in which you’re using the fs module APIs, most of them are also able to handle the differences in path types between the platforms.

For those cases in which you absolutely need to work with the different path styles, you can use the path.sep property in Node.js and some clever use of array joining and splitting, for example:

var path = require('path');
var comps = [ '..', 'static', 'photos' ];
console.log(comps.join(path.sep));

The process global in Node.js always tells you what platform you’re running on and can be queried as follows:

if (process.platform === 'win32') {
    console.log('Windows');
} else {
    console.log('You are running on: ' + process.platform);
}

Summary

In this chapter, you looked at how to take your Node applications and run them in production environments, investing execution scripts, load balancing, and multiprocess applications. This chapter also showed you how to secure your web applications using HTTPS over SSL and finally described multiplatform development, showing that it’s not nearly as scary as you might fear.

You now have a pretty hefty set of tools that enable you to build and deploy Node.js applications. In the next chapter, we’re going to look at other options for deploying your applications, notably public hosting services such as Heroku and Microsoft Azure. These give you the flexibility and power of full production-ready servers, without requiring you to buy the hardware or become system administrators.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset