How to Create and Automate Virtual Machines using Vagrant
Tags: devops, linux, beginners, cloud
Published: 2022-10-17 10:01:42

Vagrant is an automation tool that helps in easily creating virtual machines and automating them while saving time and increasing productivity. 

>Based on https://www.vagrantup.com/intro, Vagrant is a tool for building and managing virtual machine environments in a single workflow. With an easy-to-use workflow and focus on automation, Vagrant lowers development environment setup time, increases production parity, and makes the "works on my machine" excuse a relic of the past.

Have you ever used VirtualBox or VMWare before? Vagrant helps us automate and quickly build the things you'd have used VirtualBox for; sounds easy, I know, right? 

We'd be learning about Vagrant by using it with Ansible; what is Ansible?

>Ansible is an IT automation tool. It can configure systems, deploy software, and orchestrate more advanced IT tasks such as continuous deployments or zero downtime rolling updates.

Ever thought of writing a script to automate something for you using Python or Golang? Ansible helps you do that without writing actual codes; YAML is the language ansible understands. 

Even though I've tried to make this article as beginner-level as it could be, there are still some requirements;

- YAML (basics)
- Docker (basics)
- Linux
- Basics of VirtualBox

So what's the point of this article? What do we want to use Ansible and Vagrant for?

We would use Vagrant to create three remote Virtual Machines using Virtual Box and automate tasks using Ansible on the VMs. 

The three VMs are;
1. **The Controller VM:** This controls other targets VMs and automates tasks using Ansible, so, Ansible will be installed on the Controller VM.
2. **Target 1 and Target 2 VMs**: These are the target VMs; we'd use the Controller VM to automate tasks in these target VMS.

Now, let's dive right in and install Vagrant. 

## Install Vagrant on your PC
Visit https://www.vagrantup.com/downloads and download; you can use the executable file for Windows OR use brew for Mac; it's available for Mac, Linux and Windows.

After downloading and installing it, to confirm, run;

```
vagrant --version
```

Also, install VirtualBox, https://www.virtualbox.org/wiki/Downloads.

## Using Vagrant to create the VMs (Controller VMs and Targets)
As a `Dockerfile` is to Docker, a `Vagrantfile` is for Vagrant; inside a Vagrantfile is where we configure the VM we want to use, so open your text editor and create a Vagrantfile

**Vagrantfile**
```
NUM_NODES = 2
NUM_CONTROLLER_NODE = 1
IP_NTW = "192.168.56."
CONTROLLER_IP_START = 2
NODE_IP_START = 3
```

We created a few variables quick explanation about them.
- **NUM_NODES**: This is the number of nodes(target VMs) we want to create.
- **NUM_CONTROLLER_NODE**: Number of Controller nodes.
- **IP_NTW**: This is the network range we want the VMs to belong to, so our network will have the format of `192.168.56.{any_number}`
- **CONTROLLER_IP_START**: We specified the start number where the controller would be assigned to in the network, so our controller will start from `192.168.56.2`, and the **NODE_IP_START** will be starting from 3, that's `192.168.56.3`. 

These will all make sense in some moments. Let's continue for now. We'd be using an Ubuntu image, it's called Image in Docker, but it's called a Box in Vagrant; you can search for boxes at https://app.vagrantup.com/boxes/search.

**Vagrantfile**
```
NUM_NODES = 2
NUM_CONTROLLER_NODE = 1
IP_NTW = "192.168.56."
CONTROLLER_IP_START = 2
NODE_IP_START = 3

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/trusty64"

end
```
The 2 in vagrant.configure("2") specifies the configuration version, don't change it, we also used the `ubuntu/trusty64` box, the `config` is a local variable inside the vagrant.configure("2") directive, we use it to configure the box, like `config.vm.box = "ubuntu/trusty64"`, this specifies the box we used. 

## Configuration for the Target VMs

**Vagrantfile**
```
NUM_NODES = 2
NUM_CONTROLLER_NODE = 1
IP_NTW = "192.168.56."
CONTROLLER_IP_START = 2
NODE_IP_START = 3

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/trusty64"

    (1..NUM_NODES).each do |i|
        config.vm.define "target0#{i}" do |node|
            node.vm.provider "virtualbox" do |vb|
                vb.name = "target0#{i}"
                vb.memory = 2048
                vb.cpus = 2
            end

            node.vm.hostname = "target0#{i}"
            node.vm.network "private_network", ip: IP_NTW + "#{NODE_IP_START + i}"
            node.vm.network "forwarded_port", guest: 22, host: "#{2720 + i}"
        end
end
```
We created the target VMs, and we already created a Variable called `NUM_NODES`, so we created a foreach loop to loop from 1..NUM_NODES, and for each loop instance, we define the config for each target. Our counter variable is **i**.

Then we defined the name of the box **config.vm.define "target0#{i}" do |node|**, in it, we set a provider of Virtualbox, Vagrant will use Virtualbox to set up the box, also expect our configs to be for Virtualbox, we then gave the box a name, set the memory to use, also the number of CPUs.

`node.vm.hostname = "target0#{i}"` set the hostname, so the first target will be **target01** and the second **target02**.

Next, we defined the networks.

A quick network refresher.
- By default, all boxes in Vagrant use Adapter 1, which is the NAT; using NAT, our VMs can get their internet connections from the host(YOUR PC), but your VMs can't communicate with each other. 
- In Vagrant, a **private network** is the same as a **Host-Only network**; a Host-Only network allows VMs on the same network to communicate with each other but can't communicate outside the network.

So combining NAT and a Host-Only network means our VMs can communicate with each other (since they're on the same network); also, they can get Internet Connectivity from the host using the NAT.

Back to Vagrant, This line creates a private network(Host Only) `node.vm.network "private_network", ip: IP_NTW + "#{NODE_IP_START + i}"`, also set the IP, it uses the `IP_NTW` and adds the `NODE_IP_START` variable and the current loop, poor little `i`, we could have used the DHCP server to automatically assign IP address to our VMs, but we won't be covering that.

`node.vm.network "forwarded_port", guest: 22, host: "#{2720 + i}"` this part defines a port forwarding, which is used to handle SSH connection; SSH connection uses the port 22 on all systems, so whenever the host receives a request on port `2720`, it's routed to the VM on port 22(SSH), that's basically what port forwarding does, it forwards ports on the host to the VM in this case, using this you can SSH from your PC into the VMs.

## Configuration for the Controller VM(s)
### Vagrantfile
```
NUM_NODES = 2
NUM_CONTROLLER_NODE = 1
IP_NTW = "192.168.56."
CONTROLLER_IP_START = 2
NODE_IP_START = 3

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/trusty64"

    (1..NUM_NODES).each do |i|
        config.vm.define "target0#{i}" do |node|
            node.vm.provider "virtualbox" do |vb|
                vb.name = "target0#{i}"
                vb.memory = 2048
                vb.cpus = 2
            end

            node.vm.hostname = "target0#{i}"
            node.vm.network "private_network", ip: IP_NTW + "#{NODE_IP_START + i}"
            node.vm.network "forwarded_port", guest: 22, host: "#{2720 + i}"
        end
    end

    i = 0
    
    (1..NUM_CONTROLLER_NODE).each do [i] 
        config.vm.define "nodecontroller" do |node|
            node.vm.provider "virtualbox" do |vb|
                vb.name = "nodecontroller"
                vb.memory = 2048
                vb.cpus = 2
            end
    
            node.vm.hostname = "nodecontroller"
            node.vm.network "private_network", ip: IP_NTW + "#{CONTROLLER_IP_START + i}"
            node.vm.network "forwarded_port", guest: 22, host: "#{2710 + i}"
        end
    end
end
```

Starting from the comment `Configuration for Controller VM(s)` down is where we did the config of the controller VM; there is nothing to explain here; both configs are similar. Your `Vagrantfile` should be the same as this. 


![Vagrant Ubuntu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5e524bv5sszl384ahebo.png)

Now let's run the Vagrantfile and set up the virtual machines, change the directory into where the Vagrantfile is then run.

```
vagrant up
```

This would take a few minutes, so be patient; when it finishes, you can run `vagrant status` to see the Virtual machines that were created.


![Vagrant status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vwgm2eh21ofqr9le2lb.png) 

You can also open your Virtualbox, and you would see the virtual machines there too.


![Virtualbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfh6npan2itxn4qc315d.png)

## Working with the Virtual Machines
Let's SSH into the `nodecontroller`.

```
vagrant ssh nodecontroller
```
If it requests a password, use `vagrant` as the password for all the VMS.  

### Get the IP address of `nodecontroller.`
```
ifconfig
```

You should see (`192.168.56.2`)

![ifconfig](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7yirae9ja1cnfaxtu8k.png)

Now exit `nodecontroller`. 

```
exit
```

### Let's get the IP addresses of target01 and target02
```
vagrant ssh target01
```
Run `ifconfig` and exit, then repeat the same process for `target02`. 

My IP addresses are; 
- target01: 192.168.56.4
- target02: 192.168.56.5
- nodecontroller: 192.168.56.2

Yours could be different; you can notice they all end in `56.` this means they're on the same network and can communicate. 

### SSH to target02 from target01
First, SSH into target01.

```
ssh 192.168.56.5
```
Try SSH all vice and versa, from target01-target02, target02-target01, nodecontroller-target01, nodecontroller-target02. 

Remember I said earlier that since we used a Host-Only network, we can communicate with other VMs on the same networks; let's try to SSH into `target01` from `nodecontroller`.

You can also test internet connectivity on all VMs; let's try pinging google.com.

```
ping google.com
```

![ping google](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcd0z0u612k01d9er8m9.png)

Now everything is set!

However, installing Ansible and Automating Tasks would come in the Part 2 series of this article. This one is long enough, felt like I've been typing forever, lol.

I know I said a lot in this article, I just wanted everyone to be able to follow along, including beginners, so I tried breaking everything down; hopefully, I didn't bore you.

If you got this far, Add a Reaction and Follow me so you will get notified when I post part 2 of this article. 

Thanks for reading. 😀