SaltStack: Создание зависимых или ссылающихся конфигураций сервисов

от автора


О чем эта статья?

Знакомство с возможностями SaltStack по созданию конфигураций сервисов зависимых друг от друга; от сервисов расположенных на других или всех остальных подчиненных системах и т. д. Если проще — рассмотрим как каждая подчиненная система может получить данные с других таких же систем в момент создания и распространения своих конфигураций.

Это третья статься в серии о SaltStack, первую читайте здесь, вторую — тут.

Задача: развернуть кластер какого-то сервиса, так что-бы в конфигурациях нод были ссылки на все остальные ноды этого кластера

Самое тривиальное задание для автоматизации — сделать много нод с предварительно описанной конфигурацией сервисов, разместить эти конфигурации и получить профит. Во многих гайдах, в том числе и по SaltStack, описаны такие стандартные случаи, потому останавливать внимание на них не будем. Все это прекрасно до тех пор пока эти ноды не должны знать о состоянии и параметрах других нод входящих в процесс автоматизации.

Рассмотрим самый простой вариант описанной задачи, — записать в файл /etc/hosts на каждой ноде адреса и имена всех остальных нод в кластере.

Как решить это с помощью SaltStack? Самое простое, что приходит в голову — записать имена и адреса всех нод в pillar файл и подключить его ко всем стейтам всех нод:

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 

Создадим стейт для внесения этих данных в /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.sls

mine_functions:   grains.item: [fqdn, fqdn_ip4] 

pillar/top.sls

base:   '*':     - 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 в достаточно нетривиальных конфигурациях.

Спасибо за чтение.

ссылка на оригинал статьи http://habrahabr.ru/post/253443/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *