Pull to refresh

SaltStack — Темная лошадка систем управления конфигурациями

Reading time 10 min
Views 22K

Вступление

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

Это мое личное мнение основанное на десятке лет работы с различными системами конфигураций и двухлетнего периода миграции 200+ ролей с Ansible на SaltStack.

Сотни статей уже было написано про сравнение таких систем как Puppet, Chef, Ansible и SaltStack. У каждой из них есть своя ниша и серебряной пули не существует. Эта статья не ставит целью сделать очередное сравнение, которое устареет через ~6 месяцев. Мне скорее хочется показать вам несколько фундаментальных отличий между этими системами.

Статья рассчитана на людей уже знакомых с системами контроля версий и может быть сложна в понимании для тех кто с ними не работал.

Я буду стараться использовать английский язык в именах и названиях и русский язык для всего остального.

Если вкратце, то SaltStack - это один из лучших инструментов для конфигурирования серверов и оркестрации не контейнеризованных приложений на сегодняшний день. SaltStack без особых усилий может обслуживать тысячи нод. При желании он может обслуживать десятки тысяч нод.

Я много работал с Puppet, Ansible и SaltStack, но сосредоточится я хочу на SaltStack как альтернативе для Ansible.

Главная причина для этого то что Ansible и SaltStack написаны на Python, когда Puppet и Chef написаны на Ruby. Язык инструмента казалось бы не должен иметь принципиального значения, но именно в этой сфере он очень важен.

Так же Ansible сейчас считается лидером рынка и на этот факт просто грустно смотреть.

Транспорт

Ключевым различием между Ansible и SaltStack является транспорт.

Ansible работает поверх протокола SSH и гордится этим.

Почему это важно?

Давайте посмотрим под капот коммуникации Ansible, для этого запустите ansible-playbook с аргументом -vvv в консоли вы увидите вот такие куски логики:

ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=600s -o StrictHostKeyChecking=no -o Port=22 -o 'IdentityFile="/tmp/elasticsearch-salt-withmaster-current/ssh_key"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=centos -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o ControlPath=/home/jenkins/.ansible/cp/%h-%p-%r 172.31.23.3 '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-lpecwxhszeyfmvzzvfgbqaxgyctivtwe; PATH='"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:$PATH'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"' /usr/bin/env python'"'"'"'"'"'"'"'"' && sleep 0'"'"''
[CUT]
scp -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=600s -o StrictHostKeyChecking=no -o Port=22 -o 'IdentityFile="/tmp/elasticsearch-salt-withmaster-current/ssh_key"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=behavox -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o ControlPath=/home/jenkins/.ansible/cp/%h-%p-%r /ws/roles/common_user_env/files/screenrc '[172.31.18.215]:/opt/behavox/.ansible/tmp/ansible-tmp-1573069165.36-50102547946249/source'

"Во-первых это красиво!", точнее ужасно.

Во-вторых это очень медленно и неэффективно.

Ansible вынужден совершать множество операций на каждое действие, таких как:

  • Проверить ssh подключение и права

  • Собрать факты об удаленной системе, такие как информация об ОС, hardware, network и тд.

  • Скопировать Python модули и сопутствующие файлы

  • Выполнить Python модули и получить результат

  • Очистить временные файлы за собой

И так плюс-минус на каждую операцию. Маркетологи Ansible называют это "radically simple IT automation platform" так как он работает без специального транспорта и без удаленного агента, то есть готов к работе буквально сразу. И скорость работы - это цена за эту простоту.

Давайте теперь посмотрим на SaltStack.

Создатель SaltStack, Thomas S. Hatch, рассказывал что когда он решил написать свою систему для удаленного управления серверами он выбрал самое быстрое и эффективное что было на рынке для транспорта - ZeroMQ. Позже инженеры из Linkedin добавили возможность работы через raw tcp.

Скорость работы SaltStack была изначальной целью заложенной в дизайн приложения. Идея заключается в том что Salt Master общается с Salt Minions(далее Мастер и Миньены) через publish/subscribe модель, где мастер публикует задачу, а миньены принимают ее и асинхронно выполняют.

From Said van de Klundert blogpost
From Said van de Klundert blogpost

Это означает что Мастер отправляет одно сообщение в общую шину коммуникации вида "Миньены соответствующие определённым критериям - выполняйте вот такую-то логику". Например: "Миньены с именем начинающимся на fe-* - выполняйте formula.nginx"

Это очень легкое сообщение вида "одно-ко-многим". Миньены, которые постоянно слушают эту коммуникацию, забирают эту работу к себе, если они соответствуют указанным критериям, и начинают ее выполнять. Асинхронно. Мастер же зная что указанному критерию соответствовало например 100 миньенов переходит в режим ожидания ответа от этих 100 миньенов.

Даже если мастер в этот момент упадет - работа на миньенах все равно будет выполнена.

У асинхронной коммуникации есть свои плюсы и минусы, но в целом это архитектурное решение позволяет SaltStack масштабироваться до размеров Linkedin где на один мастер может обслуживать до 30000 миньенов.

DSL: Ansible

Ansible DSL прост и понятен. Его легко изучить за короткое время и достаточно просто читать.

Например здесь я создаю две директории используя встроенные возможности циклов и Jinja2 шаблонизации:

- name: 'Create directory {{ item }}'
  file:
    name: "/opt/behavox/{{ item }}"
    state: directory
    mode; "0755"
  loop:
    - myserviceA
    - myserviceB

DSL: SaltStack

SaltStack DSL это очень интересный зверь. Он целиком полагается на внешние способы шаблонизации.

У SaltStack есть такое понятие как Renders и по умолчанию SaltStack использует рендеры Jinja|Yaml - что это значит? Давайте рассмотрим вот этот пример:

{% for dir_name in ['myserviceA', 'myserviceB'] %}
formula.myservice.dir.{{ dir_name }}:
  file.directory:
    - name: /opt/behavox/{{ dir_name }}
    - mode: "0755"
    - makedirs: True
{% endfor %}

Вы можете видеть что у нас здесь Jinja2 шаблон под которым формируется yaml. SaltStack парсит этот файл используя указанные рендеры что бы получить рабочую структуру. В этом примере сначала отработает Jinja2 render и мы получили следующий yaml который и будет загружен.

  formula.myservice.dir.myserviceA:
    file.directory:
    - name: /opt/behavox/myserviceA
    - mode: '0755'
    - makedirs: true
  formula.myservice.dir.myserviceB:
    file.directory:
    - name: /opt/behavox/myserviceB
    - mode: '0755'
    - makedirs: true

Это пример стандартного подхода, который будет использоватся в 99% случаев по моему опыту. Но SaltStack позволяет делать гораздо больше. Если вы откроете ссылку на рендеры выше то вы заметите что SaltStack предлагает множество альтернативных вариантов.

Вы хотите хранить как DSL как GPG зашифрованные куски информации внутри которых лежат Mako шаблоны? Без проблем.

Если идти до конца то SaltStack предлагает py рендер и да это рендер на 100% Python.

Например вот как нем можно реализовать ту же логику создания двух директорий:

!#py

mkdir_common = {'file.directory': [
                  {'mode': '0755'},
                  {'makedirs': True}
                 ]
               }
def run(dir_name):
    config = {}
    for dir in ['myserviceA', 'myserviceB']:
        dirpath = '/opt/behavox/{}'.format(dir)
        config[dir] = mkdir_common
        config[dir]['file.directory'].append(dirpath)

return config

Это может показаться сверхусложнением и... это так и есть, но в одном случае из 1000 вы можете захотеть иметь под рукой всю силу ЯП что бы описать какую-нибудь сложную логику и SaltStack позволяет сделать это.

Оркестрация: Ansible

В большинстве случаев, работая с распределенными системами мы хотим выполнять операции в определенном порядке. Например поставить некие системные пакеты и запустить сервис на хостеА, дождаться пока сервис станет доступен и только потом запустить сервис на хостеБ, который зависит от сервиса на хостеА.

Это называется оркестрацией. Если вам это не нужно - вы счастливый человек.

Оркестрация это очень мощный инструмент и Ansible весьма хорошо ее поддерживает.

По умолчанию Ansible запускает каждую задачу последовательно(на одном или множестве хостов), ждет ее выполнения и только потом приступает к следующей.

Это очень медленно, но позволяет Ansible работать с данными полученными в реальном времени. Например:

  1. Запустить Mysql

  2. Загрузить в него некие данные

  3. Сделать в него запрос и сохранить ответ как переменную

  4. Использовать значение этой переменной в последующих задачах

Так же в любой момент времени Ansible можно сказать выполнить определенную операцию на другом хосте даже если изначально этот хост не входил в список хостов для выполнения.

Так же Ansible можно сказать выполнить определенную задачу только один раз даже если логика была нацелена на множество хостов.

Эти три возможности(сохранение данных в реальном времени, делегация исполнения и выполнение только один раз) позволяеют создавать очень сложные оркестрации способные покрыть множество сценариев.

Главный минус этих возможностей - это все происходит очень медленно из-за необходимости постоянной синхронизации выполнения с Ansible контролером и из-за изначально медленного протокола коммуникации.

Оркестрация: SaltStack

SaltStack так же позволяет запускать определённую логику на определенных хостах, но как только выполнение было начато вы уже никак не можете на него повлиять.

Главная причина здесь асинхронный подход и просчитывание графа выполнения до выполнения. Ansible может поменять ход исполнения посреди логики, более того он в принципе не в курсе что будет дальше чем то что он делает прямо сейчас.

SaltStack просчитывает все наперед что позволяет работать быстрее, но теряет в гибкости. Зато оператор может получить граф исполнения до исполнения и сверится с тем что это именно то что он бы ожидал.

SaltStack пытается исправить это неудобство и предлагает решение которое называется slots которое позволит получать некую информацию и принимать по ней решение прямо перед выполнением логики. Это все еще далеко от гибкости Ansible, но это первый шаг.

Разработка и отладка: Ansible

В Ansible вы можете модифицировать следующее:

  1. Execution modules

  2. Callback plugins

  3. Strategy plugins

  4. Inventory plugins

То есть логику выполнения определенной задачи, как и куда будет возвращен результат выполнения, какая стратегия исполнения будет применена и как собрать информацию о списке машин по которым вы хотите что-то запустить.

Это приемлемый список, но важно понимать что каждый пункт идет с огромным количеством ограничений, например Callback plugins может поменять как результат исполнения будет выглядет в вашем терминале, но только в рамках самой логики исполнения. Например вы не сможете убрать таймстампы перед вызовом вашей логики и тд.

Мы очень долго сражались с Ansible пытаясь скрыть информацию которая нам не нужна, но показывать подробно информацию которая нам нужна и потерпели неудачу. Ansible предлагал все или ничего.

Отладка в Ansible... спорная. Ansible пытается скрыть от оператора всю поднаготную и каждый модуль может вернуть вам только одно сообщение. Вы не можете использовать такие привычные вещи как логирование. Только один ответ за раз и потом вы получаете что-то вида:

fatal: [demo-rm -> localhost]: FAILED! => {"changed": false, "msg": "Failed to connect to Zabbix server: urllib2.URLError - "}

Ок.

Для отладки таких проблем вам придется делать следующее:

  1. Запустить проблемную логику снова(в надежде что ошибка повторится) но с:

    1. Специальными аргументами которые сообщают Ansible не удалять временные файлы.

    2. С увеличенным логированием (-vvv) что бы увидеть в какую временную директорию эти файлы были помещены.

  2. Зайти на удаленную систему где происходит ошибка

  3. Распаковать Ansible модуль

  4. Поправить его что бы он печатал необходимую информацию

  5. Запустить и наконец понять в чем была проблема

Каждый раз когда мне приходилось проделывать эту процедуру - часть меня умирала.

Разработка и отладка: SaltStack

Вот список вещей которые можно модифицировать в SaltStack:

  1. beacons

  2. clouds

  3. engines

  4. grains

  5. log_handlers

  6. matchers

  7. modules

  8. output

  9. proxymodules

  10. renderers

  11. returners

  12. sdb

  13. serializers

  14. states

  15. thorium

  16. utils

  17. pillar

Это впечатляет. По сути все что не транспорт - поддается горячей модификации.

И да output модули SaltStack позволяют нам скрывать бесполезную информацию и раскрывать в подробностях изменения и ошибки.

В плане отладки SaltStack ведет себя так как бы вы ожидали от обычного приложения на Python. Модифицируйте код, добавляйте логирование, перезапускайте логику и смотрите на результат!

[behavox@test-run-kproskurin-rm ~]$ salt-call state.apply formula.java -l debug
[DEBUG   ] Reading configuration from /opt/behavox/salt/minion
[DEBUG   ] Including configuration from '/opt/behavox/salt/minion.d/_schedule.conf'
[DEBUG   ] Reading configuration from /opt/behavox/salt/minion.d/_schedule.conf
[DEBUG   ] Including configuration from '/opt/behavox/salt/minion.d/proxy.conf'

...
[cut]

Это небо и земля для любого инженера и позволяет не только легче отлаживать сложную логику, но и сразу иметь нужное логирование на случай плавающей ошибки.

Imperative vs Declarative

Ansible утверждает что он декларативный инструмент для управления конфигурацией. Это лишь наполовину правда. В Ansible множество 100% декларативных модулей, но в нем так же огромное количество императивных модулей, например как uri.

Такие механизмы как register, when, changed_when, failed_when в Ansible поддерживают эти идею и позволяют делать идемпотентность и условную деларативность через императивные инструменты.

Это породило такое веяние как "Программирование на Ansible" aka "Башсибл".

Отчасти это очень мощный и гибкий инструментарий позволяющий делать плюс-минус все что угодно без навыков програмирования.

Я искренне убежден что минусы этого подхода перевешивают плюсы в долгом забеге.

Так же Ansible не гарантирует что все его модули идемпотентны и предлагает те же самые инструменты что бы обойти эту проблему. Это ведет к ухудшению читаемости и поддержки логики.

SaltStack явно разграничивает императивные и декларативные модули.

Execution modules(далее "модули исполнения") императивны и не идемпотентны. Они написаны как глаголы - скачай архив, удали файл и тд.

State modules(далее "модули состояния") декларативны и идемпотентны. Они приводят систему в определенное... состояние описывая финальный результат, а не как к нему прийти - убедись что пакет nginx установлен и он версии 1.21.3 и тп.

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

По сути большинство модулей состояния это просто клей между модулями исполнения и дополнительными утилитами.

Например модуль состояния который создает директорию внутри себя вызывает модули исполнения вроде "проверь существует ли эта директория" и "создай директорию" и добавляет логический "клей" между этими операциями.

Если директории нет, то создай. Если есть, но с другими правами - поменяй. Если состояние уже верное - ничего не делай. И тд.

В SaltStack гиганское колличество написанных модулей исполнения и писать свой модуль состояния зачастую невероятно просто и приятно когда всю сложную работу уже кто-то сделал и все на чем надо сосредоточится это на основной логике.

Выводы

В этой статье не покрыты сотни вещей которые может делать SaltStack и не может Ansible. Функциональность reactors и engines позволяют делать очень интересные решения вроде реакции на события на вашей системе или построения своего сервиса поверх SaltStack.

По моему мнению главное преимущество Ansible на сегодня это предельная простота и гибкость оркестрации. Этот инструмент хорошо подойдет командам с небольшим количеством обслуживаемых хостов(до сотни) и\или для команд без сильной инженерной экспертизы. Ansible как никто другой приблизился к идее о no-code configuration management.

Но как-только вы вырастаете из этой простоты Ansible становится сильно сложнее и менее удобным чем вы бы ожидали.

Отладка становится мучением, время выкаток увеличивается экспоненциально и вы начинаете регулярно наступать на различные мины вроде того что any_erros_fatal не остановится на падении первого элемента в цикле а доведет его до конца прежде чем вернуть ошибку и тп.

И большая часть примеров логики в интернете будет полна "Программирования на Ansible" которую вам придется поддерживать.

Спустя несколько лет использования SaltStack я пришел к выводу что Ansible это инструмент, а SaltStack это фреймворк.

Вам придется погрузиться чуть глубже, но есть что-то невероятно прекрасное в замене 10 тасков на Башсибле на один хорошо написаный модуль состояния.


Спасибо что дочитали до конца. Если вам хочется обсудить SaltStack на русском языке, то приглашаем вас в русскоязычный Telegram канал: https://t.me/saltstack

Only registered users can participate in poll. Log in, please.
Какой системой управления конфигурациями вы пользуетесь?
64.23% Ansible 88
22.63% SaltStack 31
5.84% Puppet 8
0.73% Chef 1
6.57% Other 9
137 users voted. 35 users abstained.
Tags:
Hubs:
+9
Comments 32
Comments Comments 32

Articles