Using states for configuration management
The files inside the /srv/salt/
directory define the Salt states. This is a configuration management format that enforces the state that a minion will be in: package X
needs to be installed, file Y
needs to look a certain way, service Z
needs to be enabled and running, and so on. For example:
apache2: pkg: - installed service: - running file: - name: /etc/apache2/apache2.conf
States may be saved in a single SLS file, but it is far better to separate them into multiple files, in a way that makes sense to you and your organization. SLS files can use include blocks that pull in other SLS files.
Using include blocks
In a large SLS tree, it often becomes reasonable to have SLS files include other SLS files. This is done using an include
block, which usually appears at the top of an SLS file:
include: - base - emacs
In this example, the SLS file in question will replace the include block with the contents of base.sls
(or base/init.sls
) and emacs.sls
(or emacs/init.sls
). This imposes some important restrictions on the user. Most importantly, the SLS files that are included may not contain IDs that already exist in the SLS file that includes them.
It is also important to remember that include
itself, being a top-level declaration, cannot exist twice in the same file. The following is invalid:
include: - base include: - emacs
Ordering with requisites
State SLS files are unique among configuration management formats in that they are both declarative and imperative. They are imperative, as each state will be evaluated in the order in which it appears in the SLS file. They are also declarative because states may include requisites that change the order in which they are actually executed. For instance:
web_service: service.running: - name: apache2 - require: - pkg: web_package web_package: pkg.installed: - name: apache2
If a service is declared, which requires a package that appears after it in the SLS file, the pkg
states will be executed first. However, if no requirements are declared, Salt will attempt to start the service before installing the package, because its codeblock appears before the pkg
codeblock. The following will require two executions to complete properly:
web_service: service.running: - name: apache2 web_package: pkg.installed: - name: apache2
Requisites point to a list of items elsewhere in the SLS file that affect the behavior of the state. Each item in the list contains two components: the name of the module and the ID of the state being referenced.
The following requisites are available inside Salt states and other areas of Salt that use the state compiler.
require
The require
requisite is the most basic; it dictates that the state that it is declared in is not executed until every item in the list that has been defined for it has executed successfully. Consider the following example:
apache2: pkg: - installed - require - file: apache2 service: - running - require: - pkg: apache2 file: - managed - name: /etc/apache2/apache2.conf - source: salt://apache2/apache2.conf
In this example, a file will be copied to the minion first, then a package installed, then the service started. Obviously, the service cannot be started until the package that provides it is installed. But Debian-based operating systems such as Ubuntu automatically start services the moment they're installed, which can be problematic if the default configuration files aren't correct. This state will ensure that Apache is properly configured before it is even installed.
watch
In the preceding example, a new minion will be properly configured the first time. However, if the configuration file changes, the apache2
service will need to be restarted. Adding a watch
requisite to the service will force that state to perform a specific action when the state that it is watching reports changes.
apache2: ...SNIP... service: - running - require: - pkg: apache2 - watch: - file: apache2 ...SNIP...
The watch
requisite is not available for every type of state module. This is because it performs a specific action, depending on the type of module. For instance, when a service is triggered with a watch
, Salt will attempt to start a service that is stopped. If it is already running, it will attempt either a reload: True
, service.full_restart
, or service.restart
, as appropriate.
As of version 2016.3, the following states modules support using the watch
requisite: service, pkg
, cmd
, event
, module
, mount
, supervisord
, docker
, dockerng, etcd
, tomcat
, and test
.
onchanges
The onchanges
requisite is similar to watch
, except that it does not require any special support from the state module that is using it. If changes happen, which should only occur when a state completes successfully, then the list of items referred to with onchanges
will be evaluated.
onfail
In a simple state tree, the onfail
requisite is less commonly used. However, a more advanced state tree, which is written to attempt alerting the user, or to perform auto-correcting measures, can make use of onfail
. When a state is evaluated and fails to execute correctly, every item listed under onfail
will be evaluated. Assuming that the PagerDuty
service is properly configured via Salt and an apache_failure
state has been written to use it, the following state can notify the operations team if Apache fails to start:
apache2: service: - running - onfail - pagerduty: apache_failure
use
It is possible to declare default values in one state and then inherit them into another state. This typically occurs when one state file has an include
statement that refers to another file.
If an item in the state that is being used has been redeclared, it will be overwritten with the new value. Otherwise, the item that is being used will appear unchanged. Requisites will not be inherited with use
; only non-requisite options will be inherited. Therefore, in the following SLS, the mysql_conf
state will safely inherit the user
, group
, and mode from the apache2_conf
state, without also triggering Apache restarts:
apache2_conf: file: - managed - name: /etc/apache2/apache2.conf - user: root - group: root - mode: 755 - watch_in: - service: apache2 mysql_conf: file: - managed - name: /etc/mysql/my.cnf - use: - file: apache2_conf - watch_in: - service: mysql
prereq
There are some situations in which a state does not need to run, unless another state is expected to make changes. For example, consider a web application that makes use of Apache. When the codebase on a production server changes, Apache should be turned off, so as to avoid errors with the code that has not yet finished being installed.
The prereq
requisite was designed exactly for this kind of use. When a state makes use of prereq
, Salt will first perform a test run of the state to see if the items referred to in the prereq
are expected to make changes. If so, then Salt will flag the state with the prereq
as needing to execute.
apache2: service: - running - watch: - file: codebase codebase: file: - recurse ...SNIP... shutdown_apache: service: - dead - name: apache2 - prereq: - file: codebase
In the preceding example, the shutdown_apache
state will only make changes if the codebase
state reports that changes need to be made. If they do, then Apache will shutdown, and then the codebase
state will execute. Once it is finished, it will trigger the apache2
service state, which will start up Apache again.
Inverting requisites
Each of the aforementioned requisites can be used inversely, by adding _in
at the end. For instance, rather than state X requiring state Y, an SLS can be written so that state X declares that it is required by state Y, as follows:
apache2: pkg: - installed - require_in: - service: apache2 service: - running
It may seem silly to add inverses of each of the states but there is in fact a very good use case for doing so: include blocks.
SLS files cannot use requisites that point to a code that does not exist inside them. However, using an include block will cause the contents of other SLS files to appear inside the SLS file. Therefore, generic (but valid) configuration can be defined in one SLS file, included in another, and modified to be more specific with a use_in
requisite.
Extending SLS files
In addition to an include block, state SLS files can also contain an extend
block that modifies SLS files that appear in the include block. Using an extend
block is similar to a use requisite, but there are some important differences.
Whereas a use
or use_in
requisite will copy defaults to or from another state, the extend block will only modify the state that has been extended.
# cat /srv/generic_apache/init.sls apache2_conf: file: - managed - name: /etc/apache2/apache2.conf - source: salt://apache2/apache2.conf (In django_server/init.sls) include: - generic_apache extend: apache2_conf: file: - source: salt://django/apache2.conf (In image_server/init.sls) include: - generic_apache extend: apache2_conf: file: - source: salt://django/apache2.conf
The preceding example makes use of a generic Apache configuration file, which will be overridden as appropriate for either a Django server or a web server that is only serving images.