Handling the dependency graph

In configuration management, execution order is important. You can't run apache if apache isn't installed. You can't write to a file if the directory doesn't exist. Successful configuration management requires careful management of the execution order of different commands.

By default, SaltStack compiles the requisite (dependency) graph by reading state options such as require and watch. The advantage of using these state options is that SaltStack is able to safely execute multiple unconnected states in parallel rather than executing everything in serial. We'll discuss how to setup the dependency graph to be able to rely on your states being applied in the proper and correct order.

In our quickstart, we setup the example of installing and starting apache2.

apache2:
  pkg:
    - installed
  service:
    - running
    - require:
      - pkg: apache2

While we didn't discuss the require option in-depth, we used it to ensure that the pkg: apache2 is installed before the apache2 service could be set to running. Visually, this looks like:

Apache2 dependency graph

In order to run the the service: apache2, the apache2 package must be installed.

Note that the way we are setting up the requisite is by using the key of pkg and the value of apache2. This is called requisite matching. requisite matching basically matches on the ID and the name.

This sets the graph to relate one state to another state. This is important to note because it requires two things to work:

You must have a state defined to require another state

In our example, we have two states, a pkg and a service. Broken out, our state from above looks like:

apache2_package:
  pkg.installed:
    - name: apache2

apache2_service:
  service.running:
    - require:
      - pkg: apache2_package

In order for the apache2_service to set a requisite, it must be able to refer to another state entirely, as the states above clearly show.

One state type exists per state declaration

The dependency graph requires that one type of state refers to another state declaration. If there are multiple types of the same type, then SaltStack will not know which state to set in the dependency graph. For example, the following is invalid:

# THIS IS INVALID SYNTAX
apache2_package:
  pkg.installed: 
    - name: apache2
  pkg.installed:
    - name: apache2-tools

SaltStack will respect this order if it's explicitly set as an order.

This is called direct requisites where we explicitly set the requirement inside the state. Salt enables a second way for states to be set in the dependency graph through setting up requisites in.

To set a requisite in, use the option: require_in. For instance, the above example could be rewritten with the same dependency graph as:

apache2_package:
  pkg.installed:
    - name: apache2
    - require_in:
      - service: apache2_service

apache2_service:
  service.running:
    - name: apache2

require_in sets up a similar graph, in the reverse order:

Apache2 dependency graph

The order of appearance does not set a dependency graph.

Unlike other tools for configuration management, the order in which the state appears in the file does not set an order in which the states run.

Watches

To restart a service, or act upon action inside of SaltStack, SaltStack provides the watch option. The watch option sets establishes a dependency graph, just the same as require does, but it also runs the state if and only if the state that it points to has made any changes to the system.

Watches are used to restart services or cause actions only if another state actions succeeds. For instance, if we look back at the second example in our quickstart, we have the apache2 config being set by SaltStack:

apache2:
  pkg:
    - installed
  service:
    - running
    - require:
      - pkg: apache2
  file.managed:
    - name: /etc/apache2/apache2.conf
    - source: salt://apache2.conf
    - template: jinja
    - user: apache2
    - group: apache2
    - mode: 644
    - require:
      - user: apache2

What happens in the case that we want to restart the apache2 service if we make a chance to the apache2 config? Currently, the service apache2 will not be restarted (or reloaded). To set the service to restart, we simply need to apply the watch option. The service then can be changed to look like:

service.running:
  - require:
    - pkg: apache2
  - watch:
    - file: apache2

Using the requisite matching syntax, we can simply add the watch option and whenever file: apache2 is seen to change the system, the service.running will be triggered.

An example of when you'd want to use a watch would be if you were going to download a file and need to untar it. You obviously cannot untar a file that doesn't exist on the system, so you have to wait for the file to exist on the system, but you don't want to run untar everytime you run a highstate.

Using a watch will fix this for us:

get-nginx:
  pkg.installed:
    - names:
      - libpcre3-dev
      - build-essential
      - libssl-dev
  file.managed:
    - name: /usr/local/src/nginx.tar.gz
    - source: http://nginx.org/download/nginx-1.5.2.tar.gz
    - source_hash: sha1=3546be28a72251f8823ab6be6a1180d300d06f76
  cmd.wait:
    - cwd: /usr/local/src
    - name: tar -zxf /usr/local/src/nginx.tar.gz -C /usr/local/nginx
    - require:
      - pkg: get-nginx
    - watch:
      - file: get-nginx

Similar to require, watch has a watch_in option. The watch_in option works just like the require_in in that we can tell another state that it requires the containing state.

Prereq

The prereq requisite allows for actions to be taken based off the results of a state that has not yet been executed. This is a complex way of saying that a state will only run if the specified state expects to change the underlying system and that it will be run before the state requiring the prereq.

An example of this is the case in which we want to deploy new site code. We don't want to stop the apache service if we aren't going to deploy new code and we want to ensure that apache has been gracefully shutdown before we deploy the new code:

graceful-down:
  cmd.run:
    - name: service apache graceful
    - prereq:
      - file: site-code

site-code:
  file.recurse:
    - name: /opt/site_code
    - source: salt://site/code

Just like the require_in and watch_in, prereq_in allows us to specify in advance that we will need to require a prereq in another state.

Manual Ordering

Although by default SaltStack encourages the use of requisite declarations to determine execution order, it is also possible to specify execution order using the order keyword. When this keyword is used, commands are executed in order according to the number specified in the order command. Multiple commands with the same order number will be executed with the same priority level. The order: last option will forcibly set a state to execute after all other states set with or without the order option.

Notes: If a requisite declaration is also used, it will override an order option. Any states without the order option will be executed after states specifying the order option.

The order keyword can be useful for state files where you want to execute lots of commands in a row:

nginx-install:
  cmd.run:
    - name: apt-get install nginx
    - order: 1

nginx-delete-config:
  cmd.run:
    - name: rm -rf /etc/nginx/conf.d/
    - order: 2

nginx-create-log-dir:
  cmd.run:
    - name: mkdir /var/log/nginx
    - order: 3

nginx-set-conf-dir-permissions:
  cmd.run:
    - name: chown -R nginx:nginx /etc/nginx
    - order: 4

nginx-set-log-dir-permissions:
  cmd.run:
    - name: chown -R nginx:nginx /var/log/nginx
    - order: 4

nginx-start:
  cmd.run:
    - name: service nginx start
    - order: last
Your email is required. Your email is required to be at least 3 characters That is not a valid email. Please input a valid email.

Comments

comments powered by Disqus