DevOps инженер; Автоматизатор; Архитектор.
0,0
рейтинг
19 марта 2015 в 11:53

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

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


Знакомство с возможностями 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 в достаточно нетривиальных конфигурациях.

Спасибо за чтение.
Евгений Чепурный @eugenechepurniy
карма
18,0
рейтинг 0,0
DevOps инженер; Автоматизатор; Архитектор.

Самое читаемое Разработка

Комментарии (12)

  • 0
    А как вы определяете роль? Предсозданием файла /etc/salt/grains на миньенах?
    • 0
      Можно так, как Вы указали — непосредственно в момент бутстраппинга миниона;
      можно создать отдельный стейт для этого файла (/etc/salt/grains) с перезапуском миниона.
      В моем случае, это все делает salt-cloud при автоматизированном создании инстансов в Amazon EC2.
  • 0
    Это нормальная статья новичка в SaltStaсk, давайте не минусовать, пожалейте человека)
  • +1
    Я вот поставлю огромный и жирный плюс. Ну наболело, простите. У нас пропали специалисты, которые что-то внедряют. У нас ( простите за мой француский ) дохуя пиздоделов. Это статья человека, который реально с нуля сделал. Спасибо.
    • 0
      Спасибо за признательность. Действительно — почти все приходится делать с самого нуля т.к. реально работающих примеров и техник не так уже и много найдется среди гигабайтов сырых перепечаток документации и туториалов.
  • 0
    А тег Python? Где он здесь, Вы его как-то применяли?
  • 0
    Вполне может показаться, что его (я про Python) тут и нету, но, как говорят, дьявол кроется в мелочах: любое описание стейта для SaltStack — микс YAML/Jinja2/Python funcs — что неразрывно связанно с языком Python. Конечно же, в этом материале не будет описаний революционных методик написания кода на питоне, но связь в любом случае присутствует.
  • 0
    Скажите, пожалуйста, есть где-нибудь production-level пример использования SaltStack? Интересно, во что всё это впоследствии вырастает.
  • 0
    Судя по живому интересу сетевого сообщества к последним разработкам в области систем управления и автоматизации (я имею ввиду SaltStack и Ansible — как флагманы, также недавно переработанный Chef) таки есть удачные внедрения и, думается, достаточно много.
    Из своего личного опыта могу сказать, что пределов применения подобных систем почти нету, к примеру, — сейчас я использую SaltStack для создания тестовых окружений для приложения работающего с BigData провайдерами (Apache Hadoop, Horton Works, MapR) с применением автоматизированного создания этих окружений в публичных/приватных облачных провайдерах (Amazon, GoGrid, OpenStack). Если слышали что-то про указанные технологии, то понимаете, сколько административной работы пришлось бы затратить на создание этого «добра» в ручном режиме. С помощью грамотно созданных автоматизаций этот процесс занимает 20-30 минут для кластера из 10 машин.
    В любом случае — каждый выбирает для себя что автоматизировать. Можно даже, банально накладывать security патчи на 100 серверов одновременно и в этом уже большой профит от таких систем.
  • 0
    А не пробовали использовать что-нибудь из service discovery штук для подобной задачи, типа etcd/serf/consul? Вроде они как раз для этого, поднялась машина, serf приаттачился к кластеру, остальные получили нотификацию и подправили свои конфиги. Сам вот сейчас думаю над подобной задачей в ansible и прихожу к тому что надо попробовать service discovery разверуть.
    • 0
      Идея с consul звучит прекрасно и очень подходит для уже существующих систем которым необходимы модификации в живую инфраструктуру. Конкретно в моем случае — все создается «с нуля» и в уже подготовленной конфигурации — т.е. масштабирование не входит в задачу для SaltStack. В любом случае — использовать или нет service discovery очень специфичный вопрос во многом зависящий от того, какую инфраструктуру оно будет обслуживать. К примеру, CHD (одна из реализаций стека Apache Hadoop от Cloudera) управляет своим кластером не требуя внешних приложений.
  • 0
    ну наконец начали писать про salt =)
    несколько вопросов:
    а как вы поддерживайте top.sls… динамическая генерация например?
    используйте ли шину… например для мониторинга, статистики? кастомное тэгирование и отправка например в splunk.
    есть ли какие-нибудь связки с cmdb… например хранить state by host и маппить через pillar?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.