Abstract
Recently, I needed to test a new K3s configuration. However, I didn’t want to do it on a production cluster. Therefore, I considered running it locally while ensuring I had at least two nodes to properly test my solution.
I figured out how to achieve this using Vagrant. While Vagrant seems to be somewhat forgotten and feels almost obsolete these days, I still find it quite useful.
Vagrant
What is Vagrant?
It is a Ruby-based tool that allows for the programmatic setup of
virtual machines, primarily for development, using a Vagrantfile
. It
supports several virtualization technologies, primarily
VirtualBox, which we will
use in this guide.
Solution
First, ensure you have
Vagrant
installed. You will also need a hypervisor. Today, we will focus on
VirtualBox, though
libvirt is also a viable
option. I will skip the installation steps for brevity since they vary
between operating systems and distributions. If you choose to use
libvirt
, adjust the provider
blocks in the examples accordingly.
Now, let’s create a Vagrantfile
. It starts with the following line:
Vagrant.configure("2") do |config|
This configures Vagrant to use API version 2.
Next, let’s define some constants. Feel free to modify them as needed:
k3s_channel = "v1.30"
agent_count = 3
master_ip = "192.168.56.3"
Master Node Configuration
Now, let’s define the master node. Here, we configure the hostname, network, and system resources. The memory and CPU values are the minimum recommended by the K3s documentation:
config.vm.define "master" do |master|
master.vm.hostname = "master"
master.vm.network "private_network", ip: master_ip
master.vm.provider "virtualbox" do |vb|
vb.name = "k3s-master"
vb.memory = "2048"
vb.cpus = 2
end
Vagrant allows us to execute scripts during machine provisioning. Let’s
take advantage of this functionality. One key detail is specifying the
network interface for flannel. This is important because Vagrant
provides eth0
and eth1
interfaces, but machines can only communicate
over the latter. The first is typically used for SSH access and internet
connectivity. I also chose wireguard-native
as the flannel backend
since that’s what I use in production, though it likely doesn’t make a
big difference.
master.vm.provision "shell", inline: <<-SHELL
sudo dnf update -y
sudo dnf install -y curl vim
# Install K3s (master node) with the specified channel
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=#{k3s_channel} sh -s - \
--write-kubeconfig-mode=0664 \
--write-kubeconfig-group=vagrant \
--node-external-ip=#{master_ip} \
--flannel-backend=wireguard-native \
--flannel-iface=eth1
# Get K3s token and save it for joining the agent
sudo cat /var/lib/rancher/k3s/server/node-token > /vagrant/k3s_token
SHELL
Agent Nodes Configuration
Agent nodes follow a similar setup, but we wrap the configuration inside a loop to create the desired number of machines dynamically.
(1..agent_count).each do |i|
agent_ip = "192.168.56.#{i + 3}"
config.vm.define "agent#{i}" do |agent|
agent.vm.hostname = "agent#{i}"
agent.vm.network "private_network", ip: agent_ip
agent.vm.provider "virtualbox" do |vb|
vb.name = "k3s-agent#{i}"
vb.memory = "2048"
vb.cpus = 2
end
agent.vm.provision "shell", inline: <<-SHELL
sudo dnf update -y
sudo dnf install -y curl
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=#{k3s_channel} K3S_URL=https://#{master_ip}:6443 K3S_TOKEN=$(cat /vagrant/k3s_token) sh -
SHELL
end
end
Full Vagrantfile
Here is the complete Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.box = "almalinux/9"
k3s_channel = "v1.30"
agent_count = 3
master_ip = "192.168.56.3"
config.vm.define "master" do |master|
master.vm.hostname = "master"
master.vm.network "private_network", ip: master_ip
master.vm.provider "virtualbox" do |vb|
vb.name = "k3s-master"
vb.memory = "2048"
vb.cpus = 2
end
master.vm.provision "shell", inline: <<-SHELL
sudo dnf update -y
sudo dnf install -y curl vim
# Install K3s (master node) with the specified channel
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=#{k3s_channel} sh -s - \
--write-kubeconfig-mode=0664 \
--write-kubeconfig-group=vagrant \
--node-external-ip=#{master_ip} \
--flannel-backend=wireguard-native \
--flannel-iface=eth1
# Get K3s token and save it for joining the agent
sudo cat /var/lib/rancher/k3s/server/node-token > /vagrant/k3s_token
SHELL
end
(1..agent_count).each do |i|
agent_ip = "192.168.56.#{i + 3}"
config.vm.define "agent#{i}" do |agent|
agent.vm.hostname = "agent#{i}"
agent.vm.network "private_network", ip: agent_ip
agent.vm.provider "virtualbox" do |vb|
vb.name = "k3s-agent#{i}"
vb.memory = "2048"
vb.cpus = 2
end
agent.vm.provision "shell", inline: <<-SHELL
sudo dnf update -y
sudo dnf install -y curl
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=#{k3s_channel} K3S_URL=https://#{master_ip}:6443 K3S_TOKEN=$(cat /vagrant/k3s_token) sh -
SHELL
end
end
end
Save this file in your chosen directory and run:
vagrant up
Wait for the process to complete. It may take some time as it runs sequentially.
After setup, log into the master node:
vagrant ssh master
Then, verify that all nodes are connected:
kubectl get nodes
You should see output similar to:
NAME STATUS ROLES AGE VERSION
agent1 Ready <none> 5m4s v1.30.10+k3s1
agent2 Ready <none> 34s v1.30.10+k3s1
master Ready control-plane,master 9m29s v1.30.10+k3s1
Congratulations, now lets go and break something!
Conclusion
Despite its aging ecosystem and occasional plugin issues, Vagrant remains a powerful tool for quickly provisioning development environments on demand. Its ability to create multiple machines makes it an excellent choice for testing network configurations, clusters, and other distributed setups.