Salt DevKit: Developing Formulas
Developing Salt Formulas on a Salt Stack Dev Environment
Salt Stack offers modularity where you can package up your Salt Stack code into a single component called a Salt formula. You store on the file system along with other Salt code, or publish these in a git repository.
In previous article, I documented how to use external modules, such as the nginx-formula, and now in this article, I show how to craft your own formulas. We’ll demonstrate how to install Docker and run a nginx container.
We will facilitate this by using a local Salt DevKit by using Vagrant with the Vagrant Salt Provisioner.
Previous Article
How to use external formulas:
Tool Prerequisites
These are the tools used in this guide:
- Vagrant is virtual machine automation solution setups up local development environments.
- VirtualBox (recommended) is a cross-platform (Windows, Linux, mac OS) that allows you to run virtual machines. Another virtual machine system can be used, such as Hyper-V for Windows 10 Pro or KVM (libvrt) on Linux. This guide uses VirtualBox as it is open source and cross platform.
- Bash Shell (recommended) is not strictly required, but the commands in this tutorial are tested and oriented toward
bash
. [Note: this is default on mac OS and popular Linux distros, or msys2 with Windows].
The Guide
In this guide, we start by creating the docker formula, then states and pillars that use the docker formula, and finally the Vagrantfile
configuration that will show how to provision the system using all of this.
Step 1: Create Project Area
Create the directory and file structure that looks like following:
.
├── Vagrantfile
└── roots
├── pillar
│ ├── docker.sls
│ └── top.sls
└── salt
├── docker
│ ├── default.yaml
│ ├── init.sls
│ └── map.jinja
├── docker_nginx.sls
└── top.sls
In Bash, you can quickly create this by running the following:
# create directory structure
mkdir -p ~/salt-devkit-dev-formula/roots/{pillar,salt/docker}
cd ~/salt-devkit-dev-formula# create files
touch roots/salt/{top.sls,docker_nginx.sls} \
roots/salt/docker/{default.yaml,init.sls,map.jinja} \
roots/pillar/{top.sls,docker.sls} \
Vagrantfile
Step 2: Create Docker Formula
A typical Salt formula has an main entry point (init.sls
), default values (default.yaml
), and a map.jinja
to merge variables between pillars and local defaults, should values in the pillar not be set.
We can start by adding the following to salt/docker/init.sls
:
This is a very generic way to install package dependencies, add a remote Debian repository, and then install the desired package from the remote repository.
To reference these values, we need to set them somewhere locally. Edit the docker/default.yaml
, and add the following:
You noticed that the top of the salt/docker/init.sls
, we had this line:
{% from "docker/map.jinja" import docker with context %}
This references our local map that will merge pillar and default data together into a single structure. Edit the salt/docker/map.jinja
with the following content:
Note: In the above map, we create a dict referenced by the key docker. This merges both default.yaml
and pillar, and should the user neglect to setup docker:
in a pillar somewhere, this map will then use the defaults.yaml
only.
So far, we showed how to install the docker environment on the target guest system. For convenience, we may want to add developers to the docker group, so that they can access docker. For demonstration purposes, we’ll add the vagrant
user, so that it can access the docker on the guest system. Append the following code to salt/docker/init.sls
.
Now that Docker is installed, we need to install the docker python module, so that Salt can actually manage docker wherever docker is installed. We can do that by appending the following to salt/docker/init.sls
:
Note: Both python and python3 versions of pip
are installed, so that either will be available regardless of the Python version Salt commands use.
Step 3: Create Salt State
Let’s create a state that will use docker. Edit docker_nginx.sls
file and add the following content:
This will run a container using some settings up we set locally. This is another way to set values locally.
Note: This state will only work if docker engine along with python module docker are installed on the target system previously.
We can create a top state that will use both of these:
If we run this in highstate, it will fail with a Traceback on its first run, because despite installing docker engine along with the docker python module, the python module will not be in memory, so will need to run again.
Normally, you could reload the python modules with reload_modules: true
when installing a python module that you need, but this doesn’t seem to be working anymore:
Step 4: Create Salt Pillars
Now comes time to setup the values we want to use for our project. First edit the top pillar pillar/top.sls
that references pillars for use with our docker
formula:
Now we should define our values for docker. Edit pillar/docker.sls
pillar:
Normally, in pure function mindset, a modular component like a formula should not be dependent global states outside its function. In a previous step, when we created a map.jinja
that should not depend setting any values external to the formula itself.
However, that being said, many formulas out there will fall flat and give feedback with a Traceback when pillar values are not set.
So for that above reason, when using a formula, it is good practice in Salt, to, at minimum, setup required key(s) without any value fields. This will create an empty dict
structure when the YAML is converted into Python.
Step 5: Create Vagrant Configuration
Add the following content to the Vagrantfile
configuration:
In this configuration, we want to do provisioning in two phases:
- Install Docker CE and Python
docker
module. - Run
nginx
docker container
I posed some questions and answers for this approach:
Why use two steps instead of just one step?
Earlier, we noted that a newly installed docker python module is not available in Salt’s memory space, and so we need to run it again for all states that require this module. This is the reason for two steps.
Why must we use Shell provisioner?
Simply, the salt provisioner can only run everything in highstate (roots/salt/top.sls
) when using masterless.
Therefore, we have to use the shell provisioner to run a single state in two separate commands, one for the docker
formula and one for the docker_nginx
state.
Why then use Salt provisioner at all?
The salt provisioner is used only for downloading and installing the Salt minion on this system. This needs to happen before running salt commands with the shell provisioner.
Step 6: Provision and Test the Docker Formula
The whole Ubuntu 18.04 guest environment (using VirtualBox) can be brought up by typing the following:
vagrant up --no-provision
If you are doing this the first time, it may take some time to download the Ubuntu 18.04 box image, and then create and run the virtual machine guest.
Once completed, you can provision the system using docker
formula and docker_nginx
state to demonstrate docker is working.
vagrant provision
After, you can test by pointing your web browser to http://localhost:8081 or if you have curl
, you can run:
curl localhost:8081
Step 7: Clean up
You can halt and destroy the virtual guest by the following command:
vagrant destroy
I recommend doing this if the virtual machine guest instance is no longer needed, as this will free a few gigabytes of disk space.
Testing Solution with Python 3
Salt Stack installation process (salt bootstrap) defaults to Python2. Unfortunately, Python2 is officially deprecated as of 2020.
On a clean system (one that hasn’t been provisioned already), you can create a fresh Ubuntu guest system, bootstrap it with Salt using Python3, and then provision with Salt using Python3, by typing the following:
PYTHON=python3 vagrant up
If the system is already up, but not provisioned yet (and no Salt is yet installed), then you can just do:
PYTHON=python3 vagrant provision
Note: In the Vagrantfile
configuration (see Step 6 above), I added capability to use either Python3 or Python2, by specifying the environment variable PYTHON
.
Links
Here are some useful articles I have come across that might be useful in your journey and related to the topics covered above.
Docker
Here’s information about installing Docker Engine as well as the nginx image (and source).
Vagrant Ubuntu 18.04 box
Vagrant community calls system images boxes (or boxen). This guide uses a bento box, which is a kit for building vagrant boxes in a variety of distros for the purpose of testing change configuration tools.
Vagrant
Information about provisioners in case you need options not demonstrated in this guide:
Salt States
General information about Salt States.
- States Tutorial, Part I: Basic Usage
- SaltStack Fundamentals: Getting Started Tutorial: Create a Salt State
- How do I use Salt States?
Salt Pillars
General information about Salt Pillars.
- Salt Stack Pillar Walkthrough
- Salt Configuration Management: Getting Started: Pillar
- Storing static Data in the Pillar
Salt Formulas
General information about Salt Formulas.
- Salt Formulas (Community Practices)
- Salt Formula (ReadTheDocs)
- Salt Formulas (Conventions)
- Use and Modify Official SaltStack Formulas (Linode)
Salt References
Some Salt states configured in this guide.
Conclusion
This concludes the guide on how to quickly develop formulas for use with your Salt Stack environment. Vagrant as a tool from Hashicorp is useful to configuring, developing, and testing Salt formulas before they are integrated into production.