Сегодня мы поговорим о том, что такое SCM и расскажем несколько историй, через призму которых рассмотрим Ansible, SaltStack, Chef и Puppet, выбрав лучший вариант для конкретной задачи.
В основе материала — расшифровка доклада Андрея Филатова, ведущего системного инженера компании EPAM Systems, c нашей октябрьской конференции DevOops 2017.

Что же такое SCM? В первую очередь это штука, которая позволяет нашу инфраструктуру из состояния A при помощи выполнения какого-то кода привести к состоянию Б. Многие разработчики, которые не являются на практике DevOps инженерами, думают, что каким-то «автомагическим» способом что-то происходит на инфраструктуре.
«Автомагический» способ нам реализует SCM (System Configuration Management). Для чего это нужно? В первую очередь для того, чтобы строить повторяемые и консистентные инфраструктуры. SCM хорошо расширяет CI/CD-процессы. Так как это код, его можно хранить в любой системе контроля версий: Git, Mercurial. Его достаточно просто развивать и поддерживать.
Финальное — это замкнутый цикл автоматизации: все можно делать в автоматическом режиме, от создания инфраструктуры до ее развертывания и deployment-а кода.

Рассмотрим наших претендентов. Первый — Ansible. Имеет безагентную архитектуру, если мы говорим об open source-версии, написан на Python, имеет Yaml-подобный DSL, легко расширяемый за счет модулей, написанных на Python, очень простой и легковесный. Ansible имеет самый низкий порог вхождения — вы можете научить кого угодно.
Есть опыт, когда человек, не зная Python, не зная ничего о SCM, вошел в Ansible буквально за два дня и уже начал что-то делать.
Ниже пример ChatOps: нотификатор в Slack. Код на Ansible, кто видел Yaml — ничего нового.

Chef — это клиент-серверная архитектура, есть Chef-сервер и Chef-клиент. Конфигурация основана на поиске, написан на Ruby, имеет Ruby DSL. Соответственно, внутри своих cookbook-ов и рецептов вы можете использовать всю мощь Ruby, но я не советую этого делать. У Chef огромное комьюнити и самый большой набор инструментов среди всех SСM. Вот так выглядит код на Chef, это разворачивание Jetty.

SaltStack имеет как безагентную архитектуру, которая работает в push-режиме при помощи Salt-SSH, так и клиент-серверную архитектуру, когда есть Salt-master и Salt-minion. Упор сделан на автоматизацию в реальном времени, имеет из коробки параллельное исполнение всех процессов и написан на Python. Тоже Yaml-подобный язык, код очень похож на Ansible.

Последний из наших претендентов — Puppet. Тоже имеет клиент-серверную архитектуру, как Chef, конфигурация основана не на поиске, а на «фактах», которые приходят с Puppet-master-а, написан на Ruby, имеет Ruby-подобный DSL. Но ребята из Puppet не разрешают использовать в своих манифестах чистый код Ruby. Это и плюс, и минус. Вот так выглядит код манифеста на Puppet:
В первую очередь я хотел бы поделиться проектом, который был написан на SaltStack. Это наш предыдущий проект и самая свежая боль, а свежая боль всегда самая больная. Наш заказчик занимается хранением данных — это производство железных серверов для хранения данных на GPFS, GlusterFS, но сборки кастомные. Он пришел к нам со следующими задачами:
У заказчика было несколько требований. В первую очередь, у него огромное количество Python-экспертизы, по факту только C и Python-разработчики. Заказчик сразу сказал: «Мы хотим SaltStack», не оставив выбора.
С чем мы столкнулись? У заказчика в инсталляции есть несколько продуктов, все должны быть c Salt-Master’ами. Но мы столкнулись с проблемой масштабирования Multi-Master-конфигурации. К примеру, у нас в NODЕ Info (состояние конкретного сервера) выбиралось при двухмастерной конфигурации миллисекунды, при трех — уже секунды, а при пяти мы ни разу не дождались завершения операции. MultiMaster хорошая фишка, но масштабируется плохо.
Вторая проблема, с которой мы столкнулись — командная работа: в SaltStack есть Runner и Module. Module — расширение, которое выполняется на Salt Minion, на стороне машины. Runner выполняется на стороне сервера. У нас очень часто возникали баталии: что делать Runner, и что делать Modules.
Затем столкнулись с небольшим сюрпризом от сache.mine:
У нас есть утилита, которая написана на C. Мы ее запускаем, она генерирует случайный ID. Он д��лжен быть уникален среди всех участников кластера, соответственно, нам это нужно делать один раз на мастере, и дальше распространять среди машин. Мы для этого использовали cache.mine. Как оказалось, он не переживает перезагрузки.

«Race condition». Параллелизация — хорошо, но в базовой конфигурации state.orchestrate приходит в состояние state.sls is running, если происходят длительные процессы. По таймауту он считает, что State уже выполнился, хотя тот еще выполняется, и пытается запустить следующий. Возникает ошибка. И эта проблема пока ещё не исправлена.

Можно посмотреть на GitHub.
Что мы могли использовать, кроме SaltStack?

Заказчик пришел с задачей консолидировать все ресурсы в одном облаке — это был Openstack. До этого все было разбросано: что-то на Rackspace Cloud, что-то на выделенных серверах или своих приватных датацентрах.
Они хотели полностью динамическое управление ресурсами, а также, чтобы их приложения в случае необходимости могли добавить себе мощностей. То есть нужна полная динамическая инфраструктура и полностью динамическое окружение как вверх, так и вниз.
Для того чтобы правильно построить процесс CD, нужна полностью автоматизированная среда. Мы создали для них SDLC — Software Development Lifecycle, и применили его, в том числе, для SCM. У них проходят интеграционные тесты не только приложений, но и инфраструктуры.
Соответственно, когда у нас идет что-то не так, мы должны, как ребята из Netflix, уметь убивать дефективные ресурсы и на их место восстанавливать свежие и гарантированно рабочие.
С какими проблемами мы столкнулись:
Что мы можем использовать вместо Chef?

За год-полтора до появления AWS OpsWorks мы хотели создать расширенный Amazon CloudFormation, интегрировав Chef, чтобы ресурсы не только разворачивались, но и настраивались.
Вторая глобальная задача — создание сервис-каталога, чтобы заказчики и пользователи могли при помощи CLI развернуть полностью готовый к использованию например LAMP-стек.

Мы выбрали Chef, но проект должен был поддерживать разные SCM. Мы стартовали со встроенным Chef-Server’ом, а также пользователи могли использовать собственный Chef-Server, который хостится где-то у них. То есть мы не получали доступа к пользовательским ресурсам и ноутбукам, но это все равно работало.

Для реализации CloudFormation + OpsWork можно использовать любой SCM, подходят все. Для создания каталога — все, кроме SaltStack, хорошо с этим справятся. У SaltStack есть нюансы: найти специалиста, который хорошо знает SaltStack и может создавать сервис и наполнять каталог, крайне сложно.

Это статистика популярности SCM внутри EPAM. SaltStack очень далеко позади. На первом месте Ansible, он самый простой и с низким порогом вхождения. Когда мы пытаемся найти кого-то на рынке со знанием SCM — рынок выглядит примерно так же.
Советы, которые я могу дать при работе с Ansible:
Лично для меня Ansible — фаворит. SaltStack очень хороший, очень гибкий, но требует знания Python, без них SaltStack лучше не использовать. Chef — универсальная серебряная пуля для любых задач и любых масштабов, но требует больших знаний, чем Ansible. А кто использует Puppet — я не знаю. В принципе, он очень похож на Chef, но со своими нюансами.
В основе материала — расшифровка доклада Андрея Филатова, ведущего системного инженера компании EPAM Systems, c нашей октябрьской конференции DevOops 2017.
Что такое SCM и с чем его едят?

Что же такое SCM? В первую очередь это штука, которая позволяет нашу инфраструктуру из состояния A при помощи выполнения какого-то кода привести к состоянию Б. Многие разработчики, которые не являются на практике DevOps инженерами, думают, что каким-то «автомагическим» способом что-то происходит на инфраструктуре.
«Автомагический» способ нам реализует SCM (System Configuration Management). Для чего это нужно? В первую очередь для того, чтобы строить повторяемые и консистентные инфраструктуры. SCM хорошо расширяет CI/CD-процессы. Так как это код, его можно хранить в любой системе контроля версий: Git, Mercurial. Его достаточно просто развивать и поддерживать.
Финальное — это замкнутый цикл автоматизации: все можно делать в автоматическом режиме, от создания инфраструктуры до ее развертывания и deployment-а кода.
Что такое SCM: Ansible

Рассмотрим наших претендентов. Первый — Ansible. Имеет безагентную архитектуру, если мы говорим об open source-версии, написан на Python, имеет Yaml-подобный DSL, легко расширяемый за счет модулей, написанных на Python, очень простой и легковесный. Ansible имеет самый низкий порог вхождения — вы можете научить кого угодно.
Есть опыт, когда человек, не зная Python, не зная ничего о SCM, вошел в Ansible буквально за два дня и уже начал что-то делать.
Ниже пример ChatOps: нотификатор в Slack. Код на Ansible, кто видел Yaml — ничего нового.
- block:
- name: "SlackNotify : Deploy Start"
local_action:
module: slack
token: "{{ slack_url }}"
attachments:
- title: "Deploy to {{ inventory_hostname }} has been Started"
text: "<!here> :point_up_2:"
color: "#551a8b"
- include: configure.yml
tags:
- configure
- include: remote-fetch.yml
tags:
- remote
- include: assets.yml
Что такое SCM: Chef

Chef — это клиент-серверная архитектура, есть Chef-сервер и Chef-клиент. Конфигурация основана на поиске, написан на Ruby, имеет Ruby DSL. Соответственно, внутри своих cookbook-ов и рецептов вы можете использовать всю мощь Ruby, но я не советую этого делать. У Chef огромное комьюнити и самый большой набор инструментов среди всех SСM. Вот так выглядит код на Chef, это разворачивание Jetty.
#
# Cookbook Name:: dg-app-edl
# Recipe::fe
#
node.normal[:jetty][:home] = "/usr/share/jetty"
node.normal[:jetty][:group] = "deploy"
include_recipe "dg-auth::deploy"
include_recipe "newrelic::repository"
include_recipe "newrelic::server-monitor"
include_recipe "dg-jetty::jetty9"
include_recipe "newrelic::java-agent"
directory "edl" do
action :create
owner
group "deploy"
mode "0775"
path "/usr/share/where/edl"
recursive true
end
Что такое SCM: SaltStack

SaltStack имеет как безагентную архитектуру, которая работает в push-режиме при помощи Salt-SSH, так и клиент-серверную архитектуру, когда есть Salt-master и Salt-minion. Упор сделан на автоматизацию в реальном времени, имеет из коробки параллельное исполнение всех процессов и написан на Python. Тоже Yaml-подобный язык, код очень похож на Ansible.
#ntp-packages:
pkg.installed:
- pkgs:
- ntp
- ntpdate
#/etc/ntp.conf:
file:
- managed
- source: salt://common/ntpd/ntp.conf
- template: jinja
- mode: 644
#/etc/sysconfig/ntpd:
file:
- managed
- source: salt://common/ntpd/ntpd
- template: jinja
- mode: 644
#ntp-service:
service.running:
- name: ntpd
Что такое SCM: Puppet

Последний из наших претендентов — Puppet. Тоже имеет клиент-серверную архитектуру, как Chef, конфигурация основана не на поиске, а на «фактах», которые приходят с Puppet-master-а, написан на Ruby, имеет Ruby-подобный DSL. Но ребята из Puppet не разрешают использовать в своих манифестах чистый код Ruby. Это и плюс, и минус. Вот так выглядит код манифеста на Puppet:
class { 'mysql::server' :
root_password => 'password'
}
mysql::db{ ['test', 'test2', 'test3']:
ensure => present,
charset => 'utf8',
require => Class['mysql::server'],
}
mysql::db{ 'test4':
ensure => present,
charset => 'latin1',
}
mysql::db{ 'test5':
ensure => present,
charset => 'binary',
collate => 'binary',
}
SСM на практике
SaltStack в условиях демилитаризованной среды
В первую очередь я хотел бы поделиться проектом, который был написан на SaltStack. Это наш предыдущий проект и самая свежая боль, а свежая боль всегда самая больная. Наш заказчик занимается хранением данных — это производство железных серверов для хранения данных на GPFS, GlusterFS, но сборки кастомные. Он пришел к нам со следующими задачами:
- Создание USB/DVD инсталлятора. Нужно создать медиа, из которого все инсталлируется. Это делается для клиентов заказчика, которые живут в закрытых зонах, где на серверах чаще всего нет интернета. Нам нужно упаковать в одну ISO, отправлять field-инженерам, которые на месте развернут все необходимое.
- Развертывание кластера с продуктом. У заказчиков несколько крупных продуктов, мы должны уметь их разворачивать в кластерном режиме.
- Управление, настройка и сопровождение кластера с помощью CLI-утилиты. Наш фреймворк должен помогать field-инженерам управлять кластером.
У заказчика было несколько требований. В первую очередь, у него огромное количество Python-экспертизы, по факту только C и Python-разработчики. Заказчик сразу сказал: «Мы хотим SaltStack», не оставив выбора.
С чем мы столкнулись? У заказчика в инсталляции есть несколько продуктов, все должны быть c Salt-Master’ами. Но мы столкнулись с проблемой масштабирования Multi-Master-конфигурации. К примеру, у нас в NODЕ Info (состояние конкретного сервера) выбиралось при двухмастерной конфигурации миллисекунды, при трех — уже секунды, а при пяти мы ни разу не дождались завершения операции. MultiMaster хорошая фишка, но масштабируется плохо.
Вторая проблема, с которой мы столкнулись — командная работа: в SaltStack есть Runner и Module. Module — расширение, которое выполняется на Salt Minion, на стороне машины. Runner выполняется на стороне сервера. У нас очень часто возникали баталии: что делать Runner, и что делать Modules.
Затем столкнулись с небольшим сюрпризом от сache.mine:
ime = ImeActions()
id = __grains__['id']
if id == ime.master_id:
ret = __salt__['mine.get'](id, 'ime_actions.make_bfs_uuid')
ime_dict = ret.get(id, None)
if not ime_dict:
try:
result = __salt__['mine.send']('ime_actions.make_bfs_uuid')
except Exeption, e:
log.error("Failed to generate uuid: {0}.".format(str(e)))
result = False
else:
У нас есть утилита, которая написана на C. Мы ее запускаем, она генерирует случайный ID. Он д��лжен быть уникален среди всех участников кластера, соответственно, нам это нужно делать один раз на мастере, и дальше распространять среди машин. Мы для этого использовали cache.mine. Как оказалось, он не переживает перезагрузки.

«Race condition». Параллелизация — хорошо, но в базовой конфигурации state.orchestrate приходит в состояние state.sls is running, если происходят длительные процессы. По таймауту он считает, что State уже выполнился, хотя тот еще выполняется, и пытается запустить следующий. Возникает ошибка. И эта проблема пока ещё не исправлена.

Можно посмотреть на GitHub.
Что мы могли использовать, кроме SaltStack?
SaltStack в DMZ окружении

- DMZ. Chef отлично пакуется, Puppet тоже. А с Ansible проблема — если нет Tower, — нет возможности запустить конфигурацию в Pull-режиме с наших нод, что необходимо делать в демилитаризованной зоне.
- Framework for CLI (на Python). Chef и Puppet не очень подходят, но если у вас нет ограничений использовать только Python — можно писать на Ruby и использовать API Chef или Puppet. Ansible подобный инструментарий не поддерживает.
- Cluster Management. Chef хорошо подходит для управления кластерами, Puppet тоже, а Ansible изначально писался для того, чтобы управлять кластерами в Amazon.
Chef в большой и динамичной среде
Заказчик пришел с задачей консолидировать все ресурсы в одном облаке — это был Openstack. До этого все было разбросано: что-то на Rackspace Cloud, что-то на выделенных серверах или своих приватных датацентрах.
Они хотели полностью динамическое управление ресурсами, а также, чтобы их приложения в случае необходимости могли добавить себе мощностей. То есть нужна полная динамическая инфраструктура и полностью динамическое окружение как вверх, так и вниз.
Для того чтобы правильно построить процесс CD, нужна полностью автоматизированная среда. Мы создали для них SDLC — Software Development Lifecycle, и применили его, в том числе, для SCM. У них проходят интеграционные тесты не только приложений, но и инфраструктуры.
Соответственно, когда у нас идет что-то не так, мы должны, как ребята из Netflix, уметь убивать дефективные ресурсы и на их место восстанавливать свежие и гарантированно рабочие.
С какими проблемами мы столкнулись:
- Это был 2013 год, использовали Chef 10, в котором медленный поиск. Мы запускали поиск, обходя все машины, и это занимало вечность. Попытались решить проблему naming-конвенцией, а также выбором и поиском по fqdn. Это сужало область поиска, за счет чего он ускорялся.
Но некоторые операции нужно делать на всем окружении. Соответственно, поиск запускался один раз в самом начале, результат сохранялся в атрибуте, и с помощью Ruby фильтровали результаты: парсили нужные нам кусочки и делали, что было нужно.
if !Chef::Config[:solo] search(:node, "fqdn:*metro-#{node[:env]}-mongodb*").each do |mongo| @mongodbs << mongo.fqdn end else @mongodbs = ["lvs-metro-#{node[:env]}-mongodb3001.qa.example.com"] end
Итог: используйте нейминг-конвенции, запускайте поиск один раз, используйте Ruby для фильтрации нужных результатов.
- Использовать «node.save» небезопасно, будьте внимательны и осторожны. Мы столкнулись с этой проблемой, когда разворачивали MySQL-кластеры, и использовали внутри рецепта node.save на не полностью сконфигурированной MySQL-ноде. И в момент Scale-up некоторые приложения выдавали 500 ошибку. Выяснилось, что мы не в то время сохраняли ноду: она уходит на Chef-сервер, тут же Chef-клиент на UI подхватывает новую ноду, которая не сконфигурировалась до рабочего режима.
- Отсутствие «splay» может убить chef-сервер. Splay — параметр Chef-клиента, который позволяет задать диапазон, когда клиент пойдет к серверу за конфигурацией. При большой нагрузке, когда нужно развернуть много нод одновременно, это позволит не убить сервер.
Что мы можем использовать вместо Chef?

- Dynamic provisioning. SaltStack подходит идеально, так как у него есть SaltCloud, который отлично интегрируется куда угодно. В Puppet есть подобная функциональность, но она доступна только в Puppet Enterprise, за деньги. Ansible хорошо подходит, если компания «живет» в Amazon, если что-то другое — можно завязать его в альтернативы, но это не так удобно.
- SDLC. В Chef есть всё, начиная от Test Kitchen до выбора инструментов для интеграционного тестирования. В SaltStack есть весь доступный Python-инструментарий, сейчас в Puppet тоже все есть. В Ansible есть Role Spec, можно использовать Test Kitchen от Chef, но это не нативный инструмент.
- Resource replacement. В Chef все устроено хорошо, в SaltStack можно допилить SaltCloud до нужного состояния, в Puppet инструменты только в Enterprise-версии, а Ansible хорошо работает только с Amazon.
EPAM Private Cloud с Chef
За год-полтора до появления AWS OpsWorks мы хотели создать расширенный Amazon CloudFormation, интегрировав Chef, чтобы ресурсы не только разворачивались, но и настраивались.
Вторая глобальная задача — создание сервис-каталога, чтобы заказчики и пользователи могли при помощи CLI развернуть полностью готовый к использованию например LAMP-стек.

Мы выбрали Chef, но проект должен был поддерживать разные SCM. Мы стартовали со встроенным Chef-Server’ом, а также пользователи могли использовать собственный Chef-Server, который хостится где-то у них. То есть мы не получали доступа к пользовательским ресурсам и ноутбукам, но это все равно работало.

Для реализации CloudFormation + OpsWork можно использовать любой SCM, подходят все. Для создания каталога — все, кроме SaltStack, хорошо с этим справятся. У SaltStack есть нюансы: найти специалиста, который хорошо знает SaltStack и может создавать сервис и наполнять каталог, крайне сложно.
Популярность SCM в EPAM

Это статистика популярности SCM внутри EPAM. SaltStack очень далеко позади. На первом месте Ansible, он самый простой и с низким порогом вхождения. Когда мы пытаемся найти кого-то на рынке со знанием SCM — рынок выглядит примерно так же.
Работа с Ansible
Советы, которые я могу дать при работе с Ansible:
- Используйте ‘accelerate’, он в 2-6 раз быстрее SSH разворачивает конфигурации (для el6). Для всех остальных есть ‘pipelining’. Для обратной совместимости он выключен, но включить обратно ‘pipelining’ очень легко, рекомендую это делать.
- Используйте ‘with_items’
- name: project apt dependencies installed apt: name: "{{ item }}" become: yes with_items: - build-essential - acl - git - curl - gnupg2 - libpcre3-dev - python-apt - python-pycurl - python-boto - imagemagick - libmysqlclient-dev # needed for data import
В данном примере мы устанавливаем пакеты, эту схему можно использовать для создания пользователей и тому подобных операций.
- Аккуратно используйте ‘local_action’ и ‘delegated’. Первый позволяет получить нечто похожее на SaltStack Runner, второй умеет делегировать задачи конкретным машинам.
- name: create postgresql database postgresql_db: name: "{{ database_name }}" login_host: "{{ database_host }}" login_user: "{{ database_master_user }}" login_password: "{{ database_master_password }}" encoding: "UTF-8" lc_collate: "en_US.UTF-8" lc_ctype: "en_US.UTF-8" template: "template0" state: present delegate_to: "{{ groups.pg_servers|random}}"
Это кусок по созданию баз данных. Без последней строчки операция выполнилась бы несколько раз и свалилась на второй попытке создать ту же самую базу данных.
- Оптимизируйте ваши роли и исполнение при помощи тегов. Это позволяет значительно сократить время выполнения.
Выводы
Лично для меня Ansible — фаворит. SaltStack очень хороший, очень гибкий, но требует знания Python, без них SaltStack лучше не использовать. Chef — универсальная серебряная пуля для любых задач и любых масштабов, но требует больших знаний, чем Ansible. А кто использует Puppet — я не знаю. В принципе, он очень похож на Chef, но со своими нюансами.
Минутка рекламы. Если вам понравился этот доклад с конференции DevOops — обратите внимание, что 14 октября в Санкт-Петербурге пройдет новый DevOops 2018, в его программе тоже будет много интересного. На сайте уже есть первые спикеры и доклады.
