Configuration 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/