Continuous deployment

The more streamlined our processes, the more productive we can be. Continuous deployment is about committing small ongoing improvements to a production server in a time saving, efficient way.

Continuous deployment is especially relevant to team collaboration projects. Instead of working on separate forks of the code and spending extra time, money, and effort on integration, everyone works on the same code base so integration is seamless.

In this recipe, we'll create a deployment flow using Git as a version control tool. While this may not be Node, it can certainly boost productivity for coding, deploying, and managing Node projects.

Note

If we're a little unfamiliar with Git, we can gain insight from Github's help documents, http://help.github.com.

Getting ready

We'll need Git installed on both our server and desktop systems, instructions for different systems can be found here http://book.git-scm.com/2_installing_git.html. If we're using Linux with the apt-get package manager we can do:

sudo apt-get install git git-core

If we are installing Git for the first time, we'll have to set the personal information configuration settings as follows:

git config --global user.name "Dave Clements"
git config --global user.email "[email protected]"

We'll be using our login app, which we deployed to our server in the first recipe. So let's SSH into our server and enter the /var/www/login directory.

ssh -l root nodecookbook.com -t "cd /var/www/login; bash"

Since we'll not be running our app as root, we'll keep things simple and change the listening port in login/app.js to 8000:

app.listen(8000);

How to do it...

Once we've logged in to our server and installed Git (see Getting ready) in the login folder, we say the following:

git init
git add *
git commit -m "initial commit"

Next, we create a bare repository (it has a record of all the changes but no actual working files) which we'll be pushing changes to. This helps to keep things consistent.

We'll call the bare repository repo, because this is the repository we'll be pushing our changes to and we'll create it within the login folder:

mkdir repo
echo repo > .gitignore
cd repo
git --bare init

Next. we hook up our bare repo to the login app repository, and push all the commits to repo.

cd ..
git remote add repo ./repo
git push repo master

Now we'll write a Git hook that instructs the login repository to pull any changes from the bare repo repository, then restarts our login app whenever repo is updated via a remote Git push.

cd repo/hooks
touch post-update
chmod +x post-update
nano post-update

With the file open in nano, we write the following code:

#!/bin/sh

cd /root/login
env -i git pull repo master

exec forever restart /root/login/app.js

Saving our hook with Ctrl + O, then exit with Ctrl + X.

If we ever make Git commits to the login repository, the two repositories could go out of sync. To fix this, we create another hook for the login repository:

#!/bin/sh
git push repo

We store this in login/.git/hooks/post-commit, ensuring it has been made executable using chmod +x post-commit.

We'll be making commits to the repo remotely via the SSH protocol. Ideally, we want to create a system user just for Git interactions.

useradd git
passwd git   #set a password

mkdir /home/git
chown git /home/git

We've also created home directories for the git user to make it easy for forever to store logs and PID files. We'll need to make git the owner of the login app, allowing us to manage it using Git through SSH:

cd /var/www
chown -R git login

Finally (for the server-side setup), we log in as the git user and start our app using forever.

su git
forever start /var/www/login/app.js

Assuming our server is hosted at nodecookbook.com, we could now access the login app at http://nodecookbook.com:8000.

Back on our desktop, we clone the repo repository:

git clone ssh://[email protected]/var/www/login/repo

This will give us a repo directory, containing all the generated files perfectly matching our original login folder. We can then enter the repo folder and make a change to our code (say, altering the port in app.js).

app.listen(9000);

Then we commit the change and push to our server.

git commit -a -m "changed port"
git push

On the server side, our app should have automatically restarted, resulting in our app now being hosted from http://nodecookbook.com:9000 instead of http://nodecookbook.com:8000.

How it works...

We created two Git repositories. The first is the login app itself. When we ran git init, a .git directory was added to the login folder. git add * adds all of the files in the folder and commit -m "initial commit" plants our additions into Git's version control system. So now our entire code base is recognized by Git.

The second is repo, which is a created with the --bare flag. This is a sort of skeleton repository providing all of the expected Git functionality, but lacking the actual files (it has no working tree).

While it may seem overly complex to use two repositories, it actually simplifies things greatly. Since Git does not allow pushes to a branch that is currently checked in, we would have to create a separate dummy branch so we can checkout of the master and into the dummy branch. This creates problems with the Git hooks and restarting our app. The hooks try to start the app for the wrong branch. The branches can also quickly become out of sync, and the hooks only add fuel to the fire.

As repo is within the login directory, we create a .gitignore file telling Git to disregard this subdirectory. Even though login and repo are on the same server, we add repo as a remote repository. This puts some necessary distance between the repositories and allows us to later use our first Git hook to cause login to pull changes from repo. A Git push from repo to login wouldn't cause login to update its working directory, whereas pulling from repo into login does initiate a merge.

After our remote add, we perform an initial push from the master branch (login) to repo, now they're singing off the same hymn sheet.

Then we created our hooks.

Git hooks are executable files which reside in the repository's hook folder. There are a variety of available hooks (already in the folder, but suffixed with .sample). We used two: post-update and post-commit. One executes after an update (for example, once changes have been pulled and integrated into repo), and one after a commit.

The first hook, login/repo/hooks/post-update, essentially provides our continuous deployment functionality. It changes its working directory from repo to login using cd, and commands a git pull. The git pull command is prefixed with env -i. This prevents problems with certain Git functionality that would otherwise execute the Git commands on behalf of repo no matter what directory we sent our hook script to. Git utilizes a $GIT_DIR environment variable to lock us in to the repository that the hook is called from. env -i deals with this by telling git pull to ignore (-i) all environment variables.

Having updated the working directory, our hook then goes on to call forever restart, thus causing our app to reinitialize with the committed changes in place.

Our second hook is little more than a polyfill to ensure code base consistency in the event that commits are made directly to the login repository. Making commits directly to the login directory won't update the working tree, nor will it cause our app to restart but the code between login and repo will at least maintain synchronicity.

For the sake of damage limitation (if we were ever compromised), we create a specific account for handling Git updates over SSH, giving it a home directory, taking ownership of the login app and executing the primary initialization of our app.

Once the server is configured it's plain sailing. After cloning the repo repository to our local development environment, we simply make a change, add and commit that change, then push to the server.

The server receives our push request, updates repo, initiates the post-update hook which makes login pull the changes from repo, after which the post-update hook uses forever to restart app.js, and thus we have a continuous deployment work flow.

We can potentially have as many clones from as many locations as we like, so this method lends itself well to geographically-independent team collaboration projects both large and small.

There's more...

We could avoid uploading modules by using npm install in the post-update hook. Also, Git hooks don't have to be written in shell script, we can write them in Node!

Building module dependencies on update

Some Node modules are written in pure JavaScript, others have C or C++ bindings. Those with C or C++ bindings have to be built from source — a task which is system specific. Unless our live server environment is identical to our development environment, we shouldn't simply push code build for one system onto another.

Further, to save on transfer bandwidth and have faster synchronizations, we could have our Git hooks install all modules (native bindings and JavaScript) and have Git ignore the node_modules folder entirely.

So in our local repository, let's do the following:

echo node_modules >> .gitignore

Then we'll change the post-update hook in our bare remote repository (login/repo/hooks) to:

#!/bin/sh



cd /root/login

env -i git pull repo master && npm rebuild && npm install



exec forever restart /root/login/app.js

We've added&& npm rebuild && npm install to the git pull line (using&& to ensure they benefit from the env -i command).

Now if we added a module to package.json, and did a git commit -a followed by git push, our local repo would push the package.json to the remote repo. This would trigger the post-update hook to pull changes into the main login repository, and follow this up with an npm rebuild (to rebuild any C / C++ dependencies) and an npm install (to install any new modules).

Writing a Node Git hook for integrated testing

Continuous deployment is an extension of continuous integration which generally carries the expectation that a thorough test suite is run against any code changes for quality assurance.

Our login app (being a basic demonstration site) doesn't have a test suite (for info on test suites, seeChapter 9, Writing Your Own Node Modules), but we can still write a hook that executes any tests that could be added to login in the future.

What's more, we can write it in Node, which has the added bonus of functioning cross platform (on Windows, for example).

#!/usr/bin/env node

var npm = require("npm");

npm.load(function (err) {
    if (err) { throw err; }

    npm.commands.test(function (err) {
        if (err) { process.exit(1); }       
    });

});

We would place this code on the server into login/repo/hooks/pre-commit and make it executable (chmod +x pre-commit).

The first line sets node as the scripts interpreter directive (much as #!/bin/sh sets the sh shell for shell scripts). Now we're in Node country.

We use npm programmability, to load the package.json file for our app, and then run the test script (if any is specified).

We then add the following to our package.json file:

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.5"
    , "jade": ">= 0.0.1"
  },
   "scripts": {
    "test": "node test"
  },
  "devDependencies": {"npm": "1.1.18"}
}

Then do the following:

npm -d install

Now whenever we push to repo, any changes will only be committed if they pass the tests. As long as we have a well-written test suite, this is a great way to maintain good code.

Tip

For our scripts.test property, we used node test (as In Chapter 9, Writing Your Own Node Modules). However, there are more advanced test frameworks available to us, such as Mocha http://visionmedia.github.com/mocha/.

Note

This Node Git hook is adapted (with permission) from a gist by Domenic Denicola, which can be found at https://gist.github.com/2238951.

See also

  • Deploying to a server environment discussed in this chapter
  • Automatic crash recovery discussed in this chapter
  • Creating a test-driven module API discussed In Chapter 9,Writing Your Own Node Modules
  • Hosting with a Platform as a Service provider discussed in this chapter
..................Content has been hidden....................

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