Vagrant
This article is co-posted on swtestacademy. You can more stuff about testing there.
Note: Vagrant VM user is
vagrant
, and its password isvagrant
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
- 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:
- 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.
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);
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:
- copy
dev.json
content to a file calledtest.json
. - Type
service --status-all
to check if redis and mongo services are active. - Run and test application with
NODE_ENV=test pm2 start app.js
. - If it runs without an error, kill it with
pm2 kill
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 withwhoami
(which will be vagrant) or get the path of PM2 executable withwhich 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
Copy the file to the path of services with
sudo cp startup.sh /etc/init.d/nodeapp
.Give execute right to the file with
sudo chmod a+x /etc/init.d/nodeapp
Run the service with
sudo service nodeapp start
. You cansudo /etc/init.d/nodeapp start
without using service.Check if application started with
pm2 list
.Stop the service with
sudo service nodeapp stop
. Check if no application is running withpm2 list
and there is no node.js process withps aux | grep node
.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.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 :)