Puppetry with Vagrant — Rapidly Develop and Test using the Vagrant Puppet Apply provisioner

Puppetry with Vagrant

Streamlining Puppet Development with Vagrant

Joaquín Menchaca (智裕)
9 min read1 day ago

--

This tutorial covers how to provision systems using the Vagrant Puppet Apply Provisioner. You’ll learn how to set up a local development environment to create and test Puppet manifests and modules.

Vagrant is widely used for automating the creation and provisioning of virtual machines, offering various provisioners, including Shell and Puppet Apply. By combining Vagrant with your preferred text editor, you can have an IDE-like environment for developing change configuration scripts, or infrastructure-as-code.

In 2018, I explored this topic with Puppet 5 on Ubuntu 16.04 (Xenial Xarus) and CentOS 7. This updated guide uses Puppet 8.9 on Ubuntu 22.04 (Jammy Jellyfish) and Rocky Linux 9, allowing you to test across both systems based on the Debian family or RHEL family operating systems.

Tested Environments

These are the test environments I used for testing this solution.

* **Host**: 
* macOS "Monterey" 12.2.1
* Vagrant 2.4.1
* Virtualbox 7.0.18r162988
* **Guests**:
* Ubuntu 22.04.3 "Jammy Jellyfish": `generic/ubuntu2204`
* puppet 8.9.0
* Rocky Linux 9.3 "Blue Onyx": `generic/rocky9`
* puppet 8.9.0

Requirements

These are the tools required for this tutorial.

  • Vagrant [vagrant]: virtualization management tool
  • Virtualbox [vboxmanage]: virtualization solution that is used by Vagrant
  • POSIX-compliant shell [bash or zsh]: tutorial uses commands compatible with one of these shells. On Windows, you can run these shells with either MSYS2 or Cygwin environments.

Project Setup

Here’s how you can set up the project directories for this tutorial:

PROJ_HOME=~/vagrant-puppet

# craete directory structure
mkdir -p \
$PROJ_HOME/site/hello_web/{manifests,files} \
$PROJ_HOME/{ubuntu2204,rocky9}/manifests
cd $PROJ_HOME

# create files
touch \
bootstrap.sh \
site/hello_web/{manifests/init.pp,files/index.html,metadata.json} \
{ubuntu2204,rocky9}/{manifests/default.pp,Vagrantfile}

Afterward, the following structure should be created:

Visual display of directory structure

Powershell Hints

This is off-topic and beyond the scope of this tutorial, but if you prefer using PowerShell on Windows instead of a POSIX shell, you’ll need to convert some commands. Here are a few tips:

  • Replace the tilde (~) with $HOME.
  • Use Set-Location in place of cd.
  • For pushd and popd, use Push-Location and Pop-Location.
  • Replace mkdir and touch with New-Item.
  • Ensure the vagrant command is in your path by adding it to $env:PATH.

As an example, the PowerShell script below can create the same directory structure for this project.

# Create directory structure
New-Item -ItemType Directory -Force -Path `
"$HOME\vagrant-puppet\site\hello_web\manifests", `
"$HOME\vagrant-puppet\site\hello_web\files", `
"$HOME\vagrant-puppet\ubuntu2204\manifests", `
"$HOME\vagrant-puppet\rocky9\manifests"

Set-Location "$HOME\vagrant-puppet"

# Create files
New-Item -ItemType File -Force -Path `
"$HOME\vagrant-puppet\bootstrap.sh", `
"$HOME\vagrant-puppet\site\hello_web\manifests\init.pp", `
"$HOME\vagrant-puppet\site\hello_web\files\index.html", `
"$HOME\vagrant-puppet\site\hello_web\metadata.json", `
"$HOME\vagrant-puppet\ubuntu2204\manifests\default.pp", `
"$HOME\vagrant-puppet\ubuntu2204\Vagrantfile", `
"$HOME\vagrant-puppet\rocky9\manifests\default.pp", `
"$HOME\vagrant-puppet\rocky9\Vagrantfile"

Vagrant Configuration

In this tutorial, we’ll use VirtualBox as the default provider, which works consistently on Intel-based machines running Windows, macOS, and Linux. We’ll be configuring recent versions of Ubuntu and Rocky Linux.

Vagrant also supports other providers besides VirtualBox. Although this tutorial focuses on VirtualBox, feel free to explore these alternatives based on your system:

  • Windows Professional: Hyper-V using the hyperv provider
  • macOS: Hypervisor.framework using the vagrant-qemu plugin
  • Linux: KVM using the vagrant-libvirt plugin

Ubuntu 22.04 guest virtual machine

Update the file $HOME/vagrant-puppet/ubuntu2204/Vagrantfile with the following:

Vagrant.configure('2') do |config|
config.vm.box = 'generic/ubuntu2204'
config.vm.network 'forwarded_port', guest: 80, host: 8085

####### Install Puppet Agent #######
config.vm.provision "bootstrap",
before: :all,
type: "shell",
path: "../bootstrap.sh"

####### Provision #######
config.vm.provision :puppet do |puppet|
puppet.module_path = "../site"
puppet.options = "--verbose --debug"
end
end

You can download this image and start the virtual machine with the following command:

pushd ~/vagrant-puppet/ubuntu2204 && vagrant up --no-provision && popd

Rocket Linux 9 guest virtual machine

Update the file $HOME/vagrant-puppet/rocky9/Vagrantfile with the following:

Vagrant.configure('2') do |config|
config.vm.box = 'generic/rocky9'
config.vm.network 'forwarded_port', guest: 80, host: 8085

####### Install Puppet Agent #######
config.vm.provision "bootstrap",
before: :all,
type: "shell",
path: "../bootstrap.sh"

####### Provision #######
config.vm.provision :puppet do |puppet|
puppet.module_path = "../site"
puppet.options = "--verbose --debug"
end
end

You can download this image and start the virtual machine with the following command:

pushd ~/vagrant-puppet/rocky9 && vagrant up --no-provision && popd

Installing Puppet

The Vagrant Puppet Apply provisioner does not bootstrap the systems with the Puppet agent, so we have to install this ourselves before we can use Puppet.

First, update the file $HOME/vagrant-puppet/boostrap.sh with the following code:

#!/bin/sh
command -v puppet > /dev/null \
&& { echo "Puppet is installed! skipping" ; exit 0; }

ID=$(grep -oP '(?<=^ID=).*' /etc/os-release | tr -d '"')

case "${ID}" in
rocky)
VERS=$(grep -oP '(?<=PLATFORM_ID="platform:el).*[0-9]' /etc/os-release)
sudo rpm -Uvh https://yum.puppet.com/puppet8-release-el-${VERS}.noarch.rpm
sudo yum install -y puppet-agent
;;
debian|ubuntu)
wget https://apt.puppetlabs.com/puppet8-release-$(lsb_release -cs).deb
sudo dpkg -i puppet8-release-$(lsb_release -cs).deb
sudo apt-get -qq update
sudo apt-get install -y puppet-agent
;;
*)
echo "ERROR: Distro '${ID}' not supported" 2>&1
exit 1
;;
esac

Ubuntu 22.04

On the Ubuntu 22.04 virtual machine guest, you can install (bootstrap) the puppet agent using the following command:

pushd ~/vagrant-puppet/ubuntu2204 && \
vagrant provision --provision-with "bootstrap" && popd

Rocky 9

On the Rocky 9 virtual machine guest, you can install (bootstrap) the puppet agent using the following command:

pushd ~/vagrant-puppet/rocky9 \
&& vagrant provision --provision-with "bootstrap" && popd

Puppet Configuration

This guide will walk you through setting up all the Infrastructure-as-Code (IaC) scripts used in this project. Puppet consists of several key components:

  • Manifests: Puppet’s Domain-Specific Language (DSL) that defines the desired state of a system.
  • Modules: Collections of manifests, templates, and files organized to manage system configurations.
  • Facts: Information about a system (node) that can be used by manifests and modules.
  • Node Definitions: Blocks within manifests that define configurations for specific nodes, based on their hostname or facts.
  • Site Manifest: The primary manifest that orchestrates and configures your entire infrastructure, often including node definitions.

Site Manifest

In this tutorial, we won’t use a formal site manifest to list all nodes. Instead, we’ll use a local manifest/default.pp file, which will apply a default configuration to the single virtual machine guest.

This manifest will call our module hello_web and optionally pass in parameters if they are different than the defaults.

For Ubuntu, update the file $HOME/vagrant-puppet/ubuntu2204/manifests/default.pp with:

node default {
class { 'hello_web': }
}

For Rocky Linux, update the file $HOME/vagrant-puppet/rocky9/manifests/default.pp with:

node default {
class { 'hello_web':
package_name => 'httpd',
service_name => 'httpd',
doc_root => '/var/www/html',
}
}

The hello_web module

This is a simple module to install Apache HTTP Server and a default hello world web page.

Update $HOME/vagrant-puppet/site/hello_web/files/index.html with the following:

<html>
<body>
<h1>Hello World!</h1>
</body>
</html>

Update $HOME/vagrant-puppet/site/manifests/init.pp with the following:

class hello_web (
$package_name = 'apache2',
$service_name = 'apache2',
$doc_root = '/var/www/html'
) {

package { $package_name:
ensure => present,
}

service { $service_name:
ensure => running,
enable => true,
}

file { "$doc_root/index.html":
source => "puppet:///modules/hello_web/index.html",
}
}

Update $HOME/vagrant-puppet/site/metadata.json with the following:

{
"name": "joachim8675309-hello_web",
"version": "0.1.0",
"author": "joachim8675309",
"summary": "Hello World Tutorial",
"license": "Apache-2.0",
"source": "https://github.com/darkn3rd/blog_tutorials",
"dependencies": [],
"operatingsystem_support": [
{
"operatingsystem": "RedHat",
"operatingsystemrelease": ["9"]
},
{
"operatingsystem": "Rocky",
"operatingsystemrelease": ["9"]
},
{
"operatingsystem": "Debian",
"operatingsystemrelease": ["12"]
},
{
"operatingsystem": "Ubuntu",
"operatingsystemrelease": ["22.04"]
}
],
"requirements": [
{
"name": "puppet",
"version_requirement": ">= 7.24 < 9.0.0"
}
]
}

Provisioning Automatically

You can run puppet apply through the following commands below.

Ubuntu 22.04

On the Ubuntu 22.04 virtual machine guest, you can provision using Puppet with the following command:

pushd ~/vagrant-puppet/ubuntu2204 && vagrant provision && popd

Rocky 9

On the Rocky 9 virtual machine guest, you can provision using Puppet with the following command:

pushd ~/vagrant-puppet/rocky9 && vagrant provision && popd

Provisioning Manually

Alternatively, instead of using Vagrant to automate running the puppet command, you can run this yourself. Here’s how you can do that:

Ubuntu 22.04

First log onto the virtual machine guest:

cd ~/vagrant-puppet/ubuntu2204
vagrant ssh

On the virtual machine guest, run these commands:

MODULES=$(mount | grep -o '/tmp/vagrant-puppet/modules-[^ ]*')
MANIFESTS=$(mount | grep -o '/tmp/vagrant-puppet/manifests-[^ ]*' | uniq)

puppet apply --verbose --debug \
--modulepath "$MODULES:/etc/puppet/modules" \
--detailed-exitcodes \
"$MANIFESTS/default.pp"

# When finished
logout

Rocky 9

First log onto the virtual machine guest:

cd ~/vagrant-puppet/rocky9
vagrant ssh

On the virtual machine guest, run these commands:

MODULES=$(mount | grep -o '/tmp/vagrant-puppet/modules-[^ ]*')
MANIFESTS=$(mount | grep -o '/tmp/vagrant-puppet/manifests-[^ ]*' | uniq)

puppet apply --verbose --debug \
--modulepath "$MODULES:/etc/puppet/modules" \
--detailed-exitcodes \
"$MANIFESTS/default.pp"

# When finished
logout

Testing the Solution

You can use curl with the command line option to include headers in the response for testing purposes.

Ubuntu 22.04

The Ubuntu guest system, by default, does not have a firewall, making it accessible from outside the guest system. This setup maps port 80 to port 8085. To test this, you can use the following command from the host system:

curl --include http://localhost:8085

This should show something like the following below:

Rocky 9

By default, the Rocky Linux guest system has a firewall enabled, restricting access from outside the guest system. To allow traffic, you can open the necessary ports using the following command:

cd ~/vagrant-puppet/rocky9

vagrant ssh --command \
'sudo /usr/bin/firewall-cmd --zone=public --add-port=80/tcp'

This vagrant setup maps port 80 to port 8083. To test this, you can use the following command from the host system:

curl --include http://localhost:8083

This should show something like the following below:

Cleanup

These systems can be cleaned up by using the vagrant destroy command. This will halt the system and delete the disk image used by the guest VM.

pushd vm_guests/ubuntu2204 && vagrant destroy && popd
pushd vm_guests/rocky9&& vagrant destroy && popd

Conclusion

In many organizations, engineers often joke that “Prod is the new Test,” highlighting the tendency for untested changes to reach production. One major challenge with configuration management tools like Puppet, and infrastructure-as-code in general, is the difficulty of testing changes locally. Vagrant addresses this by reducing the friction, making it easier to develop and test in a local environment before pushing changes to staging or production.

This tutorial serves as a simple introduction to help you quickly get started with Puppet. I’ve intentionally kept the example straightforward — a basic “hello world” scenario — to ease newcomers into Puppet and Vagrant.

In more complex environments, you’d be managing fleets of servers, each interacting with databases, monitoring tools, and services like message queues, search indexing, caching, and reverse proxy load balancers. And that’s just for monolithic applications.

When managing clusters (e.g., ElasticSearch) or microservices, asynchronous service discovery becomes crucial. Tools like Consul or cloud metadata like AWS tags help with this process. For more advanced setups, you’ll also need to integrate an External Node Classifier (ENC) or Hiera plugins to retrieve real-time health information from cluster nodes, which is then used to configure other interdependent nodes accordingly.

Thank you for reading!

Related Articles

Earlier Article

This was the original article written 2018 that has some extra material about using node classifiers, smart parameters with an inherited class, and some comments about Hiera and ENC.

Vagrant with Salt Provisioner

Similar article using the Salt provisioner.

Vagrant with Macbook Mx (arm64)

Article that shows how to run virtual machines using hypervisor.framework or emulator with q35 using the vagrant-qemu plugin.

Resources

These are articles that I found through searches (some dated) about introductory material on Puppet.

--

--