Vagrant

Loading table of contents...

This article is co-posted on swtestacademy. You can more stuff about testing there.

Note: Vagrant VM user is vagrant, and its password is vagrant too.

What is Vagrant for

Vagrant is a virtual machine manager. It makes easy for creating, running and monitoring of VM's on your host machines. It's an open source and cross-platform (for MacOS, Windows and Linux) software. Right now you can you can manage VMs for VMWare, VirtualBox ve AWS (Amazon Web Services). This means you can easily manage different kinds of VMs from one Vagrant configuration which is beautiful!

But what real benefits we can get from it? What are the examples from real life?

First of all, this could heavily increase of your development quality: Do you remember how much time did you spend for just running projects on your local computer for the first time?

Or you would be much older after doing all of these work for all of your environments like staging, test and production. Lets say you need to setup and run your project on a new environment lets say: UAT. You will need to prepare all of the project's dependencies manually. That can react to a one week period depending your project's complexity and size.

But maybe the most beneficial part would be ability to reproduce production errors and bugs on your development environment. Because it is so easy to run VMs with exactly the same configuration on any computer with Vagrant. Goodbye "can't reproduce, runs on my machine" issues.

Prerequisites

Vagrant needs a VM manager as a provider. I already told vagrant can use multiple VM managers but for this article I chose VirtualBox which is an open and free provider presented by default on Vagrant. To download VirtualBox: click here.

Vagrant setup

  1. Download Vagrant from: http://www.vagrantup.com/downloads

Setup Box Path

Note: This is for MacOS and Linux only:

Vagrant uses this path to download VMs by default:

~/.vagrant.d/boxes

VMs could take up a lot of space so you might want to change its default path before installing any operating system. To change it just open .bash_profile on your favourite editor (you can use vi instead of nano)

1sudo nano ~/.bash_profile

Add this line:

export VAGRANT_HOME="/<TYPE PATH HERE>" New downloads will be under this directory.

Setup operating system

There are lots of Linux distributions provided from Hashi Corp's Atlas but I chose Ubuntu which I'm already familiar and it is a LTE version! https://atlas.hashicorp.com/ubuntu/boxes/trusty64

Setup as told by the documentation:

1vagrant init ubuntu/trusty64
2vagrant up

init command will create a file named VagrantFile on the current path. vagrant up command will use information on this file.

up command will download and install OS specified on to that VM.

You can see and examine many useful commands with vagrant --help

Connecting to the Box

To see the list of the boxes we type vagrant box list command.

1ubuntu/trusty64 (virtualbox, 20151020.0.0)

Now we need to use vagrant ssh, which will connect to the VM written in the VagrantFile.

1vagrant@vagrant-ubuntu-trusty-64:~$

My terminal connection is changed to the above which means I'm connected using ssh.

Installing Requirements

These requirements are totally for my node.js example application and will be changed by the project and development/running stack you use.

1sudo apt-get -y install git build-essential
2curl --silent --location https://deb.nodesource.com/setup_4.x | sudo bash -
3sudo apt-get install -y nodejs redis-server
4sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
5echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
6sudo apt-get update
7sudo apt-get install -y mongodb-org

Lets do the Redis configuration: (you can use vi instead of nano) Our application uses redis with this configuration:

1sudo vi /etc/redis/redis.conf

change the line with notify-keyspace-events to this:

1notify-keyspace-events "Kgs"

After that restart redis:

1sudo service redis-server restart

Now download and install the application:

1git clone https://github.com/dhalsim/chatcat.git
2cd chatcat
3git checkout dort
4npm install
5sudo npm install grunt-cli -g

We'll need to write all these commands to a file: provision.sh. Afterwards open and change the VagrantFile to this:

1 -*- mode: ruby -*-
2
3Vagrant.configure(2) do |config|
4  config.vm.box = "ubuntu/trusty64"
5  config.vm.network "forwarded_port", guest: 3000, host: 8080
6  config.vm.provision :shell, path: "provision.sh"
7end

Exit from ssh with exit command and then type:

1vagrant reload --provision

With this line Vagrant will close, reopen and make installments based on instruction you wrote on provision.sh file.

Vagrant synchronization

It might be hard for you to develop on Vagrant VMs especially with non-graphical terminals. If you're not used to terminal apps like Vim or Emacs and you want to use your IDE and tools on your host machine, you can do this by enabling Vagrant's synchronization mode. That could also be used for any data transfer.

I'll be explaining usage of synchronization between Mac OSX host machine and VirtualBox ubuntu guest machine. A different combination would need something else and related information can be found on vagrant documentation about synced folders.

Lets change the VagrantFile:

1Vagrant.configure(2) do |config|
2  config.vm.box = "ubuntu/trusty64"
3  config.vm.network "forwarded_port", guest: 3000, host: 8080
4  config.vm.provision :shell, path: "provision.sh"
5  config.vm.synced_folder "~/Projects/chatcat", "/home/vagrant/chatcat"
6end
  • We added config.vm.synced_folder command.
  • First parameter path, is on the host machine.
  • Second parameter path, is on the guest machine

We can check the new options by

1vagrant reload
2vagrant ssh

Now when we change a file using our favourite editor/IDE from our host machine within the specified path, it will automatically synchronized by given guest path.

Port Forwarding

Port forwarding means binding a port number used from guest machine to host machine's any port to be able to use from there.

1config.vm.network "forwarded_port", guest: 3000, host: 3000

Now VM's 3000 port could be used from our compter with 3000 port.

You can try the application running on guest, by entering this URL to your computer's browser: http://localhost:3000. Bon appetit :)

Private Network

You can also communicate with your VMs from a private network defined by you. You should change the configuration as follow:

1config.vm.network "private_network", ip: "192.168.1.20"

With this method, there is no need for port forwarding, instead localhost you can use this network's IP address directly and test from here: http://192.168.1.20:3000.

By defining an IP for the guest OS, you can even use your DNS which can be used for clustering.

For VirtualBox use these commands (to make sure VM is closed type vagrant halt):

1VBoxManage list vms  --> type VM name below
2VBoxManage modifyvm "YOUR_VM'S_NAME" --natdnshostresolver1 on

For local usages, you can set meaningful names on your computer's hosts file. For Mac OSX change that file using this command (you can use vi instead of nano):

1sudo vi /private/etc/hosts  

Add 192.168.1.20 nodeapp.com to the bottom of the file and save. After that we can reach to our node.js application from http://nodeapp.com:3000 from the host machine. Furthermore other VMs can reach to that if we made the same configuration on that VMs.

I'll continue with nodeapp.com below examples.

These changes will cause problems on facebook login and socket processes. Facebook application settings should be changed to use nodeapp.com instead of localhost. These changes will be on config/commons.js on socket_host variable and on test.json file on callbackURL variable. Things to be done:

  1. We need to change application settings from http://developers.facebook.com I suggest you to use Test Apps to create a new application and change Site URL to http://nodeapp.com:3000/. You need to change test.json with related configuration variables with new AppID and AppSecret given by facebook.
  2. On config/index.js there is a little issue that I didn't notice before. We'll change that to override common settings by the given one:

    1module.exports = require('src/lib/utils').extend(commons, environmentConfig);
    
  3. On test.json add this to override commons.json:

    1...
    2"socket_host": "http://nodeapp.com:3000",
    3...
    

Run Application as a Service

Until now, I explained what to be done to install and use our node.js application. Now I'll explain managing and monitoring it by using a tool named PM2 which is very popular in node.js community. We'll use it to restart our application on application *crash*es and I also show you how to add our application to the OS as a service of Ubuntu/Linux which automates running application on OS restarts.

Install PM2:

1sudo npm install -g pm2

Run this command to install PM2's command completion tool to help us write many complex commands:

1pm2 completion install

You can close and reopen your terminal window for completion to work or run this command:

1source ~/.bashrc

NOTE: PM2 has actually lots of handy features for us, but that would be a topic for another article so I'll pass for now. If you'd like you can search on your own from: http://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/

Before going throug service options, we need to make changes on grunt side. Until now our application ran on dev mode. I decided to use test mode for VM. We are still going to use grunt for running redis and mongo in the same instance but we don't use watch of grunt or nodemon, instead we'll use PM2 for that feautures.

Spoiler: You can get the code ready for VM by typing git checkout bes but you'd still need to do some of these tasks:

  1. copy dev.json content to a file called test.json.
  2. Type service --status-all to check if redis and mongo services are active.
  3. Run and test application with NODE_ENV=test pm2 start app.js.
  4. If it runs without an error, kill it with pm2 kill
  5. Add a new file called startup.sh which will provide starting, stoping etc. of the service. Have a look on variables like NAME, PM2, USER, APP_HOME and change as your needs. For instance check your user name with whoami (which will be vagrant) or get the path of PM2 executable with which pm2.

     1#!/bin/bash
     2 chkconfig: 2345 98 02
     3#
     4 description: our chatcat node app startup script
     5 processname: chatcat
     6#
     7## BEGIN INIT INFO
     8 Provides:
     9 Required-Start: $local_fs $remote_fs
    10 Required-Stop: $local_fs $remote_fs
    11 Should-Start: $network
    12 Should-Stop: $network
    13 Default-Start:        2 3 4 5
    14 Default-Stop:         0 1 6
    15 Short-Description: chatcat
    16 Description: chatcat node app is started
    17## END INIT INFO
    18
    19NAME=chatcat
    20PM2=/usr/bin/pm2
    21USER=vagrant
    22APP_HOME="/home/vagrant/chatcat"
    23
    24export NODE_ENV="test"
    25
    26get_user_shell() {
    27    local shell=$(getent passwd ${1:-`whoami`} | cut -d: -f7 | sed -e 's/[[:space:]]*$//')
    28
    29    if [[ $shell == *"/sbin/nologin" ]] || [[ $shell == "/bin/false" ]] || [[ -z "$shell" ]];
    30    then
    31      shell="/bin/bash"
    32    fi
    33
    34    echo "$shell"
    35}
    36
    37super() {
    38    local shell=$(get_user_shell $USER)
    39    su - $USER -s $shell -c "NODE_ENV=$NODE_ENV $*"
    40}
    41
    42start() {
    43    echo "Starting $NAME"
    44    super $PM2 start $APP_HOME/app.js
    45}
    46
    47stop() {
    48    super $PM2 dump
    49    super $PM2 delete all
    50    super $PM2 kill
    51}
    52
    53restart() {
    54    echo "Restarting $NAME"
    55    stop
    56    start
    57}
    58
    59reload() {
    60    echo "Reloading $NAME"
    61    super $PM2 reload all
    62}
    63
    64status() {
    65    echo "Status for $NAME:"
    66    super $PM2 list
    67    RETVAL=$?
    68}
    69
    70case "$1" in
    71    start)
    72        start
    73        ;;
    74    stop)
    75        stop
    76        ;;
    77    status)
    78        status
    79        ;;
    80    restart)
    81        restart
    82        ;;
    83    reload)
    84        reload
    85        ;;
    86    force-reload)
    87        reload
    88        ;;
    89    *)
    90        echo "Usage: {start|stop|status|restart|reload|force-reload}"
    91        exit 1
    92        ;;
    93esac
    94exit $RETVAL
    
  6. Copy the file to the path of services with sudo cp startup.sh /etc/init.d/nodeapp.

  7. Give execute right to the file with sudo chmod a+x /etc/init.d/nodeapp

  8. Run the service with sudo service nodeapp start. You can sudo /etc/init.d/nodeapp start without using service.

  9. Check if application started with pm2 list.

  10. Stop the service with sudo service nodeapp stop. Check if no application is running with pm2 list and there is no node.js process with ps aux | grep node.

  11. If everything is OK, type this command to register service to start after VM started sudo update-rc.d nodeapp defaults.

    Note: If you encounter any issue trying these, proceed from basic to advanced. For instance first check startup.sh is working or not then check the service.

  12. We also need to make changes to the provision.sh file:

 1...
 2git clone https://github.com/dhalsim/chatcat.git
 3cd chatcat
 4git checkout bes
 5npm install
 6sudo npm install grunt-cli -g
 7sudo npm install -g pm2
 8sudo cp startup.sh /etc/init.d/nodeapp
 9sudo chmod a+x /etc/init.d/nodeapp
10sudo update-rc.d nodeapp defaults

That's all for now. If you don't feel familiar to all these Linux commands and such, there are tons of free sources for Linux from novice to advanced. If you encounter a problem you can't solve, I'd like to help from the comments.

The next article will be about how to manage logging on different machines using Vagrant to help simulate it. Wait for it :)


Published: November 14 2015

blog comments powered by Disqus