http://habrahabr.ru/post/253443/
О чем эта статья?
Знакомство с возможностями SaltStack по созданию конфигураций сервисов зависимых друг от друга; от сервисов расположенных на других или всех остальных подчиненных системах и т. д. Если проще — рассмотрим как каждая подчиненная система может получить данные с других таких же систем в момент создания и распространения своих конфигураций.
Это третья статься в серии о SaltStack, первую читайте
здесь, вторую —
тут.
Задача: развернуть кластер какого-то сервиса, так что-бы в конфигурациях нод были ссылки на все остальные ноды этого кластера
Самое тривиальное задание для автоматизации — сделать много нод с предварительно описанной конфигурацией сервисов, разместить эти конфигурации и получить профит. Во многих гайдах, в том числе и по SaltStack, описаны такие стандартные случаи, потому останавливать внимание на них не будем. Все это прекрасно до тех пор пока эти ноды не должны знать о состоянии и параметрах других нод входящих в процесс автоматизации.
Рассмотрим самый простой вариант описанной задачи, — записать в файл /etc/hosts на каждой ноде адреса и имена всех остальных нод в кластере.
Как решить это с помощью SaltStack? Самое простое, что приходит в голову — записать имена и адреса всех нод в pillar файл и подключить его ко всем стейтам всех нод:
pillar/cluster-nodes.slscluster-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.slsbase:
'*':
- cluster-nodes
Создадим стейт для внесения этих данных в /etc/hosts. Для этого будем использовать
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 %}
После применения стейта получим на всех нодах примерно вот такой
/etc/hosts:
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
Казалось бы — цель достигнута — со всех хостов видны другие. Такого рода решение хоть и не элегантное, т.к. придется делать правки в pillar файл при добавлении новых нод к кластеру, но имеет право на существование в тех случаях когда имена и адреса всех нод заведомо известны и фиксированы.
А что делать если каждая из нод получает свой адрес, к примеру, по DHCP? Или генерацией нод занимается какой-нибудь IaaS провайдер типа Amazon EC2, GoGrid или Google Grid? Там ни адресов ни имен нод заранее нет и за фиксированные адреса придется еще и доплачивать.
(Лирическое отступление — в скором будущем напишу статью о том, как создать с помощью SaltStack свою инфраструктуру в EC2).
В принципе, после установки миниона на ноду, информацию о его имени и адресе можно получить пользуя
salt-grains.
К примеру, получить эти данные можно на минионе так:
#salt-call grains.item fqdn fqdn_ip4
local:
----------
fqdn:
ip-10-6-0-150.ec2.internal
fqdn_ip4:
- 10.6.0.150
Или в описании стейта:
{% set host_name = salt['grains.get']('fqdn') %}
{% set host_ip = salt['grains.get']('fqdn_ip4') %}
Все бы ничего — но есть одно существенное
но: это все доступно на мастере и не доступно на отдельных минионах. То есть в момент генерации конфигураций на каждом из минионов видны данные самого миниона, кое-что из мастера и совсем ничего об остальных минионах.
Вот тут и встает вопрос — как же получить данные с других минионов при генерации конфигураций конкретного миниона?
Ответ прост: для этого можно использовать
salt-mine. В документации написано не много, но достаточно, чтобы понять общее назначение этого сервиса. Как он работает? Мастер кеширует с некоторой периодичностью определенный набор данных с каждого из минионов и предоставляет доступ к этим закешированным данным всем минионам.
Как это реализовать?
1. Задаем mine_functions — описание функций для получения и кеширования отдельных данных с минионов. Могут быть определены с помощью непосредственного описания в /etc/salt/minion на каждом из минионов, или подключением pillar файла с описанием этих функций на мастере для каждого из минионов.
2. Либо ждем некоторое время (mine_interval сек. — можно указать в конфигурации миниона) либо форсим обновление руками с помощью
salt '*' mine.update
3. Пользуем ф-цию mine.get для получения необходимых данных с мастера при конфигурировании текущего миниона.
Рассмотрим как решить задачу с хостами при помощи описанных шагов. Итак:
1. Создаем запись в pillar файл и подключаем его ко всем минионам:
pillar/minefuncs.slsmine_functions:
grains.item: [fqdn, fqdn_ip4]
pillar/top.slsbase:
'*':
- minefuncs
2. Форсим сбор данных с минионов.
3. Создаем стейт для
/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 %}
В итоге мы получим автоматически сгенерированный хостс файл содержащий имена и адреса всех минионов.
Если есть необходимость вычленять конкретный набор минионов для которых будет применяться стейт (к примеру у одних нод кластера одна роль, у других — другая и надо вычленить только ноды с определенными ролями) можно воспользоваться следующими рекомендациями:
1. Для всех минионов определяем
custom grain (это несложно и описано в стандартной документации), к примеру: grains:roles:name_node и grains:roles:data_node.
2. Сделать выборку в
mine.get по указанным ролям. К примеру вот так:
{% 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 %}
В этих случаях стоит обратить внимание на
expr_form и описание выражения для выборки.
Вот, собственно, и есть решение задачи описанной выше — с тем, чтобы была возможность получать конфигурационные данные с минионов.
С помощью описанных техник можно также передавать различные данные между минионами в процессе генерации их конфигураций.
Надеюсь статейка будет полезна всем, кто использует SaltStack в достаточно нетривиальных конфигурациях.
Спасибо за чтение.