SaltStack: Installing a Salt Master on Ubuntu 14.04

saltstack_logo-thumbnailConfiguration Management tools like SaltStack are invaluable for managing infrastructure at scale.  Even in the growing world of containerization where immutable image deployment is the norm, those images need to be built in a repeatable and auditable fashion.

This article will detail installation of the SaltStack master on Ubuntu 14.04, with validation using a single Minion.  Note that Minion installation is not mandatory if using Salt SSH.

Installation

The first step is to add the SaltStack repository and key:

$ wget -qO - https://repo.saltstack.com/apt/ubuntu/14.04/amd64/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add -

$ sudo apt-get install apt-transport-https ca-certificates -y

$ echo "deb http://repo.saltstack.com/apt/ubuntu/14.04/amd64/latest $(lsb_release -cs) main" | sudo tee -a /etc/apt/sources.list.d/saltstack.list

Then refresh the repository and install the SaltStack master:

$ sudo apt-get update
$ sudo apt-cache policy salt-master
$ sudo apt-get install salt-master -y

Make sure the firewall allows communication to the SaltStack ports and create default directories to support the state and pillar files:

$ sudo ufw allow 4505:4506/tcp
$ sudo mkdir -p /srv/salt
$ sudo mkdir -p /srv/pillar
$ sudo service salt-master restart

The logs are at ‘/var/log/salt/master’ and the main configuration file is at ‘/etc/salt/master’.

Test Minion

In order to validate the SaltStack Master, we will install a Minion on the same server.

$ sudo apt-get install salt-minion -y

Then we will need to modify the configuration at ‘/etc/salt/minion’, so that it points to the master.  Find the line that starts with ‘master:’ and point it at localhost (or the name/IP address of listening master)

master: localhost

Then restart the minion:

$ sudo service salt-minion restart

Now, even though the Salt minion has projected its identity to the master, we need to have the master accept the key for security.  List the minions that are waiting for acceptance:

$ sudo salt-key -L

Accepted Keys:
 Denied Keys:
 Unaccepted Keys:
 trusty1
 Rejected Keys:

You can see that my minion is named ‘trusty1’ (which is the hostname).  We now need to accept the key, and after we do that, we can run a few quick commands to validate it is working properly.

$ sudo salt-key -y -a trusty1

$ sudo salt 'trusty1' test.version

$ sudo salt 'trust1' cmd.run 'ls /tmp'

Validate States and Pillars

Now we will create a set of state and pillar files for a quick test.  If you need to read more about the concept of Pillars for defining variable data and State files for applying logic, then see the “Salt in 10 minutes” walkthrough.

Remember, my minion’s name is ‘trusty1’, but you will need to adjust this to target your own minion’s name.

Create the following pillar files in the Salt Master’s default directories:

# /srv/pillar/top.sls
base:
  'trusty1':
     - test1
# /srv/pillar/test1.sls
test1:
  lookup:
    mystringdata: this is for test1
    mydictdata:
      key1: dictvalue1
      key2: dictvalue2
    mylistdata:
      - item1
      - item2

Then the following state files:

# /srv/salt/top.sls
base:
  'trusty1':
     - test1
# /srv/salt/test1.sls
test1-task1:
  cmd.run:
    - name: echo this is test1.task1


# show string pillar data
test1-pillartest-string:
  cmd.run:
    - name: echo {{ salt['pillar.get']('test1:lookup:mystringdata') }}

# show list pillar data
{% for value in salt['pillar.get']('test1:lookup:mylistdata') %}
test1-pillartest-list-{{value}}:
  cmd.run:
    - name: echo from the list, {{ value }}
{% endfor %}

# show dictionary pillar data
{% for key,value in salt['pillar.get']('test1:lookup:mydictdata').items() %}
test1-pillartest-dict-{{key}}:
  cmd.run:
    - name: echo from the dictionary, {{ key }} is {{ value }}
{% endfor %}

Now, when you check the pillar data for this minion, you should see output like below:

$ sudo salt 'trusty1' pillar.items

trusty1:
    ----------
    test1:
        ----------
        lookup:
            ----------
            mydictdata:
                ----------
                key1:
                    dictvalue1
                key2:
                    dictvalue2
            mylistdata:
                - item1
                - item2
            mystringdata:
                this is for test1

And when you apply the highstate to this minion you should see something like below:

$ sudo salt 'trusty1' state.apply

trusty1:
----------
          ID: test1-task1
    Function: cmd.run
        Name: echo this is test1.task1
      Result: True
     Comment: Command "echo this is test1.task1" run
     Started: 15:35:29.107767
    Duration: 14.613 ms
     Changes:   
              ----------
              pid:
                  10131
              retcode:
                  0
              stderr:
              stdout:
                  this is test1.task1
----------
          ID: test1-pillartest-string
    Function: cmd.run
        Name: echo this is for test1
      Result: True
     Comment: Command "echo this is for test1" run
     Started: 15:35:29.122487
    Duration: 2.173 ms
     Changes:   
              ----------
              pid:
                  10132
              retcode:
                  0
              stderr:
              stdout:
                  this is for test1
----------
          ID: test1-pillartest-list-item1
    Function: cmd.run
        Name: echo from the list, item1
      Result: True
     Comment: Command "echo from the list, item1" run
     Started: 15:35:29.124748
    Duration: 2.184 ms
     Changes:   
              ----------
              pid:
                  10133
              retcode:
                  0
              stderr:
              stdout:
                  from the list, item1
----------
          ID: test1-pillartest-list-item2
    Function: cmd.run
        Name: echo from the list, item2
      Result: True
     Comment: Command "echo from the list, item2" run
     Started: 15:35:29.127023
    Duration: 6.268 ms
     Changes:   
              ----------
              pid:
                  10134
              retcode:
                  0
              stderr:
              stdout:
                  from the list, item2
----------
          ID: test1-pillartest-dict-key2
    Function: cmd.run
        Name: echo from the dictionary, key2 is dictvalue2
      Result: True
     Comment: Command "echo from the dictionary, key2 is dictvalue2" run
     Started: 15:35:29.133386
    Duration: 2.296 ms
     Changes:   
              ----------
              pid:
                  10135
              retcode:
                  0
              stderr:
              stdout:
                  from the dictionary, key2 is dictvalue2
----------
          ID: test1-pillartest-dict-key1
    Function: cmd.run
        Name: echo from the dictionary, key1 is dictvalue1
      Result: True
     Comment: Command "echo from the dictionary, key1 is dictvalue1" run
     Started: 15:35:29.135772
    Duration: 4.81 ms
     Changes:   
              ----------
              pid:
                  10136
              retcode:
                  0
              stderr:
              stdout:
                  from the dictionary, key1 is dictvalue1

Summary for trusty1
------------
Succeeded: 6 (changed=6)
Failed:    0
------------
Total states run:     6
Total run time:  32.344 ms

 

 

 

REFERENCES

https://repo.saltstack.com/#ubuntu

https://fabianlee.org/2016/10/31/saltstack-running-a-masterless-minion-on-ubuntu/