FacebookLinkedInShare

Along with the company’s awesome decision to “move from Ruby to Node.js”, we (the almighty DevOps) were introduced to a great challenge: create a full CI process.

Within that process we had to implement an easy to use deployment tool, we then decided to use:

  1. GitHub is a web-based Git repository hosting service
  2. Jenkins is an automation server which provides a lot of plugins to support deployment and automation
  3. Capistrano is a remote server automation and deployment tool written in Ruby
  4. PM2 is a production process manager for Node.js
  5. Gulp is a Node.js Build system and tasks manager

Before Node.js we worked with Ruby stack, which was deployed using Ansible and Jenkins. This time we choose Capistrano over Ansible for several reasons:

  1. to keep it friendly using Ruby instead of YAML
  2. for Capistrano’s built-in current symlink, release management and rollback features
  3. since Ansible was recently purchased by Redhat, we, as Ubuntu users, got a bit anxious wondering whether they are going enterprise

The Process

Schematically, the process looks like this:

Untitled Diagram (2)

We use micro-services architecture and each service has it’s own repository on GitHub, with a corresponding name.

The sequence of events may be described as follows:

  1. developers upload new code to the master branch of the repository, which triggers Jenkins via GitHub repository’s web-hook; the web-hook uses this Jenkins/GitHub plugin
  2. Jenkins stops the traffic to the first server, in our case we use AWS SDK, to remove the server from its ELB and add it back at the end of the deployment
  3. Capistrano deployment flow is:
    • pull the new code from Git
    • run pre-deploy tests with gulp (unit tests, coding standards)
    • implement and run the service in pm2
    • run post-deploy tests with gulp (service check, end-to-end)
    • replace /current symlink

We use CapHub for Capistrano’s multiconfig abilities:

Capistrano File Structure

$ ➜ tree
├── Capfile
├── Gemfile
├── config
│   ├── deploy
│   │   ├── service1
│   │   │   ├── production.rb
│   │   │   └── staging.rb
│   │   └── service2
│   │       ├── production.rb
│   │       └── staging.rb
│   ├── deploy.rb
│   └── flow
│   └── production.rb
|   └── staging.rb
└── tasks
    ├── gulp.cap
    ├── npm.cap
    └── pm2.cap

As you can see in the file tree, each service gets its own directory under config/deploy/ that has its environment files in it.

Inside of config/flow/production.rb we define the deployment flow:

after 'deploy:finished', 'npm:install'
after 'npm:install',     'gulp:predeploy'
after 'gulp:predeploy',  'pm2:install'
after 'pm2:install',     'gulp:postdeploy'
after 'deploy:failed',   'deploy:rollback' #Does rollback to previous working build

inside of tasks/gulp.cap we define the gulp tasks:

namespace :gulp do
  task :predeploy do
    execute "export NODE_ENV=#{fetch(:stage).split(':').last}; gulp pre-deploy --no-color"
  end
  task :postdeploy do
    execute "export NODE_ENV=#{fetch(:stage).split(':').last}; gulp post-deploy --no-color"
  end
end

inside of tasks/npm.cap we define the npm tasks:

namespace :npm do
  task :install do
    execute "npm install --no-progress --silent --no-spin --prefix #{current_path}"
  end
end

inside of tasks/pm2.cap we define the PM2 deployment flow:

namespace :pm2 do
  task :install do
    execute :pm2, "start #{current_path}/config/#{fetch(:stage).split(':').last}.json --no-color"
    execute :pm2, "save --no-color"
  end
  task :start do
    execute :pm2, "start #{fetch :application} --no-color"
  end
  task :stop do
    execute :pm2, "stop #{fetch :application} --no-color"
  end
end

Now we (and Jenkins) are able to use Cap multi-config functionality:

$ ➜ cap service1:production deploy
$ ➜ cap service2:staging deploy

You can also call Capistrano tasks manually per deployment:

$ ➜ cap service1:staging pm2:stop
$ ➜ cap service1:staging pm2:start

These Days we ended up dockerizing our staging and production environments,

Coming soon on this blog: CI/CD with Docker/Kubernetes