Ансамбль солёных поваров-кукловодов: сравниваем Ansible, SaltStack, Chef и Puppet

Habrahabr 2

Сегодня мы поговорим о том, что такое SCM и расскажем несколько историй, через призму которых рассмотрим Ansible, SaltStack, Chef и Puppet, выбрав лучший вариант для конкретной задачи.

В основе материала — расшифровка доклада Андрея Филатова, ведущего системного инженера компании 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, но сборки кастомные. Он пришел к нам со следующими задачами:
  1. Создание USB/DVD инсталлятора. Нужно создать медиа, из которого все инсталлируется. Это делается для клиентов заказчика, которые живут в закрытых зонах, где на серверах чаще всего нет интернета. Нам нужно упаковать в одну ISO, отправлять field-инженерам, которые на месте развернут все необходимое.
  2. Развертывание кластера с продуктом. У заказчиков несколько крупных продуктов, мы должны уметь их разворачивать в кластерном режиме.
  3. Управление, настройка и сопровождение кластера с помощью 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, уметь убивать дефективные ресурсы и на их место восстанавливать свежие и гарантированно рабочие.

С какими проблемами мы столкнулись:

  1. Это был 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 для фильтрации нужных результатов.
  2. Использовать «node.save» небезопасно, будьте внимательны и осторожны. Мы столкнулись с этой проблемой, когда разворачивали MySQL-кластеры, и использовали внутри рецепта node.save на не полностью сконфигурированной MySQL-ноде. И в момент Scale-up некоторые приложения выдавали 500 ошибку. Выяснилось, что мы не в то время сохраняли ноду: она уходит на Chef-сервер, тут же Chef-клиент на UI подхватывает новую ноду, которая не сконфигурировалась до рабочего режима.
  3. Отсутствие «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:
  1. Используйте ‘accelerate’, он в 2-6 раз быстрее SSH разворачивает конфигурации (для el6). Для всех остальных есть ‘pipelining’. Для обратной совместимости он выключен, но включить обратно ‘pipelining’ очень легко, рекомендую это делать.
  2. Используйте ‘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
    
    
    В данном примере мы устанавливаем пакеты, эту схему можно использовать для создания пользователей и тому подобных операций.
  3. Аккуратно используйте ‘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}}"
    
    
    Это кусок по созданию баз данных. Без последней строчки операция выполнилась бы несколько раз и свалилась на второй попытке создать ту же самую базу данных.
  4. Оптимизируйте ваши роли и исполнение при помощи тегов. Это позволяет значительно сократить время выполнения.

Выводы

Лично для меня Ansible — фаворит. SaltStack очень хороший, очень гибкий, но требует знания Python, без них SaltStack лучше не использовать. Chef — универсальная серебряная пуля для любых задач и любых масштабов, но требует больших знаний, чем Ansible. А кто использует Puppet —  я не знаю. В принципе, он очень похож на Chef, но со своими нюансами.
Минутка рекламы. Если вам понравился этот доклад с конференции DevOops — обратите внимание, что 14 октября в Санкт-Петербурге пройдет новый DevOops 2018, в его программе тоже будет много интересного. На сайте уже есть первые спикеры и доклады.