Pillar#

Pillar use case#

Salt pillar brings data into the cluster from the opposite direction as grains. While grains data is generated from the minion, the pillar is data generated from the master.

Pillars are organized similarly to states in a Pillar state tree, where top.sls acts to coordinate pillar data to environments and minions with access to the data.

Highly sensitive data:

  • Information transferred via pillar has a dictionary generated for the targeted minion and encrypted with that minion’s key for secure data transfer. This makes pillar suitable for managing security information, such as cryptographic keys and passwords.

  • Configuration data for applications can be stored in pillar, and the data is passed to execution modules to determine how to configure the application.

  • Pillar data is encrypted on a per-minion basis. This encryption makes it useful for storing sensitive data specific to a particular minion.

Minion configuration:

  • Minion modules such as the execution modules, states, and returners can often be configured via data stored in pillar.

  • Pillar data is targeted to minions using a pillar top file.

Note

By default, the contents of the master configuration file are not loaded into pillar for all minions, but this behavior is configured with the pillar_opts: False option in the master configuration. If enabled, this allows the master configuration file to be used for global configuration of minions.

Basics#

Pillar SLS files can contain any type of YAML structure:

/srv/pillar/top.sls#
 base:
   '*':
     - example
/srv/pillar/example.sls#
 pillar1: value
 pillar2:
   - value
   - value
 pillar3:
   sub_key:
     - value
     - value

A few things to note about pillar structures:

  • Pillars can be defined in pillar state files.

  • Pillar data must be serialized data structures such as strings, lists, and dictionaries.

  • Pillar data is typically defined and represented in YAML.

Rendering pillar#

To view pillar data, use the pillar execution module. This module includes several functions, each with their own use. These functions include:

pillar.item

Retrieves the value of one or more keys from the in-memory pillar data.

pillar.items

Compiles and returns a fresh pillar dictionary, leaving the in-memory pillar data untouched. If pillar keys are passed to this function however, this function acts like pillar.item and returns their values from the in-memory pillar data.

pillar.raw

Like pillar.items, it returns the entire pillar dictionary, but from the in-memory pillar data instead of compiling fresh pillar data.

pillar.get

Described in detail below.

The pillar.get function works much in the same way as the get method in a Python dictionary, with the addional feature that nested dictionaries can be traversed using a colon as a delimiter.

If a structure like this is in pillar:

foo:
  bar:
    baz: qux

Extracting it from the raw pillar in an SLS formula or file template is done this way:

{{ pillar['foo']['bar']['baz'] }}

Now, with the new pillar.get function the data can be safely gathered and a default can be set, allowing the template to fall back if the value is not available. This makes handling nested structures much easier.

{{ salt['pillar.get']('foo:bar:baz', 'fallback value') }}

Note

On pillar.get() vs salt['pillar.get']():

Note that within templating, the pillar variable is just a dictionary. This means that calling pillar.get() inside of a template will just use the default dictionary .get() function which does not include the extra colon delimiter functionality. It must be called using the above syntax (salt['pillar.get']('foo:bar:baz', 'qux')) to get the Salt function, instead of the default dictionary behavior.

Configuration settings#

The configuration for the pillar_roots in the master configuration is identical in behavior and function as the file_roots configuration:

/etc/salt/master.d/pillar.conf#
 pillar_roots:
   base:
     - /srv/pillar

This example configuration declares that the base environment will be located in the /srv/pillar directory.

A few things to note about the pillar environment:

  • The Salt Master server maintains a pillar_roots setup that matches the structure of the file_roots used in the Salt file server.

  • Similar to the Salt file server, the pillar_roots option in the master configuration is based on environments mapping to directories.

  • The pillar data is then mapped to minions based on matchers in a top file which is laid out in the same way as the state top file.

  • Salt pillars can use the same matcher types as the standard top file, except matching on pillar.

In-memory vs. on-demand data#

Since compiling pillar data is computationally expensive, the minion will maintain a copy of the pillar data in memory. This avoids needing to ask the master to recompile and send the minion a copy of the pillar data each time it is requested. This in-memory pillar data is what is returned by the pillar.item, pillar.get, and pillar.raw functions.

Also, for those writing custom execution modules, or contributing to Salt’s existing execution modules, the in-memory pillar data is available as the __pillar__ dunder dictionary.

The in-memory pillar data is generated on minion start, and can be refreshed using the saltutil.refresh_pillar function:

salt \* saltutil.refresh_pillar

This function triggers the minion to asynchronously refresh the in-memory pillar data and will always return None.

In contrast to in-memory pillar data, certain actions trigger pillar data to be compiled to ensure that the most up-to-date pillar data is available. These actions include:

  • Running states

  • Running pillar.items

Performing these actions will not refresh the in-memory pillar data. So, if pillar data is modified, and then states are run, the states will see the updated pillar data. However, pillar.item, pillar.get, and pillar.raw will not see this data unless refreshed using saltutil.refresh_pillar.

External pillar#

Salt provides a mechanism for generating pillar data by calling external services for compatible data.

Salt will load any external pillar modules in the specified extension_modules directory as well as the modules installed with Salt by default.

With the directory set and code loaded for external pillar, the final step for implementation is to configure the master.

Set ext_pillar in /etc/salt/master.d/ext_pillar.conf:

ext_pillar:
  - example_a: some argument
  - example_b:
    - argumentA
    - argumentB
  - example_c:
      keyA: valueA
      keyB: valueB

Pillar namespace#

The separate pillar SLS files all merge down into a single dictionary of key:value pairs. Pillar files are applied in the order they are listed in the top file, so when there are conflicting keys, earlier ones will be overwritten. In the previous scenario, conflicting key values in services will overwrite those in packages because the service values are at the bottom of the list.

/srv/salt/pillar/top.sls#
 base:
   '*':
     - packages
     - services
/srv/salt/pillar/packages.sls#
 bind: bind9
/srv/salt/pillar/services.sls#
 bind: named

In this scenario, a request for the bind pillar key will only return named The bind9 value will be lost, because services.sls was evaluated later.

When working with extensive pillar data, structuring your pillar files with more hierarchy can avoid namespace collisions and more effectively map variables to jinja variables in the states. For example, you can rework a pillar file to nest any key:value to be unique:

/srv/salt/pillar/packages.sls#
 packages:
   bind: bind9

This now makes packages:bind key unique since it is nested, and won’t conflict with the services:bind key.

Pillar data merge#

If the same pillar key is defined in multiple pillar SLS files, and the keys in both files refer to nested dictionaries, then the content from these dictionaries will be recursively merged.

To demonstrating this, take the top.sls pillar structure and change the packages.sls and services.sls dictionary structure to have no nested key:value conflicts:

/srv/salt/pillar/top.sls#
 base:
   '*':
     - packages
     - services
/srv/salt/pillar/packages.sls#
 bind:
   package-name: bind9
   version: 9.9.5
/srv/salt/pillar/services.sls#
 bind:
   port: 53
   listen-on: any

The resulting pillar dictionary from the services.sls and packages.sls pillar union will be:

salt-call pillar.get bind
local:
 ----------
 listen-on:
     any
 package-name:
     bind9
 port:
     53
 version:
     9.9.5

Since both pillar SLS files contained a bind key which contained a nested dictionary, the pillar dictionary’s bind key contains the combined contents of both SLS files’ bind keys.

Include pillar#

Pillar SLS files may include other pillar files, similarly to state files. There are two syntax types to choose from: simple and full.

A simple include adds the additional pillar as if it were part of the same file:

include:
  - users

A full include allows two additional options:

  • Passing default values to the templating engine for the included pillar file.

  • Adding an optional key under which to nest the results of the included pillar.

include:
  - users:
      defaults:
        sudo: ['bob', 'paul']
      key: users

With this form, users.sls will be nested within the users key of the compiled pillar. Additionally, the sudo value will be available as a template variable to users.sls.

Pillar cache#

If there is an unacceptable delay in job publishing because of pillar render time, we can enable master side caching for pillar.

This option reduces job time, but will introduce disadvantages that need to be considered.

Master caching#

If the pillar rendering time is too slow, we can set pillar_cache: True. This creates a cache, either in memory or on the disk, to pull pillar data from, removing time for pillar render for each minion on every request:

/etc/salt/master.d/pillar.conf#
pillar_cache: True

Cache expiring#

The cache TTL controls the amount of time, in seconds, before the cache expires and pillar is recompiled in to a new cache:

/etc/salt/master.d/pillar.conf#
pillar_cache_ttl: 3600

Memory or disk#

When electing to use the cache, you can either set for disk or RAM memory storage:

/etc/salt/master.d/pillar.conf#
 # Value can be "disk" or "memory"
 # This example uses "disk"
 pillar_cache_backend: disk

disk:

  • The default storage backend.

  • Rendered pillars are serialized and deserialized as msgpack structures for speed.

Warning

This may represent a substantial security risk. Pillars are stored UNENCRYPTED. Ensure that the master cache has permissions set appropriately (sane defaults are provided).

memory:

  • Uses an in-memory Python data structure for maximal performance.

  • Each master worker contains its own in-memory cache

  • No guarantee of cache consistency between minion requests.

  • This works best in situations where the pillar rarely if ever changes.

Warning

This may represent a substantial security risk. These unencrypted pillars will be accessible to any process which can examine the memory of the salt-master.

Pillar environment#

When multiple pillar environments are used, the default behavior is for the pillar data from all environments to be merged together. The pillar dictionary will therefore contain keys from all configured environments.

The pillarenv minion config option can be used to force the minion to only consider pillar configuration from a single environment. This can be useful in cases where one needs to run states with alternate pillar data, either in a testing or QA environment, or to test changes to the pillar data before pushing them live.

For example, assume that the following is set in the minion config file:

pillarenv: base

This would cause that minion to ignore all other pillar environments besides base when compiling the in-memory pillar data. Then, when running states, the pillarenv CLI argument can be used to override the minion’s pillarenv config value:

salt \* state.apply mystates pillarenv=testing

The above command will run the states with pillar data sourced exclusively from the testing environment, without modifying the in-memory pillar data.

Note

When running states, the pillarenv CLI option does not require a pillarenv option to be set in the minion config file. When pillarenv is left unset, as mentioned above, all configured environments will be combined. Running states with pillarenv=testing in this case would still restrict the states’ pillar data to just that of the testing pillar environment.

It is possible to pin the pillarenv to the effective saltenv, using the pillarenv_from_saltenv minion config option. When this is set to True, if a specific saltenv is specified when running states, the pillarenv will be the same. This essentially makes the following two commands equivalent:

salt \* state.apply mystates saltenv=dev
salt \* state.apply mystates saltenv=dev pillarenv=dev

However, if a pillarenv is specified, it will override this behavior. So, the following command will use the qa pillar environment but source the SLS files from the dev saltenv:

salt \* state.apply mystates saltenv=dev pillarenv=qa

So, if a pillarenv is set in the minion config file, pillarenv_from_saltenv will be ignored, and passing a pillarenv on the CLI will temporarily override pillarenv_from_saltenv.

Jinja in pillar#

A simple example is to set up a mapping of package names in pillar for separate Linux distributions:

/srv/pillar/pkg/init.sls#
 pkgs:
   {% if grains['os_family'] == 'RedHat' %}
   apache: httpd
   vim: vim-enhanced
   {% elif grains['os_family'] == 'Debian' %}
   apache: apache2
   vim: vim
   {% elif grains['os'] == 'Arch' %}
   apache: apache
   vim: vim
   {% endif %}

States with pillar#

Consequently this data can be used from within modules, renderers, and State SLS files via the shared pillar dictionary:

apache:
  pkg.installed:
    - name: {{ pillar['apache'] }}
git:
  pkg.installed:
    - name: {{ pillar['git'] }}

Finally, the above states can use the values provided to them via pillar. All pillar values targeted to a minion are available via the pillar dictionary. As seen in the above example, Jinja substitutions can then be utilized to access the keys and values in the pillar dictionary.

Note that you cannot just list key/value-information in top.sls. Instead, target a minion to a pillar file and then list the keys and values in the pillar:

/srv/pillar/top.sls#
 base:
   '*':
     - updates
   'load-balancer-minion':
     - ibm-cloud-keys