Vagrant Multimode
After encountering the networking bug from my last post, I also discovered Vagrant’s multimode style to writing a Vagrantfile in this post.
This style basically allows describing multiple virtual machines within a single Vagrantfile. Isn’t that convenient??
Requirements
If you would like to follow along, these are the system requirements:
- Install Vagrant
- Install Virtualbox (or another hypervisor provider)
Offline
If you are working offline or with a limited internet connection (like tethering through a phone), preload a Linux image, such as Ubuntu Xenial. The easiest way to do this after installing Vagrant and Virtualbox is:
- Create a Vagrantfile
- Run
vagrant up
The contents of the Vagrantfile to preload Ubuntu Xenial is:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
end
After running vagrant up
, vagrant will download and save the box
image locally, Ubuntu Xenial 64bit in this case.
Benefits of Multimode
The way I setup multiple virtual machines in my previous article was to have a separate Vagrantfile in separate folders.
While this is easy to understand, there’s a lot of duplication between each of the file.
-
If I want to update all the virtual machine setup, I would have to update each of the files.
-
For every machine I want to create, I would create another folder with the same Vagrantfile.
In general, these would be managable for a handful of machines, but two handfuls become tedious… and more than that becomes a real pain. When I was working with just four for my article, it was getting pretty painful, I couldn’t imagine ten!
With multimode vagrant, it basically sets up each virtual machine programmatically. I only have one Vagrantfile and I can specify as multipley virtual machines as I want without additional folders or files. Vagrant manages this under the hood, whether I setup two, ten, or even 100 virtual machines.
This is an example of specifying ten virtual machines:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntux/xenial64"
(1..10).each do |worker_count|
config.vm.define "worker#{worker_count}" do |subconfig|
subconfig.vm.box = "ubuntu/xenial64"
subconfig.vm.hostname = "worker#{worker_count}"
[TODO](can there be a provision here?)
end
end
end
This method is scalable and easier to manage.
Listing and Accessing
To see all the machines, type: $ vagrant status
to list machine
names and states.
$ vagrant status
Current machine states:
worker1 running (virtualbox)
worker2 running (virtualbox)
worker3 running (virtualbox)
worker4 running (virtualbox)
worker5 running (virtualbox)
worker6 running (virtualbox)
worker7 running (virtualbox)
worker8 running (virtualbox)
worker9 running (virtualbox)
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
Isn’t that beautiful??
To access any worker machine, specify its name after the command. For example, to ssh into the third worker, this would be the command:
$ vagrant ssh worker3
Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-122-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
vagrant@worker3:~$ hostname
worker3
Different Machine Types
With multimode, we can bring up multiple machines easily, but configuring specific machines for different roles, like a balance loader, database server, database fail over, etc. can become tedious if we have to manually connect to each one.
Vagrant provides the define method which basically lets you define the configuration for different machine roles.
Here is an example of specifying the IP address and hostname of a “manager” machine with ten additional machines:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntux/xenial64"
config.vm.define "manager" do |subconfig|
subconfig.vm.box = "ubuntu/xenial64"
subconfig.vm.hostname = "manager"
subconfig.vm.network :private_network
# add any manager specific configuration here
end
(1..10).each do |worker_count|
config.vm.define "worker#{worker_count}" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.hostname = "worker#{worker_count}"
subconfig.vm.network :private_network
# add any worker configurations here
end
end
Now when we list the machines, this is the output:
$ vagrant status
Current machine states:
manager running (virtualbox)
worker1 running (virtualbox)
worker2 running (virtualbox)
worker3 running (virtualbox)
worker4 running (virtualbox)
worker5 running (virtualbox)
worker6 running (virtualbox)
worker7 running (virtualbox)
worker8 running (virtualbox)
worker9 running (virtualbox)
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
and the manager machine can be accessed by its label:
$ vagrant ssh manager
Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-122-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
vagrant@manager:~$ hostname
manager
Tips
Useful tips I have found when working with mutlimode Vagrantfile:
using global variables and the shared /vagrant
folder.
Global Variables
When configuring multiple different virtual machines, using global variables to keep values consistent across machines. Storing values in a single place keep Vagrantfile clean.
Below is an example of using a common BOX_IMAGE
variable to store
the box image used across different virtual machines and the number of
workers to create.
BOX_IMAGE = "ubuntu/xenial64"
WORKER_COUNT = 10
Vagrant.configure("2") do |config|
config.vm.box = BOX_IMAGE
config.vm.define "manager" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.hostname = "manager"
subconfig.vm.network :private_network
end
(1..WORKER_COUNT).each do |worker_count|
config.vm.define "worker#{worker_count}" do |subconfig|
subconfig.vm.box = BOX_IMAGE
subconfig.vm.hostname = "worker#{worker_count}"
subconfig.vm.network :private_network
end
end
Shared Folder
All vagrant virtual machines have a folder shared with the host that
is accessible by the /vagrant
directory.
Multimode virtual machines all share the same directory, so the
manager virtual machine’s /vagrant
folder will have the same
contents as worker5’s /vagrant
folder.
This can be useful to share data between virtual machines, especially in provisioning.
Conclusion
With Vagrant mutlimode configuration, it is even easier to setup multiple virtual machines in a single file. Using a single command, one can make multiple virtual machines with differing configurations.
I see this tool to be the perfect way to experiment with site-level architectures: load balancers, databases with followers, etc.