📜 ⬆️ ⬇️

SaltStack: Creating dependent or referencing service configurations

What is this article about?


Familiarity with the ability of SaltStack to create configurations of services dependent on each other; from services located on other or all other subordinate systems, etc. If it is simpler, consider how each subordinate system can receive data from other similar systems at the time of creating and distributing its configurations.

This is the third article in the SaltStack series, read the first one here , the second one here .

Task: deploy a cluster of a service, so that, in node configurations, there are links to all the other nodes of this cluster


The most trivial task for automation is to make many nodes with the previously described configuration of services, place these configurations and get profit. In many guides, including on SaltStack, such standard cases are described, therefore we will not stop attention on them. All this is fine as long as these nodes do not need to know about the status and parameters of other nodes that are part of the automation process.

Consider the simplest version of the described task, - write to the / etc / hosts file on each node the addresses and the names of all the other nodes in the cluster.
')
How to solve this with SaltStack? The simplest thing that comes to mind is to write down the names and addresses of all the nodes in the pillar file and connect it to all the states of all the nodes:

pillar / cluster-nodes.sls
cluster-nodes: node1: name: node1.domain.com ip: 10.0.0.1 node2: name: node2.domain.com ip: 10.0.0.2 node3: name: node3.domain.com ip: 10.0.0.3 ....... nodeN: name: nodeN.domain.com ip: 10.0.0.N 


pillar / top.sls
 base: '*': - cluster-nodes 


Create a state to enter this data into / etc / hosts. For this we will use salt.states.host .

states / cluster-nodes.sls
 {% for node in salt['pillar.get']('cluster-nodes', []) %} cluster-node-{{node['name']}}: host.present: - ip: {{node['ip']}} - names: - {{node['name']}} - {{node['name'].split('.')[0]}} {% endfor %} 


After applying the state, we’ll get something like / etc / hosts on all the nodes:

 127.0.0.1 localhost 10.0.0.1 node1.domain.com node1 10.0.0.2 node2.domain.com node2 10.0.0.3 node3.domain.com node3 ......... 10.0.0.N nodeN.domain.com nodeN 

It would seem that - the goal has been achieved - others are visible from all hosts. This kind of solution, though not elegant, because You will have to make edits to the pillar file when adding new nodes to the cluster, but it has a right to exist in cases where the names and addresses of all the nodes are known and fixed.

What to do if each node receives its address, for example, via DHCP? Or is a IaaS provider like Amazon EC2, GoGrid or Google Grid doing the generation of the nodes? There are no addresses or node names in advance, and you will have to pay extra for fixed addresses.

(Lyrical digression - in the near future I will write an article about how to create your infrastructure in EC2 using SaltStack) .

In principle, after installing the minion on the node, information about its name and address can be obtained using salt-grains .

For example, you can get this data on the minion as follows:

 #salt-call grains.item fqdn fqdn_ip4 local: ---------- fqdn: ip-10-6-0-150.ec2.internal fqdn_ip4: - 10.6.0.150 


Or in the description of the state:

 {% set host_name = salt['grains.get']('fqdn') %} {% set host_ip = salt['grains.get']('fqdn_ip4') %} 

Everything would be fine - but there is one significant thing: it is all available on the master and not available on separate minions. That is, at the moment of generation of configurations, on each of the minions the data of the minion itself, some of the wizard and nothing at all about the other minions are visible.

This is where the question arises - how to get data from other minions when generating configurations of a particular minion?

The answer is simple: you can use salt-mine for this. Not much has been written in the documentation, but enough to understand the general purpose of this service. How does he work? The wizard caches a certain set of data from each of the minions with some periodicity and provides access to these cached data to all minions.

How to implement it?
1. We set mine_functions - the description of functions for obtaining and caching individual data from minions. They can be defined using a direct description in / etc / salt / minion on each of the minions, or by connecting a pillar file with a description of these functions on the wizard for each of the minions.
2. Either wait for some time (mine_interval seconds - you can specify in the configuration of the minion) or force update using the hands using salt '*' mine.update
3. Use the mine.get function to get the necessary data from the wizard when configuring the current minion.

Consider how to solve the problem with the hosts using the steps described. So:

1. Create an entry in the pillar file and connect it to all minions:

pillar / minefuncs.sls
 mine_functions: grains.item: [fqdn, fqdn_ip4] 


pillar / top.sls
 base: '*': - minefuncs 


2. Forcing the collection of data from the minions.

3. Create a state for / etc / hosts :

 {% for node, fqdn_data in salt['mine.get']('*', 'grains.item', expr_form='glob').items() %} cluster-node-{{fqdn_data['fqdn']}}: host.present: - names: - {{fqdn_data['fqdn'].split('.')[0]}} - {{fqdn_data['fqdn']}} - ip: {{fqdn_data['fqdn_ip4'][0]}} {% endfor %} 

As a result, we will get an automatically generated host file containing the names and addresses of all the minions.

If there is a need to isolate a specific set of minions for which the state will be applied (for example, one cluster has one role, others have another, and only nodes with certain roles need to be identified) you can use the following recommendations:

1. For all minions, we define custom grain (this is easy and described in standard documentation), for example: grains: roles: name_node and grains: roles: data_node.
2. Make a selection in mine.get for the specified roles. For example, like this:

 {% for node, fqdn_data in salt['mine.get']('roles:data_node', 'grains.item', expr_form='grain').items() %} cluster-node-{{fqdn_data['fqdn']}}: host.present: - names: - {{fqdn_data['fqdn'].split('.')[0]}} - {{fqdn_data['fqdn']}} - ip: {{fqdn_data['fqdn_ip4'][0]}} {% endfor %} {% for node, fqdn_data in salt['mine.get']('* and not G@roles:data_node', 'grains.item', expr_form='compound').items() %} cluster-node-{{fqdn_data['fqdn']}}: host.present: - names: - {{fqdn_data['fqdn'].split('.')[0]}} - {{fqdn_data['fqdn']}} - ip: {{fqdn_data['fqdn_ip4'][0]}} {% endfor %} 

In these cases, you should pay attention to expr_form and the description of the expression for the sample.

Here, in fact, is the solution to the problem described above - in order to be able to receive configuration data from the minions.

Using the described techniques, you can also transfer various data between minions in the process of generating their configurations.

I hope the article will be useful to everyone who uses SaltStack in fairly non-trivial configurations.

Thank you for reading.

Source: https://habr.com/ru/post/253443/


All Articles