Ambiguous term in salt documentation, overused to describe any piece of salt logic.
The *.sls
files contains state definitions, this is just the data file (YAML by default) the Salt translates into State Module execution
Core Modules
Execution Modules
salt/modules
Custom: salt://_modules
The salt
or salt-call
command executes exactly the Execution Modules
Contrary to State Modules, Execution Modules don't check any state, they just perform action.
salt '*' cmd.run "ls /" cwd=/
cmd
- the module name, the module name is either the salt/modules/cmd.py
or salt/modules/any_name.py
containing __virtualname__ = "cmd"
run
- the actual function definition, everything after function definition is the args and kwargs (for the actual function or for the salt state compiler)
"ls /"
- first positional argument of run function
cwd=/
- keyword argument
State Modules
salt/states
Custom: salt://_states
Enforces desired states on the remote. Under the hood they usually use Execution Modules
pkgs_pip:
pip.installed:
- name: pip_packages
- pkgs:
- google-auth
- reload_modules: True
pkgs_pip
- state ID
pip
- State Module name, maps directly to either salt/states/pip.py
or salt/states/any_name.py
containing __virtualname__ = "pip"
installed
- the actual function name, naming convention: past tense.
- name: pip_packages
the first positional argument, by convention called name
. When the name
is not passed the state ID is used as the name
Ordering states
Salt supports two ordering modes:
1. Definition order: in the order of appearance in top.sls
file, thus it is the filename that determines order. Then the states are executed
by the order of appearance in the state file itself. The include
d files that were defined after including file are executed first. inculde
takes precedence.
2. Lexicographic order: states are sorted by: their name, function and then by state ID. Enable with configuration option: state_auto_order: False
, the include
statement in sls
files
doesn't affect the order at all.
Both options respect the requisite statements.
The requisites take precedence over configured ordering.
Full list of requisite statements and their usage.
If multiple states happen to be assigned same order number (the internal number that formally determines execution order), then Salt fallbacks to Lexicographic ordering.
Evaluating states
By default sls files are written using in YAML format with Jinja templates. Both engines are not aware of each other. Jinja templating starts before validating YAML.
In other words: 1. Jinja must produce valid YAML file 2. YAML file must be a valid highstate data structure 3. Highstate is compiled to lowstate (lowstate is a sorted list of state executions) 4. Salt executes the list in the order
The full evaluation and execution order:
Jinja -> YAML -> highstate -> lowstate -> execution
It is very easy to misuse jinja. When the state starts to be unreadable, it is possible candidate to switch to different
renderer (usually #!py
).
However user desired logic may be too complex still. Then writing custom Execution Module or State Module is a better idea.
Additionally as the State Tree grows, it is easy to fall into following trap (depicted by example):
clone_repo:
git.latest:
- name: https://github.com/kiemlicz/util
- target: /tmp/util/
- branch: master
{% for f in '/tmp/util/' | list_files %}
{% if f == '/tmp/util/README.md' %}
add_developer_{{ f }}:
file.append:
- name: {{ f }}
- text: "added contributor: bla@o2.pl"
- require:
- git: clone_repo
{% endif %}
{% endfor %}
User assumed that the jinja will 'see' clone_repo
changes. It is not true.
Jinja is evaluated first, thus when this sls file is applied first time, effectively the YAML looks like:
clone_repo:
git.latest:
- name: https://github.com/kiemlicz/util
- target: /tmp/util/
- branch: master
However during next run, the jinja will 'see' the changes (as they are already applied). Thus the output YAML will become:
clone_repo:
git.latest:
- name: https://github.com/kiemlicz/util
- target: /tmp/util/
- branch: master
add_developer_/tmp/util/README.md:
file.append:
- name: /tmp/util/README.md
- text: "added contributor: bla@o2.pl"
- require:
- git: clone_repo
There are couple of options how to overcome such situation, most common involve: - writing custom Execution Module or State Module - using Slots (if you know what you are doing)
Slots
Relatively new Salt feature, allows to store the result of Execution Module and use it in next Modules (during same run).
Example:
dnsutils:
pkg.latest:
- name: dnsutils
find_domain: # works
cmd.run:
- name: "nslookup google.com"
the_state_id: # fails
test.show_notification:
- name: some name
- text: "the server ip: {{ salt['cmd.run']("nslookup google.com") }}"
Will fail because not-yet-existing command output is used as part of state definition.
Using slots we can overcome this:
dnsutils:
pkg.latest:
- name: dnsutils
find_domain: # works
cmd.run:
- name: "nslookup google.com"
the_state_id: # works
test.show_notification:
- name: some name
- text: __slot__:salt:cmd.run("nslookup google.com")
Idempotence
It is tempting to wonder: is the state execution idempotent?
Is depends, e.g., the states like:
run_scripts:
cmd.script:
- name: do_dangerous_stuff.sh
are not guaranteed to be idempotent because everything depends on underlying: do_dangerous_stuff.sh
script, so in general it is wise
to assume that such state is not idempotent.
Moreover adding jinja constructs that modify underlying system also doesn't help (not advised though).
However Salt provides requisite constructs that can be added to (not all) states: onlyif
or unless
Thus it is possible to make Salt states idempotent
Best practices
Check these two documents, they provide excellent details about how to create state properly: - best practices - formulas best practices
Data Modules
Contains runtime configuration, variables, secret data... data...
Data\Authority | Salt Master | Salt Minion | Other |
---|---|---|---|
Secrets | Pillar | SDB | SDB |
Config | Pillar | Grains | SDB |
Grains Modules
salt/grains
Custom: salt://_grains
Minion specific data, can be also specified in configuration file grains: {}
, or set by master.
Grains are refreshed on a very limited basis and are largely static data. If there is some minion specific data that
needs to be updated on the master then the Salt Mine is the place to go.
Pillar Modules
salt/pillar
Custom: salt://_pillar
Salt Master is authoritative over pillar data. Pushes pillar to minions that cache it. Minion may request pillar data on its own.
SDB Modules
salt/sdb
Custom: salt://_sdb
Used when neither Salt Master nor Salt Minion is authoritative over data. It could be used to pull secrets from HashiCorp Vault or other keystores. If it is Salt Minion that makes the call to sdb it calls directly the third party entity. It is possible to use SDB modules in the Salt Master/Minion config files:
client_id: sdb://module_name/secret_client_id
However it's impossible to bootstrap Salt with custom SDB modules used in config already.
During the Salt Minion/Master startup the full config is read and parsed, thus any sdb://<profile>/key
are evaluated
Use of custom SDB modules requires preceding: sync_sdb
, which doesn't happen during initial bootstrap
Event Modules and Reactor System
Salt Master and Salt Minion have their own event buses. Depending on the Module's function used to fire event, event may or may not be propagated to other event buses (e.g. from Minion to Master and vice-versa).
Event always comprises of two things: 1. event tag 2. data dictionary
Salt event system that uses Event Modules is described in separate section
Beacon Modules
salt/beacons
Custom: salt://_beacons
Way to notify the master about anything. Works like a probe/sensor, e.g. disk is going full.
Notifications use the Salt Minion's event bus and are propagated to Salt Master.
Internally beacons work in the following way:
- minion's scheduler starts the beacon module's beacon
function
- the function fetches desired data in the most lightweight way possible
- data is forwarded to Salt Minion event bus
Queue Modules
salt/queues
Custom: n/a
Helps to handle the events, sometimes it is desirable to enqueue incoming events and pop
them sequentially instead of allowing asynchronous reactions to happen.
Engine Modules
salt/engines
Custom: salt://_engines
Can be run on Salt Master or Salt Minion, once started: runs forever in separate process. Commonly used to integrate
with external systems (like sending the notifications to slack) or fetching the external systems data under the Salt infrastructure.
Thorium Modules - experimental
salt/thorium
Custom: salt://_thorium
Primarily created to add event aggregation, requires additional configuration.
Sometimes it is desirable to start a reaction once the set of N
minions complete their highstate
logic, not
every time each of the minions completes. This can be achieved with Thorium.
Example of thorium state file that fires the event only when two salt/custom/event
are received:
something:
reg.list:
- add: "some_field"
- match: 'salt/custom/event'
check.len_eq:
- value: 2
send_when:
runner.cmd:
- func: event.send
- arg:
- thor/works
- require:
- check: something
Result Modules
TODO
Output Modules
Result Modules
Admin Modules
Wheel Modules
salt/wheel
Custom: n/a
Dealing with Salt infrastructure itself, e.g., accept Salt Minion key.
Runner Modules
salt/runners
Custom: salt://_runners
(runner_dirs
configuration option)
Used exclusively by salt-run
command. They are pure Salt Master Modules, designed to run on master only.
Cache Modules
salt/cache
Custom: salt://_cache
Netapi Modules
salt/netapi Modules that expose Salt API, require configuration to enable
Python client API
This is not the part of Salt's netapi
, but a library for interaction with Salt
Same that is used by Salt commands
Integration Modules
TODO
Utility Modules
TODO