123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- .. _best-practices:
- ============================
- Salt :index:`Best Practices`
- ============================
- Salt's extreme flexibility leads to many questions concerning the structure of
- configuration files.
- This document exists to clarify these points through examples and
- code.
- General rules
- -------------
- 1. Modularity and clarity should be emphasized whenever possible.
- 2. Create clear relations between pillars and states.
- 3. Use variables when it makes sense but don't overuse them.
- 4. Store sensitive data in pillar.
- 5. Don't use grains for matching in your pillar top file for any sensitive
- pillars.
- Structuring States and Formulas
- -------------------------------
- When structuring Salt States and Formulas it is important to begin with the
- directory structure. A proper directory structure clearly defines the
- functionality of each state to the user via visual inspection of the state's
- name.
- Reviewing the :formula_url:`MySQL Salt Formula <mysql-formula>`
- it is clear to see the benefits to the end-user when reviewing a sample of the
- available states:
- .. code-block:: bash
- /srv/salt/mysql/files/
- /srv/salt/mysql/client.sls
- /srv/salt/mysql/map.jinja
- /srv/salt/mysql/python.sls
- /srv/salt/mysql/server.sls
- This directory structure would lead to these states being referenced in a top
- file in the following way:
- .. code-block:: yaml
- base:
- 'web*':
- - mysql.client
- - mysql.python
- 'db*':
- - mysql.server
- This clear definition ensures that the user is properly informed of what each
- state will do.
- Another example comes from the :formula_url:`vim-formula`:
- .. code-block:: bash
- /srv/salt/vim/files/
- /srv/salt/vim/absent.sls
- /srv/salt/vim/init.sls
- /srv/salt/vim/map.jinja
- /srv/salt/vim/nerdtree.sls
- /srv/salt/vim/pyflakes.sls
- /srv/salt/vim/salt.sls
- Once again viewing how this would look in a top file:
- /srv/salt/top.sls:
- .. code-block:: yaml
- base:
- 'web*':
- - vim
- - vim.nerdtree
- - vim.pyflakes
- - vim.salt
- 'db*':
- - vim.absent
- The usage of a clear top-level directory as well as properly named states
- reduces the overall complexity and leads a user to both understand what will
- be included at a glance and where it is located.
- In addition :ref:`Formulas <conventions-formula>` should
- be used as often as possible.
- .. note::
- Formulas repositories on the saltstack-formulas GitHub organization should
- not be pointed to directly from systems that automatically fetch new
- updates such as GitFS or similar tooling. Instead formulas repositories
- should be forked on GitHub or cloned locally, where unintended, automatic
- changes will not take place.
- Structuring Pillar Files
- ------------------------
- :ref:`Pillars <pillar>` are used to store
- secure and insecure data pertaining to minions. When designing the structure
- of the ``/srv/pillar`` directory, the pillars contained within
- should once again be focused on clear and concise data which users can easily
- review, modify, and understand.
- The ``/srv/pillar/`` directory is primarily controlled by ``top.sls``. It
- should be noted that the pillar ``top.sls`` is not used as a location to
- declare variables and their values. The ``top.sls`` is used as a way to
- include other pillar files and organize the way they are matched based on
- environments or grains.
- An example ``top.sls`` may be as simple as the following:
- /srv/pillar/top.sls:
- .. code-block:: yaml
- base:
- '*':
- - packages
- Any number of matchers can be added to the base environment. For example, here
- is an expanded version of the Pillar top file stated above:
- /srv/pillar/top.sls:
- .. code-block:: yaml
- base:
- '*':
- - packages
- 'web*':
- - apache
- - vim
- Or an even more complicated example, using a variety of matchers in numerous
- environments:
- /srv/pillar/top.sls:
- .. code-block:: yaml
- base:
- '*':
- - apache
- dev:
- 'os:Debian':
- - match: grain
- - vim
- test:
- '* and not G@os: Debian':
- - match: compound
- - emacs
- It is clear to see through these examples how the top file provides users with
- power but when used incorrectly it can lead to confusing configurations. This
- is why it is important to understand that the top file for pillar is not used
- for variable definitions.
- Each SLS file within the ``/srv/pillar/`` directory should correspond to the
- states which it matches.
- This would mean that the ``apache`` pillar file should contain data relevant to
- Apache. Structuring files in this way once again ensures modularity, and
- creates a consistent understanding throughout our Salt environment. Users can
- expect that pillar variables found in an Apache state will live inside of an
- Apache pillar:
- ``/srv/pillar/apache.sls``:
- .. code-block:: yaml
- apache:
- lookup:
- name: httpd
- config:
- tmpl: /etc/httpd/httpd.conf
- While this pillar file is simple, it shows how a pillar file explicitly
- relates to the state it is associated with.
- Variable Flexibility
- --------------------
- Salt allows users to define variables in SLS files. When creating a state
- variables should provide users with as much flexibility as possible. This
- means that variables should be clearly defined and easy to manipulate, and
- that sane defaults should exist in the event a variable is not properly
- defined. Looking at several examples shows how these different items can
- lead to extensive flexibility.
- Although it is possible to set variables locally, this is generally not
- preferred:
- ``/srv/salt/apache/conf.sls``:
- .. code-block:: jinja
- {% set name = 'httpd' %}
- {% set tmpl = 'salt://apache/files/httpd.conf' %}
- include:
- - apache
- apache_conf:
- file.managed:
- - name: {{ name }}
- - source: {{ tmpl }}
- - template: jinja
- - user: root
- - watch_in:
- - service: apache
- When generating this information it can be easily transitioned to the pillar
- where data can be overwritten, modified, and applied to multiple states, or
- locations within a single state:
- ``/srv/pillar/apache.sls``:
- .. code-block:: yaml
- apache:
- lookup:
- name: httpd
- config:
- tmpl: salt://apache/files/httpd.conf
- ``/srv/salt/apache/conf.sls``:
- .. code-block:: jinja
- {% from "apache/map.jinja" import apache with context %}
- include:
- - apache
- apache_conf:
- file.managed:
- - name: {{ salt['pillar.get']('apache:lookup:name') }}
- - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }}
- - template: jinja
- - user: root
- - watch_in:
- - service: apache
- This flexibility provides users with a centralized location to modify
- variables, which is extremely important as an environment grows.
- Modularity Within States
- ------------------------
- Ensuring that states are modular is one of the key concepts to understand
- within Salt. When creating a state a user must consider how many times the
- state could be re-used, and what it relies on to operate. Below are several
- examples which will iteratively explain how a user can go from a state which
- is not very modular to one that is:
- ``/srv/salt/apache/init.sls``:
- .. code-block:: yaml
- httpd:
- pkg:
- - installed
- service.running:
- - enable: True
- /etc/httpd/httpd.conf:
- file.managed:
- - source: salt://apache/files/httpd.conf
- - template: jinja
- - watch_in:
- - service: httpd
- The example above is probably the worst-case scenario when writing a state.
- There is a clear lack of focus by naming both the pkg/service, and managed
- file directly as the state ID. This would lead to changing multiple requires
- within this state, as well as others that may depend upon the state.
- Imagine if a require was used for the ``httpd`` package in another state, and
- then suddenly it's a custom package. Now changes need to be made in multiple
- locations which increases the complexity and leads to a more error prone
- configuration.
- There is also the issue of having the configuration file located in the init,
- as a user would be unable to simply install the service and use the default
- conf file.
- Our second revision begins to address the referencing by using ``- name``, as
- opposed to direct ID references:
- ``/srv/salt/apache/init.sls``:
- .. code-block:: yaml
- apache:
- pkg.installed:
- - name: httpd
- service.running:
- - name: httpd
- - enable: True
- apache_conf:
- file.managed:
- - name: /etc/httpd/httpd.conf
- - source: salt://apache/files/httpd.conf
- - template: jinja
- - watch_in:
- - service: apache
- The above init file is better than our original, yet it has several issues
- which lead to a lack of modularity. The first of these problems is the usage
- of static values for items such as the name of the service, the name of the
- managed file, and the source of the managed file. When these items are hard
- coded they become difficult to modify and the opportunity to make mistakes
- arises. It also leads to multiple edits that need to occur when changing
- these items (imagine if there were dozens of these occurrences throughout the
- state!). There is also still the concern of the configuration file data living
- in the same state as the service and package.
- In the next example steps will be taken to begin addressing these issues.
- Starting with the addition of a map.jinja file (as noted in the
- :ref:`Formula documentation <conventions-formula>`), and
- modification of static values:
- ``/srv/salt/apache/map.jinja``:
- .. code-block:: jinja
- {% set apache = salt['grains.filter_by']({
- 'Debian': {
- 'server': 'apache2',
- 'service': 'apache2',
- 'conf': '/etc/apache2/apache.conf',
- },
- 'RedHat': {
- 'server': 'httpd',
- 'service': 'httpd',
- 'conf': '/etc/httpd/httpd.conf',
- },
- }, merge=salt['pillar.get']('apache:lookup')) %}
- /srv/pillar/apache.sls:
- .. code-block:: yaml
- apache:
- lookup:
- config:
- tmpl: salt://apache/files/httpd.conf
- ``/srv/salt/apache/init.sls``:
- .. code-block:: jinja
- {% from "apache/map.jinja" import apache with context %}
- apache:
- pkg.installed:
- - name: {{ apache.server }}
- service.running:
- - name: {{ apache.service }}
- - enable: True
- apache_conf:
- file.managed:
- - name: {{ apache.conf }}
- - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }}
- - template: jinja
- - user: root
- - watch_in:
- - service: apache
- The changes to this state now allow us to easily identify the location of the
- variables, as well as ensuring they are flexible and easy to modify.
- While this takes another step in the right direction, it is not yet complete.
- Suppose the user did not want to use the provided conf file, or even their own
- configuration file, but the default apache conf. With the current state setup
- this is not possible. To attain this level of modularity this state will need
- to be broken into two states.
- ``/srv/salt/apache/map.jinja``:
- .. code-block:: jinja
- {% set apache = salt['grains.filter_by']({
- 'Debian': {
- 'server': 'apache2',
- 'service': 'apache2',
- 'conf': '/etc/apache2/apache.conf',
- },
- 'RedHat': {
- 'server': 'httpd',
- 'service': 'httpd',
- 'conf': '/etc/httpd/httpd.conf',
- },
- }, merge=salt['pillar.get']('apache:lookup')) %}
- ``/srv/pillar/apache.sls``:
- .. code-block:: yaml
- apache:
- lookup:
- config:
- tmpl: salt://apache/files/httpd.conf
- ``/srv/salt/apache/init.sls``:
- .. code-block:: jinja
- {% from "apache/map.jinja" import apache with context %}
- apache:
- pkg.installed:
- - name: {{ apache.server }}
- service.running:
- - name: {{ apache.service }}
- - enable: True
- ``/srv/salt/apache/conf.sls``:
- .. code-block:: jinja
- {% from "apache/map.jinja" import apache with context %}
- include:
- - apache
- apache_conf:
- file.managed:
- - name: {{ apache.conf }}
- - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }}
- - template: jinja
- - user: root
- - watch_in:
- - service: apache
- This new structure now allows users to choose whether they only wish to
- install the default Apache, or if they wish, overwrite the default package,
- service, configuration file location, or the configuration file itself. In
- addition to this the data has been broken between multiple files allowing for
- users to identify where they need to change the associated data.
- Storing Secure Data
- -------------------
- Secure data refers to any information that you would not wish to share with
- anyone accessing a server. This could include data such as passwords,
- keys, or other information.
- As all data within a state is accessible by EVERY server that is connected
- it is important to store secure data within pillar. This will ensure that only
- those servers which require this secure data have access to it. In this
- example a use can go from an insecure configuration to one which is only
- accessible by the appropriate hosts:
- ``/srv/salt/mysql/testerdb.sls``:
- .. code-block:: yaml
- testdb:
- mysql_database.present:
- - name: testerdb
- ``/srv/salt/mysql/user.sls``:
- .. code-block:: yaml
- include:
- - mysql.testerdb
- testdb_user:
- mysql_user.present:
- - name: frank
- - password: "test3rdb"
- - host: localhost
- - require:
- - sls: mysql.testerdb
- Many users would review this state and see that the password is there in plain
- text, which is quite problematic. It results in several issues which may not
- be immediately visible.
- The first of these issues is clear to most users -- the password being visible
- in this state. This means that any minion will have a copy of this, and
- therefore the password which is a major security concern as minions may not
- be locked down as tightly as the master server.
- The other issue that can be encountered is access by users on the master. If
- everyone has access to the states (or their repository), then they are able to
- review this password. Keeping your password data accessible by only a few
- users is critical for both security and peace of mind.
- There is also the issue of portability. When a state is configured this way
- it results in multiple changes needing to be made. This was discussed in the
- sections above but it is a critical idea to drive home. If states are not
- portable it may result in more work later!
- Fixing this issue is relatively simple, the content just needs to be moved to
- the associated pillar:
- ``/srv/pillar/mysql.sls``:
- .. code-block:: yaml
- mysql:
- lookup:
- name: testerdb
- password: test3rdb
- user: frank
- host: localhost
- ``/srv/salt/mysql/testerdb.sls``:
- .. code-block:: jinja
- testdb:
- mysql_database.present:
- - name: {{ salt['pillar.get']('mysql:lookup:name') }}
- ``/srv/salt/mysql/user.sls``:
- .. code-block:: jinja
- include:
- - mysql.testerdb
- testdb_user:
- mysql_user.present:
- - name: {{ salt['pillar.get']('mysql:lookup:user') }}
- - password: {{ salt['pillar.get']('mysql:lookup:password') }}
- - host: {{ salt['pillar.get']('mysql:lookup:host') }}
- - require:
- - sls: mysql.testerdb
- Now that the database details have been moved to the associated pillar file,
- only machines which are targeted via pillar will have access to these details.
- Access to users who should not be able to review these details can also be
- prevented while ensuring that they are still able to write states which take
- advantage of this information.
|