Adding Salt to Vagrant
Rapidly Develop and Test using the Vagrant Salt provisioner
This article covers provisioning systems with the Vagrant Salt provisioner. It demonstrates how to create a local development environment for crafting your Salt solutions.
In 2018, I explored this topic, focusing on Salt Stack and Vagrant, a popular tool for automating virtualization guest systems. Since then, significant changes have occurred:
- CentOS Linux was killed off by RedHat. Alternative guest OS solutions live on through Rocky Linux and AlmaLinux.
- Salt Stack, now referred to simply as Salt, underwent changes after its acquisition by VMWare in 2020 and another acquisition by Broadcom. Salt is now managed by the Salt Project.
- Medium blog site added some color syntax highlighting, making code far more readable.
This article is a refreshed version of my 2018 piece, updated to ensure compatibility with the latest versions.
Previous Article
This is the previous article on this topic from 2018.
Tested Environments
These are the environments I used for testing the solution in this tutorial.
* **Host**:
* macOS "Monterey" 12.2.1
* Vagrant 2.4.1
* Virtualbox 7.0.18r162988
* **Guests**:
* Ubuntu 22.04.3 "Jammy Jellyfish": `generic/ubuntu2204`
* salt 3007.1 (Chlorine)
* Rocky Linux 9.3 "Blue Onyx": `generic/rocky9`
* salt 3007.1 (Chlorine)
Requirements
- Vagrant [
vagrant
]: virtualization management tool - Virtualbox [
vboxmanage
]: virtualization solution that is used by Vagrant - POSIX-compliant shell [
bash
orzsh
]: this is required to setup the directory structure and symbolic links. Windows can use the same commands with either either MSYS2 or Cygwin. Alternatively, you can write your own script in Powershell and use NTFS junctions.
Project Setup
For this project, we will bring up two virtual machines to demonstrate Salt source code across two different Linux distributions: Rocky Linux and Ubuntu. Below is the project directory structure and the files we will use to achieve this setup. The following commands will work in a POSIX-compliant shell such as bash
or zsh
.
# create directory structure
mkdir -p ~/vagrant-salt/roots/{pillar,salt/hello_web/files}
mkdir -p ~/vagrant-salt/vm_guests/{ubuntu2204,rocky9}
cd ~/vagrant-salt
# create files
touch roots/salt/top.sls \
roots/pillar/{top.sls,hello_web.sls} \
roots/salt/hello_web/{defaults.yaml,init.sls,map.jinja} \
vm_guests/{ubuntu2204,rocky9}/Vagrantfile
cat <<-'HTML_EOF' > roots/salt/hello_web/files/index.html
<html>
<body>
<h1>Hello World!</h1>
</body>
</html>
HTML_EOF
# create symbolic links to salt source code
pushd vm_guests/rocky9 && ln -s ../../roots/ roots && popd
pushd vm_guests/ubuntu2204 && ln -s ../../roots/ roots && popd
📓 NOTE: Windows users can get a bash
shell with MSYS2, which can be installed using the installer or through a package manager like Chocolatey. Both Linux and macOS come with zsh
and bash
pre-installed. On macOS, you can update to the latest versions of these shells using Homebrew or MacPorts.
The results should look like this (from the tree
command):
Vagrant Configuration
We will configure Vagrant to use two Linux guest operating systems: a recent version of Ubuntu and Rocky Linux. For this tutorial, we will use the default virtualbox
provider.
📔 NOTE: The virtualbox
provider will only work on Intel systems running Virtualbox on macOS, Windows, or Linux. This will not work on Mac systems with the new Apple Silicon (ARM64 processors). For those, see my article “Vagrant with Macbook Mx (arm64)” for alternatives.
📓 NOTE: Vagrant supports several virtualization solutions, including Windows Hyper-V with the hyperv
provider, macOS Hypervisor.framework through the vagrant-qemu
plugin, and Linux KVM through the vagrant-libvirt
plugin. For consistency and simplicity, this tutorial documents only the default virtualbox
provider.
Ubuntu 22.04 guest virtual machine
Update vm_guests/ubuntu2204/Vagrantfile
with the following content.
Vagrant.configure('2') do |config|
config.vm.box = 'generic/ubuntu2204'
config.vm.network 'forwarded_port', guest: 80, host: 8085
####### File Share #######
config.vm.synced_folder './roots/salt/', '/srv/salt'
config.vm.synced_folder './roots/pillar', '/srv/pillar'
####### Provision #######
config.vm.provision :salt do |salt|
salt.masterless = true
salt.run_highstate = true
salt.verbose = true
end
end
You can download Ubuntu 22.04 image and start the virtual guest without provisioning with the following command:
pushd vm_guests/ubuntu2204 && vagrant up --no-provision && popd
Rocky Linux 9 guest virtual machine
In the previous article, we used CentOS, but this is no longer around. Instead, we’ll use Rocky Linux.
Update vm_guests/rocky9/Vagrantfile
with the following content.
Vagrant.configure("2") do |config|
config.vm.box = "generic/rocky9"
config.vm.network "forwarded_port", guest: 80, host: 8083
####### File Share #######
config.vm.synced_folder "./roots/salt/", "/srv/salt"
config.vm.synced_folder "./roots/pillar", "/srv/pillar"
####### Provision #######
config.vm.provision :salt do |salt|
salt.masterless = true
salt.run_highstate = true
salt.verbose = true
salt.pillar "hello_web" => {
"package" => "httpd",
"service" => "httpd",
"docroot" => "/var/www/html"
}
end
end
You can download Rocky 9 image and start the virtual guest without provisioning with the following command:
pushd vm_guests/rocky9 && vagrant up --no-provision && popd
Salt Configuration
The Salt platform consists of the following components:
- State: Defines the desired state of the system or the steps needed to configure and manage systems, referred to as minions.
- Pillar: Data stored in a tree-like structure, used for defining and managing secure data utilized by Salt states.
- Grain: Key-value pairs that provide static information about a Salt minion.
- Module: A collection of functions that perform specific tasks.
- Formula: A structured way to package reusable states, configurations, and templates.
- Top File: A configuration file that maps which state files should be applied to which minions or managed systems.
Below is how to configure Salt states, Salt pillars, and a Salt formula called hello_web
.
Top Files
In the top file, we can organize and group multiple systems, specifying what actions to perform on them, such as which formulas to run. For our single-machine scenario, we’re going to run the hello_web
formula on all systems.
Update the roots/salt/top.sls
to match the following:
base:
'*':
- hello_web
The pillar will have something similar. Update the roots/pillar/top.sls
to have the following:
base:
'*':
- hello_web
This will look for data in hello_web.sls
, which for this tutorial will be an empty file placeholder.
Formula Defaults
Update roots/salt/hello_web/defaults.yaml
with the following:
hello_web:
docroot: /var/www/html
package: apache2
service: apache2
These are the default variables used when running the formula. They can be overridden in a manner similar to passing variables to a function.
Formula Map
Update roots/salt/hello_web/map.jinja
with the following:
{% import_yaml 'hello_web/defaults.yaml' as defaults %}
{% set hello_web = salt['pillar.get'](
'hello_web',
default=defaults.get('hello_web'),
merge=True) or defaults.get('hello_web')
%}
The formula map integrates all the variables that we want to use within the formula. It allows us to specify local variables and merge these with data from the pillar.
In the provided code, if pillar.get
returns None
, the defaults from default.yaml
will be used. The final hello_web
variable must contain a value other than None
; otherwise, an error will occur.
Formula Salt State
Below is the main salt state file for the formula. Update the roots/salt/hello_web/init.sls
with the following:
{% from "hello_web/map.jinja" import hello_web with context %}
hello_web:
pkg.installed:
- name: {{ hello_web.package }}
service.running:
- name: {{ hello_web.service }}
- enable: True
- reload: True
file.managed:
- name: {{ hello_web.docroot }}/index.html
- source: salt://hello_web/files/index.html
This formula will install the Apache HTTP server, enable and start the service, and configure a document root with a web page.
The pkg
and service
modules intelligently detect the underlying package and service management systems. For example, on Linux, they can handle yum
or apt-get
for installing packages. Since the package name may vary, you may need to override the defaults if necessary.
Provisioning the Systems
With the Salt code set up and configured, we can now proceed to provision the systems.
Ubuntu 22.04
pushd vm_guests/ubuntu2204
vagrant provision
popd
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 this:
Rocky Linux 9
pushd vm_guests/rocky9
vagrant up --no-provision
By default, the Rocky Linux guest system has a firewall enabled, which restricts access from outside the guest system. To allow traffic, you can open the necessary ports using the following command:
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:
When done, you can return to the previous directory with
popd
The Magic of Vagrant
Using Vagrant, we easily download system images, launched two virtual machine guests, and provisioned the guests with Salt. As part of running the guest systems, we brought up virtual networks, and mapped ports 8085
and 8083
on localhost to port 80
on the guest systems.
We can view the running systems with Virtualbox Manager application:
Controlling how Salt runs
The Salt Provisioner in Vagrant provides some options that are used to configure how to run Salt. These are the ones we used for this tutorial:
- masterless: When set to
true
, it runs Salt in masterless mode, where Salt executes states locally on the machine without requiring a Salt master. This is useful for simpler setups or testing. - run_highstate: Automatically triggers the
state.highstate
command during provisioning, applying all states defined for the minion as specified in the top file. This ensures that the minion is configured as intended right from the start. - verbose: When enabled, it provides detailed output from Salt commands, including debug information. This is helpful for troubleshooting and understanding what Salt is doing during the provisioning process.
- pillar: Allows you to pass pillar data directly from the command line, which can override default values in your pillar files. This is particularly useful for customizing configurations on different systems, like providing package names for RHEL instead of the Ubuntu defaults that we defined in
defaults.yaml
.
Cleanup
These systems can be cleanup 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
The primary goal of this article was to provide an introduction to Salt and demonstrate how Vagrant can be used to test Salt configurations efficiently.
One Code, Many Systems
We illustrated how a single set of code can configure various systems, such as Ubuntu and Rocky Linux, thanks to Salt’s underlying modules like pkg
and service
. These modules abstract away the differences between package managers (e.g., yum
vs. apt-get
), enabling consistent configuration management regardless of the system's specifics. This functionality aligns with promise theory, where modules guarantee to implement the desired state specified by the control system, represented by the states in your Salt SLS files.
In practice, many organizations standardize on a consistent platform, such as Ubuntu, to streamline operations and reduce costs. This approach simplifies the codebase by limiting testing to a single environment. For components like firewalls, where universal modules may not exist, you may need to develop custom modules or advanced formulas to handle system-specific variations. Standardizing on one platform eliminates the need for such customization in these cases.
In contrast, maintainers of public formulas or modules must test across various systems, such as Rocky Linux and Ubuntu, to ensure broad compatibility. These formulas and modules need to accommodate system-specific variations to function correctly in different environments. This allows Salt Stack to come closer to the promise of promise theory.
The Current Market
I am not aware of any specific sites that track the popularity of configuration management, remote execution, and cloud provisioning platforms.
Based on my experience in the San Francisco Bay Area, I recall that around 2016, there was significant interest in both Ansible and Salt. This was partly due to Python’s popularity and the suitability of these configuration management platforms with remote execution capabilities for cloud environments, where systems are often short-lived.
In 2018, I worked with Salt Stack, but since then, I haven’t encountered many job listings requiring Salt. This might be due to the lack of robust enterprise support, certifications, and professional services. In 2024, I found offerings for contract work with the Firebase team at Google using Salt for data center configuration, but the compensation for such roles has decreased by more than 54% compared to similar positions in 2018.
Final Thoughts
While the current market for Salt may not be as robust as it once was, its potential for integration with other innovative platforms such as Consul for service discovery and Vault for secrets management remains significant.
The flexibility and power of Salt, demonstrated through its ability to manage various systems with a single codebase, underscore its value in the realm of configuration management.
As the industry evolves, opportunities for Salt may increase, particularly if enterprise support and compensation become more competitive. For those who continue to explore and utilize Salt, its capabilities offer a strong foundation for efficient and scalable configuration management.
Resources
These are some links to information that is useful.