Building Virtual Machines for Home Lab
Learning how to set up a home lab where you can practice System Admin, Cloud, and DevOps engineering exercises is essential to your growth as a software engineer. Although, you may use the platforms of Cloud service providers for practice; for example,
- Amazon Web Service (AWS) gives you a ‘Free Tier’ account where you can get hands-on within limited resources.
- Google Cloud Platform (GCP) gives you a $300 free credit on sign-up to use their services for 30 days.
- On sign-up, you receive a $200 free credit to use the services of Microsoft Azure for 30 days.
- Using the promo code of any of Linode’s YouTube influencers, you could get up to a $100 free credit to use the services of Linode when you signup.
As a learner, creating an account with each of these cloud providers would help increase your exposure and practical knowledge of these platforms. However, setting up a home lab on your local machine will enable you
- greatly minimize cost especially since you are not yet earning from your practice,
- practice more often without the fear of accruing the cost of using cloud services.
That said, let us dive into the main purpose of this article.
PREREQUISITES
To follow this tutorial, you would need
- a Personal Computer.
- VirtualBox: you can install it here.
- Install Vagrant by Hashicorp according to your Operating System here.
- a stable internet connection
- background knowledge of Vagrant, Linux OS commands, and working with the terminal.
These Vagrantfiles are customized to create multiple Virtual Machines consisting of multiple host nodes and one control node. Set the number of nodes you want by changing the value of the UBUNTU_NODE variable on line 4 of the Vagrantfile below:
UBUNTU_NODES = 3
Take note of the following:
- VirtualBox is the virtual machine provider used here, so ensure you have it installed already.
- The resource GitHub repository of this article offers two identical Vagrantfiles for spinning up ubuntu/focal64, and centos/7 virtual machines. You may change it to the Vagrant box of any Linux OS of your choice.
- The network configuration is set in such a way that an ssh connection between the centos/7 and ubuntu/focal64 virtual machines will be allowed.
Writing our Vagrantfile
Vagrantfiles are usually written in Ruby. From virtual box provider names to network configurations, a Vagrantfile specifies every single parameter needed to create a virtual machine. By simply running “vagrant init vagrant-box-name", Vagrant automatically downloads a Vagrantfile for the OS of the specified Vagrant box in your initialization command. So when you list the content of your present directory, you will find a Vagrantfile sitting right there. You may visit Vagrant’s Cloud to find the name of the box for your preferred OS. Then when you run “vagrant up" in the same directory where we have our Vagrantfile, Vagrant begins to build our VMs according to the specification on our Vagrantfile.
In this article, we will create our ownVagrantfiles to suit our needs.
First, we declare the name of our Vagrant box and the number of nodes we wish to spin up as global variables in our Vagrantfile.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Next, we begin our configuration settings. We set “config.ssh.insert_key” to false to prevent Vagrant from automatically creating an SSH key pair for our VMs. Learn more.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
Then, we set a loop to create the number of nodes we want.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
(1..UBUNTU_NODES).each do |i|
config.vm.define "ubuntu-node-#{i}" do |node|
Using our global variable, we specify the Vagrant box of our VM.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
(1..UBUNTU_NODES).each do |i|
config.vm.define "ubuntu-node-#{i}" do |node|
node.vm.box = VAGRANT_BOX_NAME
Now we set our network parameters, based on the following:
- we want it private (only available on our PC) — hence, private_network,
- we should be able to view any demo website we deploy on our VMs when we visit its IP address on the web browser of our PC; thus we do not want it internal,
- we want the IP address of each VM to remain constant — therefore, we avoid Dynamic Host Configuration Protocol (DHCP), and instead set our IP addresses ourselves.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
(1..UBUNTU_NODES).each do |i|
config.vm.define "ubuntu-node-#{i}" do |node|
node.vm.box = VAGRANT_BOX_NAME
# setting the network parameters
node.vm.network "private_network", ip: "192.168.43.#{i + 20}"
Moving forward, we name our machines and specify the name of our virtual box provider and the memory and CPU capacity of our VMs.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
(1..UBUNTU_NODES).each do |i|
config.vm.define "ubuntu-node-#{i}" do |node|
node.vm.box = VAGRANT_BOX_NAME
# setting the network parameters
node.vm.network "private_network", ip: "192.168.43.#{i + 20}"
# naming the nodes
node.vm.hostname = "ubuntu-node-#{i}"
# stating provider, memory and CPU capacity
node.vm.provider :virtualbox do |vb|
vb.name = "ubuntu-node-#{i}"
vb.memory = 1024
vb.cpus = 1
end
end
end
This is for our nodes only. Following the same pattern, we add a few more lines to create our control VM.
VAGRANT_BOX_NAME = "ubuntu/focal64"
# Creating two nodes. Change this according to the number of nodes you want
UBUNTU_NODES = 3
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
(1..UBUNTU_NODES).each do |i|
config.vm.define "ubuntu-node-#{i}" do |node|
node.vm.box = VAGRANT_BOX_NAME
# setting the network parameters
node.vm.network "private_network", ip: "192.168.43.#{i + 20}"
# naming the nodes
node.vm.hostname = "ubuntu-node-#{i}"
# stating provider, memory and CPU capacity
node.vm.provider :virtualbox do |vb|
vb.name = "ubuntu-node-#{i}"
vb.memory = 1024
vb.cpus = 1
end
end
end
config.vm.define "ubuntu-control" do |control|
control.vm.box = VAGRANT_BOX_NAME
# setting the network parameters
control.vm.network "private_network", ip: "192.168.43.11"
# naming the control virtual machine
control.vm.hostname = "ubuntu-control"
# stating provider, memory and CPU capacity
control.vm.provider :virtualbox do |vb|
vb.name = "ubuntu-control"
vb.memory = 2048
vb.cpus = 1
end
end
end
Starting your VMs
Once you have your Vagrantfile copied to a directory designated for your setup— you can clone the source code repository where our Vagrantfiles are located by executing the following commands:
mkdir vagrantfiles_for_homelab
cd vagrantfiles_for_homelab
git clone https://github.com/ozirichigozie/Vagrantfiles.git .
Then run the following commands consecutively
cd ubuntu-focal64
vagrant init
vagrant up
vagrant ssh ubuntu-control
The ubuntu-control node is used as an example in the code block above. Since we already have our desired Vagrantfile in our directory, we execute the Vagrant initialization command without the box name argument. This runs successfully, and when we run “vagrant up”, Vagrant uses our Vagrantfile to build our VM.
SSHD Configuration
To enable ssh connection, go to the /etc/ssh/sshd_config files of each node you wish to connect to and edit accordingly. Here I am going to be using the Vim text editor. You may use Nano if you want.
sudo vim /etc/ssh/sshd_config
and change the following arguments to positive:
- Permit Root Login
- Password Authentication
- Public Key Authentication
...
#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
...
PubKeyAuthentication yes
...
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication yes
#PermitEmptyPasswords no
The lines beginning with a ‘#’ are comments and will not take effect in configuration. So ensure you delete the ‘#’ character if it is found in front of any of the lines we are trying to edit. Do not alter the rest of this file to avoid running into errors. On Vim, Save and exit with ‘:wq’, or ‘CTRL+O Enter’ to save, and ‘CTRL+X’ to exit on Nano.
To integrate these changes, run
sudo systemctl restart sshd
Note: this setup is for home labs used for practice (where security is not a concern).
Generating SSH Keys
Generate ssh keys on your control nodes using the `ssh-keygen` command. We use the -t flag to specify a particular key type. Usually, I prefer the ed25519 key type because it is short and neat. The RSA key type will be assigned by default if you don’t specify. You may also add a comment with the -C flag. This may be for description but is optional. Therefore, we have a command line like this:
ssh-keygen -t ed25519 -C any-optional-descriptive-comment
This will generate a pair of private and public keys located by default at /home/vagrant/.ssh/id_ed25519 and /home/vagrant/id_ed25519.pub respectively. The default RSA key type also follows this same pattern. (The default user of Vagrant virtual machines is vagrant).
Copy SSH Keys to Host Nodes
Then you must copy your public SSH key to the target VMs’ authorized keys file before you can ssh into your VM successfully. Execute the command below:
ssh-copy-id -i ~/.ssh/id_ed25519.pub vagrant@ip.address.of.vm
You will be prompted to enter a password if this command runs successfully. The default password for Vagrant’s virtual machines is ‘vagrant’. Enter this password, and connect to your target host node using the command below
ssh vagrant@ip.address.of.vm
# OR simply
ssh ip.address.of.vm
Your IP Addresses
Following the construct of our Vagrantfiles, your IP addresses will be:
- ubuntu-node-1: 192.168.43.21
- ubuntu-node-2: 192.168.43.22
- ubuntu-control: 192.168.43.11
and for the centos/7 servers,
- centos-node-1: 192.168.43.61
- centos-node-2: 192.168.43.62
- centos-control: 192.168.43.51
To confirm your IP address after connecting to your VM, run
ip addr show
# OR simply run
ip a
You may include `sudo` in your commands where ever necessary.
Moving forward, to get better at working with these VMs, you should learn more about Linux, Vagrant commands, and VirtualBox networking.
Conclusion
Now that you have successfully set up your home lab, you can practice as much as you want with no fear of accruing cloud service charges. However, there are certain labs you may not be able to carry out on your local VMs, but if you are just getting started as a beginner, this should do.
For any questions or observations, please chat with me on WhatsApp.
You can read my article to get a simplified Bash script for installing Ansible on your virtual machines.
I also have a series of tutorials on Ansible which you can follow to learn more about how to use this powerful tool.
See you at the top!