Introduction
Infrastructure as Code (IaC) has revolutionized how we manage servers and applications. Among the various configuration management tools available, SaltStack stands out for its speed, scalability, and flexibility. In this comprehensive guide, we’ll explore SaltStack from the ground up, with a focus on AlmaLinux 10.
Whether you’re managing a handful of servers or thousands, SaltStack can help you automate configuration, deployment, and orchestration tasks efficiently.
What is SaltStack?
SaltStack (often called “Salt”) is an open-source configuration management and remote execution tool. It uses a master-minion architecture where a central Salt master manages multiple Salt minions (your servers).
Key Features
- Fast and Scalable: Built on ZeroMQ, Salt can manage thousands of nodes simultaneously
- Event-Driven: Real-time execution and event system for orchestration
- Python-Based: Easy to extend with custom modules
- Declarative Configuration: Define desired state, Salt ensures it’s maintained
- Remote Execution: Run commands across your infrastructure instantly
- Flexible: Works with or without agents (masterless mode available)
Why SaltStack?
Use Cases
1. Configuration Management
- Ensure all servers have consistent configurations
- Manage files, packages, services, and users
- Enforce security policies across your infrastructure
2. Application Deployment
- Deploy applications to multiple servers simultaneously
- Rollback deployments if issues occur
- Blue-green and canary deployments
3. Infrastructure Automation
- Provision new servers automatically
- Configure networking, storage, and security
- Integrate with cloud providers (AWS, Azure, GCP)
4. Orchestration
- Coordinate complex multi-step deployments
- Execute tasks in specific order across multiple servers
- React to events in real-time
5. Compliance and Security
- Enforce security baselines
- Audit configurations
- Remediate drift automatically
SaltStack vs Other Tools
| Feature | SaltStack | Ansible | Puppet | Chef |
|---|---|---|---|---|
| Speed | Very Fast | Moderate | Moderate | Moderate |
| Scalability | Excellent | Good | Good | Good |
| Agent Required | Yes (can run agentless) | No | Yes | Yes |
| Learning Curve | Moderate | Easy | Steep | Steep |
| Language | Python + YAML | YAML | Ruby DSL | Ruby DSL |
| Real-time Execution | Yes | No | No | No |
Core Concepts
1. Master-Minion Architecture
βββββββββββββββββββ
β Salt Master β β Central management server
β (Control Node) β
ββββββββββ¬βββββββββ
β
ββββββ΄βββββ¬βββββββββ¬ββββββββββ
β β β β
βββββΌββββ ββββΌββββ ββββΌββββ ββββΌββββ
βMinion1β βMinion2β βMinion3β βMinion4β
βββββββββ ββββββββ ββββββββ ββββββββ
Web DB Cache API
- Master: Central server that sends commands and configurations to minions
- Minions: Servers managed by the master, executing commands and applying configurations
2. States
States are declarative descriptions of how a system should be configured. Written in YAML with Jinja templating.
Example State:
nginx:
pkg.installed:
- name: nginx
nginx-service:
service.running:
- name: nginx
- enable: True
- require:
- pkg: nginx
3. Pillar
Pillar stores configuration data specific to minions. Think of it as variables for your infrastructure.
Example Pillar:
nginx:
port: 80
domain: example.com
database:
host: db.example.com
port: 5432
4. Grains
Grains are static information about minions (OS, CPU, memory, network). Collected when minion starts.
salt 'web-server' grains.items
# Shows: os, osrelease, cpu_model, mem_total, ip_interfaces, etc.
5. Execution Modules
Remote execution functions you can run on minions.
salt '*' cmd.run 'hostname'
salt 'web-*' service.restart nginx
salt 'db-*' pkg.install postgresql
Setting Up Your First Salt Environment
Prerequisites
- Two AlmaLinux 10 servers (or VMs)
- 1 for Salt Master (2 CPU, 2GB RAM minimum)
- 1 for Salt Minion (1 CPU, 1GB RAM minimum)
- Root or sudo access
- Network connectivity between servers
Lab Setup
For this tutorial, we’ll use:
- Master:
salt-master(192.168.1.10) - Minion:
web-server(192.168.1.11)
Step 1: Install Salt Master
SSH to your master server:
Update the system:
dnf update -y
Install Salt Master:
# Add the official Salt Project repository
curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.repo \
| sudo tee /etc/yum.repos.d/salt.repo
sudo dnf clean expire-cache
# Install Salt master and minion
dnf install -y salt-master salt-minion
Two traps in older copies of this guide. The first is that the repository setup used to show Debian/Ubuntu instructions β a
gpg --dearmorkeyring and adeb β¦line written to/etc/apt/sources.list.d/β and then calleddnfto install. Thoseaptlines do nothing on AlmaLinux;/etc/yum.repos.d/salt.repoabove is the RPM equivalent. The second is reaching for EPEL: EPEL ships Salt only up to EPEL 9, and there is no Salt package in EPEL 10, sodnf install salt-masterwithout the repo above simply fails on AlmaLinux 10. See the Salt install guide for the current RPM instructions.
Start and enable Salt Master:
systemctl enable --now salt-master
systemctl status salt-master
Configure firewall:
# Allow Salt master ports
firewall-cmd --permanent --add-port=4505/tcp # Salt publish port
firewall-cmd --permanent --add-port=4506/tcp # Salt return port
firewall-cmd --reload
Verify installation:
salt --version
# Should show: salt 3008.x β 3008 is the newest LTS branch enabled by default
# in salt.repo. To hold this host on 3006 LTS or 3007 STS instead, edit
# /etc/yum.repos.d/salt.repo and enable only that branch's section.
Step 2: Install Salt Minion
SSH to your minion server:
Install Salt Minion:
The minion needs the same repository as the master β add it before installing, and keep both on the same Salt release branch.
# Add the official Salt Project repository
curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.repo \
| sudo tee /etc/yum.repos.d/salt.repo
sudo dnf clean expire-cache
# Install the minion
dnf install -y salt-minion
Configure the minion:
vim /etc/salt/minion
# Add these lines:
master: 192.168.1.10
id: web-server
Start and enable Salt Minion:
systemctl enable --now salt-minion
systemctl status salt-minion
Step 3: Accept Minion Key
Back on the Salt Master:
# List unaccepted keys
salt-key -L
# Should show:
# Accepted Keys:
# Denied Keys:
# Unaccepted Keys:
# web-server
# Rejected Keys:
# Accept the minion key
salt-key -a web-server
# Verify
salt-key -L
# Should now show web-server under "Accepted Keys"
Step 4: Test Connection
Test connectivity between master and minion:
# Test ping
salt 'web-server' test.ping
# Output: web-server: True
# Check minion's OS
salt 'web-server' grains.get os
# Output: web-server: AlmaLinux
# Run a command
salt 'web-server' cmd.run 'hostname'
# Output: web-server: web-server
# Get system info
salt 'web-server' grains.items
Success! Your master and minion are communicating.
Step 5: Create Your First State
Now let’s create a simple state to install and configure nginx.
Create the state directory:
mkdir -p /srv/salt/nginx
Create the state file:
vim /srv/salt/nginx/init.sls
Add this content:
# Install nginx
nginx:
pkg.installed:
- name: nginx
# Ensure nginx is running
nginx-service:
service.running:
- name: nginx
- enable: True
- require:
- pkg: nginx
# Create a simple index page
/usr/share/nginx/html/index.html:
file.managed:
- contents: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Salt-Managed Server</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
h1 { font-size: 48px; margin-bottom: 20px; }
p { font-size: 24px; }
</style>
</head>
<body>
<h1>π Server Managed by SaltStack</h1>
<p>Hostname: {{ grains['id'] }}</p>
<p>OS: {{ grains['os'] }} {{ grains['osrelease'] }}</p>
<p>Configured automatically with Salt</p>
</body>
</html>
- mode: "0644"
- user: root
- group: root
- template: jinja
- require:
- pkg: nginx
# Configure firewall to allow HTTP
http-firewall:
firewalld.service:
- name: http
- enable: True
Create top file (tells Salt which states to apply to which minions):
vim /srv/salt/top.sls
Add:
base:
"web-server":
- nginx
Step 6: Apply the State
# Test what would change (dry run)
salt 'web-server' state.apply test=True
# Apply the state
salt 'web-server' state.apply
# Output shows:
# - nginx package installed
# - nginx service started and enabled
# - index.html file created
# - firewall configured
Step 7: Verify
Open your browser and navigate to:
http://192.168.1.11
You should see your custom welcome page showing the server is managed by Salt!
Verify from command line:
salt 'web-server' cmd.run 'systemctl status nginx'
salt 'web-server' cmd.run 'curl -s localhost | grep SaltStack'
Understanding What Happened
Let’s break down the state file:
1. Package Management
nginx:
pkg.installed:
- name: nginx
- Uses Salt’s
pkgmodule - Ensures nginx package is installed
- Idempotent: runs only if not already installed
2. Service Management
nginx-service:
service.running:
- name: nginx
- enable: True
- require:
- pkg: nginx
- Ensures nginx service is running
- Enables it to start on boot
requirecreates dependency: service only starts after package installed
3. File Management
/usr/share/nginx/html/index.html:
file.managed:
- contents: |
...HTML content...
- template: jinja
- Creates/manages the index.html file
- Uses Jinja templating to inject grain values
- Sets proper permissions
4. Jinja Templating
Hostname: {{ grains['id'] }}
OS: {{ grains['os'] }} {{ grains['osrelease'] }}
- Dynamic content based on minion’s grains
- Can use variables, loops, conditionals
Working with Pillar Data
Let’s make our state more flexible using Pillar.
Create pillar directory:
mkdir -p /srv/pillar
Create pillar data:
vim /srv/pillar/nginx.sls
Add:
nginx:
port: 80
server_name: example.com
root: /usr/share/nginx/html
Create pillar top file:
vim /srv/pillar/top.sls
Add:
base:
"web-server":
- nginx
Update the state to use pillar:
vim /srv/salt/nginx/init.sls
Add nginx configuration:
# ... previous states ...
# Custom nginx configuration
/etc/nginx/conf.d/default.conf:
file.managed:
- source: salt://nginx/files/default.conf.j2
- template: jinja
- user: root
- group: root
- mode: "0644"
- require:
- pkg: nginx
# Reload nginx when config changes
nginx-reload:
cmd.run:
- name: nginx -t && systemctl reload nginx
- onchanges:
- file: /etc/nginx/conf.d/default.conf
Create template directory and file:
mkdir -p /srv/salt/nginx/files
vim /srv/salt/nginx/files/default.conf.j2
Add:
server {
listen {{ pillar['nginx']['port'] }};
server_name {{ pillar['nginx']['server_name'] }};
root {{ pillar['nginx']['root'] }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Apply the updated state:
# Refresh pillar
salt 'web-server' saltutil.refresh_pillar
# Apply state
salt 'web-server' state.apply nginx
Remote Execution Examples
Salt’s remote execution is powerful. Here are common tasks:
System Information
# Get hostname
salt '*' cmd.run 'hostname'
# Check disk usage
salt '*' disk.usage
# Get network interfaces
salt '*' network.interfaces
# Check memory
salt '*' status.meminfo
Package Management
# Install a package
salt '*' pkg.install vim
# Update all packages
salt '*' pkg.upgrade
# Remove a package
salt '*' pkg.remove apache2
Service Management
# Restart a service
salt '*' service.restart nginx
# Check service status
salt '*' service.status nginx
# Enable service on boot
salt '*' service.enable nginx
User Management
# Create a user
salt '*' user.add john
# Add user to group
salt '*' user.chgroups john groups=wheel append=True
# Remove a user
salt '*' user.delete john
File Operations
# Create a directory
salt '*' file.mkdir /opt/myapp
# Copy a file
salt '*' cp.get_file salt://files/config.txt /etc/myapp/config.txt
# Check if file exists
salt '*' file.file_exists /etc/nginx/nginx.conf
# Set file permissions
salt '*' file.set_mode /etc/myapp/config.txt 0600
Targeting Minions
Salt offers flexible targeting options:
Glob Matching (Default)
salt 'web-*' test.ping # All minions starting with 'web-'
salt '*-prod' test.ping # All production servers
salt 'web-[1-3]' test.ping # web-1, web-2, web-3
List Matching
salt -L 'web-1,web-2,db-1' test.ping
Grain Matching
salt -G 'os:AlmaLinux' test.ping
salt -G 'osrelease:10' test.ping
salt -G 'roles:web' test.ping
Compound Matching
salt -C 'G@os:AlmaLinux and web-*' test.ping
salt -C 'G@roles:web or G@roles:api' test.ping
Pillar Matching
salt -I 'environment:prod' test.ping
Best Practices
1. Use Version Control
Always keep your Salt states in Git:
cd /srv/salt
git init
git add .
git commit -m "Initial Salt configuration"
2. Test Before Applying
Always use test=True first:
salt 'web-server' state.apply test=True
3. Use Pillar for Sensitive Data
Never hardcode passwords in states:
# Bad
mysql_root_password: 'secret123'
# Good - use pillar
mysql_root_password: {{ pillar['mysql']['root_password'] }}
4. Keep States Idempotent
States should be safe to run multiple times:
# Good - checks if already installed
nginx: pkg.installed
# Bad - would fail if already installed
install-nginx:
cmd.run:
- name: dnf install -y nginx
5. Use Meaningful State IDs
# Good
nginx-package:
pkg.installed:
- name: nginx
# Bad
install1:
pkg.installed:
- name: nginx
6. Document Your States
# Configure nginx web server
# This state:
# - Installs nginx
# - Configures virtual host
# - Manages SSL certificates
nginx:
pkg.installed:
- name: nginx
7. Use Requisites
Create proper dependencies:
nginx-config:
file.managed:
- name: /etc/nginx/nginx.conf
- require:
- pkg: nginx # File only created after package installed
- watch_in:
- service: nginx # Service reloads if config changes
Troubleshooting
Minion Not Connecting
Check minion configuration:
cat /etc/salt/minion | grep master
Check network connectivity:
nc -zv <master-ip> 4505
nc -zv <master-ip> 4506
Check minion logs:
journalctl -u salt-minion -f
Restart minion:
systemctl restart salt-minion
State Apply Fails
Run in debug mode:
salt 'web-server' state.apply -l debug
Check state syntax:
salt 'web-server' state.show_sls nginx
Verify pillar data:
salt 'web-server' pillar.items
Keys Not Accepting
Delete and re-add key:
salt-key -d web-server
# On minion: rm -rf /etc/salt/pki/minion/minion.pem*
# On minion: systemctl restart salt-minion
salt-key -a web-server
Next Steps
Now that you understand the basics:
Explore More State Modules
file,pkg,service,user,cron,mount- Check: https://docs.saltproject.io/en/latest/ref/states/all/
Learn Orchestration
- Coordinate multi-step deployments
- Use Salt’s orchestration runner
Try Reactor System
- React to events automatically
- Self-healing infrastructure
Integrate with Cloud Providers
- AWS, Azure, GCP cloud modules
- Provision and configure VMs automatically
Explore GitFS
- Store states in Git repositories
- Automatic deployment from version control
Conclusion
You’ve learned:
- β What SaltStack is and why it’s useful
- β Core concepts: States, Pillar, Grains
- β How to set up master and minions
- β Creating and applying states
- β Remote execution
- β Targeting minions
- β Best practices
SaltStack is a powerful tool that can transform how you manage infrastructure. Start small, practice with simple states, and gradually build more complex configurations.
Resources
- Official Documentation: https://docs.saltproject.io
- Salt Formulas (pre-built states): https://github.com/saltstack-formulas
- Community: https://saltproject.io/community/
- Salt User Groups: https://www.meetup.com/pro/salt-user-groups/
Happy automating! π
Published on Citizix.com - Your source for DevOps and infrastructure automation guides.