Why use Vagrant?
Vagrant 1 …command line utility …manage life cycle of virtual machines:
- …widely used by software developers and system administrators
- …configuration of a reproducible environments for development
- …many software source repositories include a
Vagrantfile
- …shared test environment for collaboration between developers
- …Vagrant configuration part of project version control
- …many software source repositories include a
- …supports various platforms, including Linux & Windows
- …isolation of the test- & development-environments
My collection of Vagrant test environments is available on GitHub:
- https://github.com/vpenso/vagrant-playground
- Examples include…
- …container tools like Docker & Podman
- …configuration management with Ansible
- …monitoring with Prometheus & Grafana
Providers
Providers interface with different virtual machine monitors (aka. hypervisors).
- On Linux LibVirt and Virtualbox are the most popular.
- It is possible to use LibVirt and Virtualbox at the same time.
- Virtualbox is used by default, Libvirt requires and additional plugin [^3].
The following two section illustrate how to install and configure both.
Libvirt
Vagrant Libivirt provider 2 installation:
# Debian/Ubuntu...
sudo apt install -y libvirt-daemon-system vagrant vagrant-libvirt vagrant-mutate
# Fedora...
sudo dnf install -y @virtualization @vagrant
Enterprise Linux packages 3 are available from HashiCorp:
# make sure to use a recent version
dnf install -y https://releases.hashicorp.com/vagrant/2.4.1/vagrant-2.4.1-1.x86_64.rpm
# install Libvirt support via plugin
dnf install -y make gcc rpm-build ruby ruby-devel ruby-devel zlib-devel libvirt-devel
gem install nokogiri
vagrant plugin install vagrant-libvirt
Configuration…
# add yourself to the `libvirt` group
sudo gpasswd -a $USER libvirt
# …or…
usermod -aG libvirt $user
# re-login or…
newgrp libvirt
# configure the libvirt service to run with your user ID (here illustrated with ID jdow)
>>> sudo grep -e '^user' -e '^group' /etc/libvirt/qemu.conf
user = "jdow"
group = "jdow"
# ...start services ...set `qemu:///system` environment variable
sudo systemctl enable --now libvirtd virtnetworkd
Configure the default provider…
- …use the command option
--provider=libvirt
… - …set the
VAGRANT_DEFAULT_PROVIDER
environment variable
# Vagrantfile
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
Vagrant.configure(2) do |config|
#....
end
# shell environment variable
export VAGRANT_DEFAULT_PROVIDER=libvirt
Force the systems session in the definition file…
Vagrant.configure("2") do |config|
.vm.provider :libvirt do |libvirt|
config# Use QEMU system instead of session connection
.qemu_use_session = false
libvirtend
end
Enable the system session globally by environment variable…
export LIBVIRT_DEFAULT_URI=qemu:///system
Connect to a Libvirt virtual network 4 5:
# ...
.vm.provider :libvirt do |libvirt|
config# Use QEMU session instead of system connection
.qemu_use_session = true
libvirt# Management network device, default is below
.management_network_device = 'virbr0'
libvirtend
#...
VirtualBox
VirtualBox 6 on Fedora…
sudo tee /etc/yum.repos.d/virtualbox.repo <<'EOF'
[virtualbox]
name=Fedora $releasever - $basearch - VirtualBox
baseurl=http://download.virtualbox.org/virtualbox/rpm/fedora/$releasever/$basearch
#baseurl=http://download.virtualbox.org/virtualbox/rpm/fedora/36/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc
EOF
# alternatively it is available from RPM Fusion as well...
sudo dnf install \
$(rpm -E %fedora).noarch.rpm \
https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-
# ...install dependencies
sudo dnf install -y \
\
@development-tools \
kernel-headers \
kernel-devel
dkms# VritualBox 7.x not supported yet...
sudo dnf install -y VirtualBox-6.1 @vagrant
sudo usermod -a -G vboxusers ${USER} && newgrp vboxusers
Boxes
- Virtual machine image templates
- Dedicated box storage for each user
- Vagrant boxes are all provider-specific
- A box must be installed for each provider
- Can share the same name as long as the providers differ
List commonly used boxes:
almalinux
– https://app.vagrantup.com/almalinuxcentos
– https://app.vagrantup.com/centosdebian
– https://app.vagrantup.com/debianfedora
– https://app.vagrantup.com/fedora or https://alt.fedoraproject.org/cloud/rockylinux
– https://app.vagrantup.com/rockylinuxubuntu
– https://app.vagrantup.com/ubuntu- …check Roboxes 7 for
generic/*
boxes - …use the search function 8 on the Vagrant Cloud
init
Life cycle sub-Commands…
init
creates a newVagrantfile
in the working directory- Modify the
Vagrantfile
to configure the virtual machine instance… - …and
validate
if the syntax is correct up
starts the virtual machine instance configured inVagrantfile
status
list active virtual machine instanceshalt
stops a virtual machine instance keeping the environmentdestroy
remove a virtual including its environment
pushd $(mktemp -d /tmp/$USER-vagrant-XXXXXX)
# create a ./Vagrantfile for a specified virtual machine image
vagrant init --minimal rockylinux/9
vagrant up && vagrant ssh
# ... work with the VM
vagrant destroy
popd && rm -rf /tmp/$USER-vagrant*
box
Manage virtual machine images on localhost…
- …before a virtual machine instance can be created with
init
andup
box add
downloads a virtual machine images from a remote repositorybox list
lists locally available virtual machine imagesbox remove
deletes a virtual machine image from localhost
Examples:
# download a specific version
vagrant box add centos/stream8 --box-version 20210210.0
# download for a specific provider
vagrant box add centos/7 --provider=libvirt
# convert a VirtualBox image
vagrant box add ubuntu/focal64
vagrant mutate ubuntu/focal64 libvirt
Using a URL to a box files …must specify --name
option
vagrant box add --name rockylinux/8 \
http://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-Vagrant-Libvirt.latest.x86_64.box
package
package
a currently running VM instance
- …supported by the
vagrant-libvirt
provider …using Vagrantpackage
- …ensure that you have set the
config.ssh.insert_key = false
in the originalVagrantfile
vagrant package --output $name
Vagrantfile
- …configure provisioning of a virtual machines
- …Ruby syntax …use one file per project …commit to version control
pushd $(mktemp -d /tmp/$USER-vagrant-XXXXXX)
# prepare a virtual machine for testing
cat > Vagrantfile <<EOF
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "rockylinux/9"
config.vm.provider :libvirt do |libvirt|
libvirt.memory = 1024
end
config.vm.synced_folder ".", "/vagrant", disabled: true
end
EOF
vagrant up && vagrant ssh
# ... work with the VM
vagrant destroy
popd && rm -rf /tmp/$USER-vagrant*
Hardware
Request specific memory and CPU resources…
Vagrantfile
.vm.provider :libvirt do |libvirt|
config.memory = 1024
libvirt.cpus = 4
libvirt.cpuset = '1-4,^3,6'
libvirt.cputopology :sockets => '2', :cores => '2', :threads => '1'
libvirt.cpuaffinitiy 0 => '0-4,^3', 1 => '5', 2 => '6,7'
libvirtend
Vagrantfile
.vm.provider "virtualbox" do |virtualbox|
config.memory = 1024
virtualbox.cpus = 2
virtualboxend
Synced Folders
Sync a folder on the host machine to the guest machine…
- …project root directory synced to
/vagrant
by default - …overwrite/additions with
config.vm.synced_folder
Vagrantfile
# ...default
.vm.synced_folder ".", "/vagrant"
config
# ...path relative to the project root
.vm.synced_folder "src/", "/srv/website",
configowner: "root", group: "root"
rsync
By default the project directory is shared to /vagrant
# sync data with a running VM instance
vagrant rsync
Configuration…
Vagrantfile
# disabling the default /vagrant share
.vm.synced_folder ".", "/vagrant", disabled: true
config
# explicitly use rsync ...execlude directories
.vm.synced_folder ".", "/vagrant", type: "rsync", rsync__exclude: ".git/" config
rsync-back
Vagrant Rsync-Back Plugin, GitHub – https://github.com/smerrill/vagrant-rsync-back
# install the plugin if required
vagrant plugin install vagrant-rsync-back
# sync data from the VM instance to the host
vagrant rsync-back ...
Network
High-level network configuration 9 directly support by Vagrant:
- …abstraction that works across multiple providers
- …such as forwarded ports, public network connection, private networks
- Networks are automatically configured…
- …as part of the
vagrant up
orvagrant reload
- …as part of the
Set a hostname 10 …modifies /etc/hosts
Vagrantfile
.vm.hostname = "myhost.local" config
IP-Addresses
Defaults to automatic assignment of IP addresses with DHCP
Vagrantfile
.vm.network "private_network", type: "dhcp" config
IP-address range for the default
network…
>>> virsh net-dumpxml default | grep range
<range start='192.168.122.2' end='192.168.122.254'/>
Assign a static IP-address to an instance…
Vagrantfile
.vm.network "private_network", ip: "192.168.122.20" config
VirtualBox 11 will only allow IP addresses in 192.168.56.0/21 12 range…
Vagrantfile
.vm.provider "virtualbox" do |virtualbox|
config.network "private_network", ip: "192.168.56.4"
virtualboxend
Port Forwarding
Port forwarding 13:
Vagrantfile
# change the default port got the SSH connection
.vm.network "forwarded_port", id: "ssh", host: 2200, guest: 22 config
vagrant port
…lists forwarding (on VirtualBox)
Nodes Iterator
Configure multiple nodes with the same configuration:
Vagrantfile
= [ 'alpha', 'beta' ]
nodes 0..(nodes.length - 1)).each do |num|
(= nodes[num]
name .vm.define "#{name}" do |node|
config.vm.hostname = name
node.vm.box = "centos/7"
node.vm.network "private_network", ip: "192.168.18.#{10+num}"
nodeend
end
Use names to operate on a specific box:
vagrant up alpha # start specific box
vagrant ssh alpha # login to a box
vagrant destroy alpha # remove a box
Nested Virtualization
Prerequisites:
# check if nested virtualization is supported
cat /sys/module/kvm_intel/parameters/nested
# enable nested virtualization after next reboot
sudo tee -a /etc/modprobe.d/kvm.conf <<EOF
options kvm_intel nested=1
options kvm_amd nested=1
EOF
Configure a VM to enable nested vitualization:
Vagrantfile
.vm.provider :libvirt do |libvirt|
config.memory = 4096
libvirt.nested = true
libvirt.cpu_mode = "host-passthrough"
libvirtend
Autostart
Make sure to automatically start the network bridge…
virsh net-autostart vagrant-libvirt
…add the autostart
option to the VM configuration:
Vagrantfile
.vm.provider :libvirt do |libvirt|
config.autostart = true
libvirtend
global-status
State of all active Vagrant environments…
- Column
id
used to control a specific machine - Column
name
is defined byconfig.vm.define "$name"
# manipulate a specific machine
vagrant [up|halt|destroy] $id
# clean up stale cache...
vagrant global-status --prune
- Option
--prune
remove invalid entries from the list - Note the
status
sub-commands target a machine in the working director.
SSH Login
General command usage:
# Interactive login via SSH to a Vagrant instance
vagrant ssh ${name:-}
# Execute a command in the Vagrant instance
vagrant ssh ${name:-} -- ls -l
# Dump the SSH configuration...
vagrant ssh-config > ssh-config
# …helps for integration with other tools
Copy Files
Use scp
with the SSH configuration…
# Create a file with the SSH configuration
vagrant ssh-config > ssh-config
# Use SSH option to reference the Vagrant SSH configuration
scp -F ssh-config vagrant@${name:-default}:/bin/bash /tmp
SSH Agent Forwarding
Forwards the local ssh-agent
…
# Create a file with the SSH configuration
vagrant ssh-config > ssh-config
ssh -AF ssh-config vagrant@${name:-default}
Upload Containers
Copy a container image from the local image cache to a Vagrant instance…
# IP address of the Vagrant instance
ip_address=$(vagrant ssh-config | grep -i hostname | cut -d' ' -f)
name=vagrant # can be an arbitrary name
# Setup the connection
podman system connection add \
--identity .vagrant/machines/default/libvirt/private_key \
$name ssh://vagrant@$ip_address:22
# …eventually check the configuration
podman system connection list
# Upload a container
podman image scp $container_image $name::
# Clean up …remove the connection configuration
podman system connection remove $name
VNC
Defaults to following configuration…
Vagrantfile
.vm.provider :libvirt do |libvirt|
config.graphics_type = "vnc"
libvirt.graphics_port = -1 # set automatically by libvirt
libvirt.graphics_ip = "127.0.0.1" # aka localhost
libvirtend
Find the console port number…
- …displayed during boot for the VM instance
- …use
virsh vncdisplay $instance
to retrieve the port number
# search for Qemu processes, instance names and VNC configuration
ps -AfH \
| grep qemu \
| grep -o -e 'guest=[a-z_]*' -e 'vnc 127\.0\.0\.1:[0-9]'
Connect to a VM console with a VNC client…
# install required package
sudo dnf install -y vinagre
# connect to VNC client to a port
vinagre 127.0.0.1:0 2>/dev/null & ; disown
# change the password for the root user
vagrant ssh -c "echo 'root:abc123' | sudo chpasswd"
Provisioning
Execute a provisioning mechanism on the guest machine…
- …multiple options …shell scripts …configuration management systems
- Command-line options
--provision
…force provisioning--no-provision
…explicitly not run provisioners
# implicitly runs provisioning
vagrant up
# explicitly disable provisioning
vagrant up --no-provision
# provision of a running VM instance
vagrant provision
vagrant reload --provision
Run provisioning on demand by setting run: "never"
…
# ...disable provisioner execution
.vm.provision "#{name}", type: "shell", run: "never" do |shell|
config.inline = "echo hello"
shellend
# ...run a specific provisioner by name
vagrant provision --provision-with $name
shell
& file
shell
and file
provisioner:
# -*- mode: ruby -*-
# vi: set ft=ruby :
= 'write this to a file'
content
= %q(
script mkdir ~/projects
echo "foo'bar" > ~/projects/bar.txt
)
Vagrant.configure("2") do |config|
.vm.box = "almalinux/8"
config
# run a command as root
.vm.provision "shell", privileged: true , inline: <<-SHELL
config dnf install -y git vim
SHELL
# copy multiple files
[
".gitconfig",
".gitignore_global"
].each do |file|
.vm.provision "file", source: "~/#{file}", destination: file
configend
# variable expansion...
.vm.provision "shell", inline: %Q(echo "#{content}" > /tmp/content.txt)
config
end
Ansible
Two provisioning methods…
- …Ansible Local Provisioner…
- …does not require to install Ansible on your Vagrant host
- …Ansible must be installed on the quest machine
- …executes
ansible-playbook
directly on the guest machine
- …Ansible Provisioner…
- …allows you to provision the guest using Ansible playbooks…
- …executes
ansible-playbook
from the Vagrant host - …auto-generated inventory in
.vagrant/provisioners/ansible/inventory/
Shared options for both…
- Inventory…
- …
groups
…inventory groups to be included - …
host_vars
…inventory host variables to be included - Or …
inventory_path
..path to an Ansible inventory
- …
- Tags…
- …
tags
…execute matching plays, roles and tasks - …
skip_tags
…execute non-matching plays, roles and tasks
- …
Example Vagrantfile
…
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
.vm.define "test_http_server" # guest name used in Ansible groups
config
#config.vm.box = "rockylinux/8"
.vm.box = "almalinux/8"
config
.vm.provider :libvirt do |libvirt|
config.memory = 1024
libvirt.cpus = 2
libvirtend
.vm.synced_folder ".", "/vagrant", type: "rsync", rsync__exclude: ".git/"
config
.vm.provision "ansible" do |ansible|
config.raw_ssh_args = ['-o UserKnownHostsFile=/dev/null']
ansible.host_key_checking = false
ansible#ansible.verbose = 'vvv'
.playbook = "main.yml"
ansible.groups = { "http_server" => "test_http_server" }
ansible.host_vars = { "http_test" => { "http_port" => 8080 } }
ansibleend
end
…using a playbook main.yml
---
- hosts: http_server
become: true
tasks:
- name: Install dependency packages
ansible.builtin.package:
name: httpd
state: installed
# ...
Debugging
Enable logging with
- …the
VAGRANT_LOG
environmental variable - …or by using the
--debug
option
VAGRANT_LOG=info vagrant up
# or
vagrant up --debug
vagrant up --debug &> | tee vagrant.log
Footnotes
Vagrant Documentation
https://www.vagrantup.com/docs↩︎Vagrant Libvirt Provider, GitHub
https://vagrant-libvirt.github.io/vagrant-libvirt
https://github.com/vagrant-libvirt/vagrant-libvirt↩︎Vagrant Packages, HashiCorp
https://developer.hashicorp.com/vagrant/install?product_intent=vagrant↩︎Virtual Networking, LibVirt documentation
http://wiki.libvirt.org/page/VirtualNetworking↩︎Networks Configuration, Vagrant Libvirt Plugin
https://vagrant-libvirt.github.io/vagrant-libvirt/configuration.html#networks↩︎VirtualBox, Oracle
https://www.virtualbox.org/manual
https://developer.hashicorp.com/vagrant/docs/providers/virtualbox↩︎Roboxes Project
https://roboxes.org
https://github.com/lavabit/robox↩︎Search Vagrant Boxes, Vagrant Cloud
https://app.vagrantup.com/boxes/search↩︎Networking, Vagrant Documentation
https://developer.hashicorp.com/vagrant/docs/networking↩︎Set Hostname, Vagrant Documentation
https://developer.hashicorp.com/vagrant/docs/networking/basic_usage#setting-hostname↩︎VirtualBox Networking, Vagrant documentation
https://www.vagrantup.com/docs/providers/virtualbox/networking↩︎Host-Only Networking, VirtualBox Documentation
https://www.virtualbox.org/manual/ch06.html#network_hostonly↩︎Forwarded Port, Vagrant Documentation
https://developer.hashicorp.com/vagrant/docs/networking/forwarded_ports↩︎