Пользователь
0,0
рейтинг
13 января 2014 в 18:18

Разработка → Deploy с помощью Salt


До сих пор во многих компаниях deploy создает большие проблемы и может занимать дни, недели и в особо запущенных случаях месяцы. Но ситуация не безнадежна. Существует много инструментов и практик, способных помочь в этом нелегком деле. Вот только эти инструменты чаще всего за один-два дня не освоишь, а сроки горят.

Чего обычно хочется:
  • Возможность поднять проект локально на машине разработчика. Весь или хотя бы частями. Причем очень хочется, чтобы Dev конфигурация отличалась от Prod в минимуме параметров. Это позволит избежать “work on my machine” багов. Да и вообще, когда один разработчик работает на OS X, другой на Windows, а продакшен на Debian, то жди беды, это не считая того, что каждый делает работу по настройке окружения.
  • Dev конфигурацию хочется разворачивать на любой машине и ОС в пару команд в консоли. Это опять же позволит уменьшить фактор “work on my machine” багов. А еще позволит привлекать других разработчиков в проект за минимальное время (vagrant up и поехали).
  • Конфигурация должна быть понятна и программисту, и админу.

Всего этого мы добьемся на связке Salt + Vagrant на примере Django проекта. Но большинство техник будут полезны разработчикам не только на Python, но на других языках.


Сразу дам ссылку на исходники: bitbucket.org/awnion/salt-django-deploy

Что же такое Salt?


Если вы знакомы с Salt, то можете пропустить этот раздел.

Salt — это достаточно мощный инструмент для управления кластером (cluster orchestration), но на мой личный взгляд даже использование на одной машине вполне оправдано и не будет оверкилом (грубо говоря если в вашей команде ровно 1 разработчик, то это не значит, что не надо использовать систему контроля версий).

Salt состояния — это YAML файлы с расширением sls, которые описывают в каком состоянии должна быть машина. Например, вот этот вот файл должен лежать тут, вот этот сервис должен быть запущен, вот этот юзер должен иметь такие права и так далее. В Salt можно поддерживать состояние не только системных утилит (apt, rpm, cron, init скрипты и разные конфиги), но и, например, можно проверить существует ли такой-то пользователь в RabbitMQ, последней ли версии репозиторий git, все ли пакеты стоят в вашем virtualenv и так далее. Полный список состояний можно найти тут docs.saltstack.com/ref/states/all, и на мой взгляд он весьма внушительный.

Несколько фактов про Salt

  • В качестве темплейтного языка для конфигов и фалов состояний Salt использует Jinja. Это невероятно удобно и позволяет следовать DRY даже в конфигах.
  • Salt уже используют такие компании, как Apple, NASA, LinkedIn и многие другие.
  • Salt написан на Python, хотя для его использования знать Python вообще не обязательно.
  • Pdf документация для Salt около 1000 страниц, и написана она вполне добротно. Там вы найдете не только описание API, но и практики использования и примеры.


Dev конфигурация


На мой взгляд сложно переоценить важность хорошей и удобной среды разработки. Но на настройку “все под себя” может уйти пара дней, а то и неделя. Давайте сэкономим эти дни нашим коллегам в будущем и создадим конфигурацию, которая позволит поднять текущую версию проекта в одну команду:
git clone <repo_url> && cd <repo_name> && vagrant up

Ладно, это на самом деле это 3 команды, но если вы можете проще — жму вам руку.

Итак, мы будем строить нашу dev конфигурацию на Vagrant (тем, кто не знаком с Vagrant, настоятельно рекомендую познакомиться):
mkdir my_app && cd my_app
git init
vagrant init

В нашей папке my_app появился git репозиторий и конфиг для Vagrant.

Далее заменяем содержание Vagrantfile на следующее:
Vagrant.configure("2") do |config|
  config.vm.box = "precise64"

  config.vm.hostname = "dev-my-app"
  config.vm.network :private_network, ip: '3.3.3.3'

  config.vm.synced_folder "salt/roots/salt", "/srv/salt/"
  config.vm.synced_folder "salt/roots/pillar", "/srv/pillar/"

  config.vm.provision :salt do |salt|
    salt.minion_config = "salt/minion"
    salt.run_highstate = true
  end
end


Этот конфиг позволит создать гостевую машину на Ubuntu, в конфиге мы задали имя хоста и IP, а так же определили, какие папки синхронизировать и указали, что для приведения нашей машинки к нужному состоянию мы будем использовать salt (кстати, корень нашего проекта будет по умолчанию синхронизован с папкой /vagrant гостевой машины).

Подробней о том, что тут происходит, можно узнать тут

Итак, мы готовы начать описывать состояния. Создадим файл salt/minion со следующим содержанием:
file_client: local


Фактически мы говорим, что эта машина хранит свои состояния сама. По умолчанию salt настроен так, что он берет файлы состояний с master-сервера, а локально их только кэширует где-то в /var/cache/salt. Поэтому если вы не хотите чего-то кастомного, то этот файл на prod машине скорее всего вообще не понадобится.

Теперь создадим две папки:
mkdir -p salt/roots/pillar
mkdir -p salt/roots/salt

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

Создадим файл salt/roots/salt/top.sls
base:
  'dev-my-app':
    - nginx
    - python
    - supervisor


Как можно было догадаться sls очень похож на yaml. Но главным отличаем тут будет то, что sls это еще и jinja темплейт со всеми вытекающими (дальше вы увидите, что это реально приносит пользу).

base — это название конфигурации состояний нашего воображаемого кластера. dev-my-app — это hostname нашей гостевой машины. Тут используется pattern matching, то есть мы могли бы указать ‘dev-*’, и все состояни ниже применились бы ко всем машинам типа dev-alpha, dev-foobar и т.п. Далее следует список состояний, которые нам надо будет описать.

Создадим заявленные состояния python, nginx и supervisor:

salt/roots/salt/python.sls
# это состояние просто проконтролирует, что пакеты python и python-virtualenv
# установлены, а если нет -- то поставит их с зависимостями
python:
  pkg:
    - installed
    - names:
      - python
      - python-virtualenv


salt/roots/salt/nginx.sls
# это состояние поставит nginx и запустит его как сервис, при этом require 
# показывает, что состояние service нужно запустить после состояния pkg
nginx:
  pkg:
    - installed
  service:
    - running
    - reload: True  # сервис поддерживает reload
    - require:
      - pkg: nginx


salt/roots/salt/supervisor.sls
supervisor:
  pkg:
    - installed
  service:
    - running
    - require:
      - pkg: supervisor


Итак, можно уже запустить “vagrant up”. Эта процедура скачает образ Ubuntu (если у вас его еще нет в кэше образов), установит туда salt и запустит синхронизацию состояния.
Теперь у нас на нашей гостевой машине есть python, supervisor и nginx.
Можете проверить это зайдя на машину через vagrant ssh или зайдя на 3.3.3.3

Пока вроде бы все просто. Продолжим:

Создадим переменные pillar:

salt/roots/pillar/top.sls
base:
  'dev-my-app':
    - my-app


salt/roots/pillar/my-app.sls
my_app:
  gunicorn_bind: 127.0.0.1:8000
  dns_name: dev.my-app.com
  venv_dir: /home/vagrant/my_app_env
  work_dir: /vagrant

Первый файл говорит, что хосту dev-my-app назначены переменные из конфига my-app. Второй файл — собственно сами переменные.

Теперь создадим папку для состояний конфигов нашего Django приложения:
mkdir -p salt/roots/salt/my_app


salt/roots/salt/my_app/init.sls
{% set my_app = pillar['my_app'] %}

my_app.venv:
  virtualenv.managed:
    - name: {{ my_app['venv_dir'] }}
    - system_site_packages: False
    - require:
      - pkg: python

my_app.pip:
  pip.installed:
    - bin_env: {{ my_app['venv_dir'] }}
    - names:
      - Django==1.6
      - gunicorn==18.0
    - require:
      - virtualenv: my_app.venv

my_app.nginx.conf:
  file.managed:
    - name: /etc/nginx/sites-enabled/my_app.conf
    - source: salt://my_app/nginx.my_app.conf
    - context: # помимо переменных вроде pillar, мы можем передать дополнительный контекст для тепмлейта
        bind: {{ my_app['gunicorn_bind'] }}
        dns_name: {{ my_app['dns_name'] }}
    - template: jinja
    - makedirs: True
    - watch_in:
      - service: nginx

my_app.supervisor.conf:
  file.managed:
    - name: /etc/supervisor/conf.d/my_app.conf
    - source: salt://my_app/supervisor.my_app.conf
    - context:
        app_name: my_app
        bind: {{ my_app['gunicorn_bind'] }}
        gunicorn: {{ my_app['venv_dir'] }}/bin/gunicorn
        directory: {{ my_app['work_dir'] }}
        workers: {{ grains['num_cpus'] * 2 + 1 }}  # в академических целях выпендрился
    - template: jinja
    - makedirs: True

my_app.supervisor:
  supervisord.running:
    - name: my_app
    - watch:
      - file: my_app.supervisor.conf
    - require:
      - pip: my_app.pip
      - pkg: supervisor


Hint: при составлении зависимостей require, watch и пр. имейте в виду, что состояния будут проверяться в произвольном порядке. При составлении статьи я допустил такого рода ошибку, и пакеты django и gunicorn пытались устанавливаться в еще не созданный virtualenv.

salt/roots/salt/my_app/nginx.my_app.conf
server {
    listen 80;
    server_name {{ dns_name }} _;
    location / {
        proxy_pass http://{{ bind }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}


salt/roots/salt/my_app/supervisor.my_app.conf
[program:{{ app_name }}]
command={{ gunicorn }} {{ app_name }}.wsgi:application -b {{ bind }} -w {{ workers }}
directory={{ directory }}
user=nobody
autostart=true
autorestart=true
redirect_stderr=true


Добавляем только что созданное состояние в salt/roots/salt/top.sls
base:
  ‘dev-my-app’:
    - nginx
    - python
    - supervisor
    - my_app  # <----


Мы почти закончили. Но не хватает самого главного — кода нашего воображаемого Django приложения. Давайте создадим пустой тестовый Django проект следующим образом:
vagrant provision


Этот процесс займет несколько минут (большую часть времени будут ставиться django и gunicorn в virtualenv).

Когда provision отработает заходим на гостевую машину:
vagrant ssh


И внутри делает следующее:
/home/vagrant/my_app_env/bin/django-admin.py startproject my_app /vagrant
exit


И на хост машине опять делаем vagrant provision, и чтобы проверить, что все работает в файле hosts пропишите временно:
dev.my-app.com 3.3.3.3


Переходим в браузере на dev.my-app.com, и если все хорошо, то мы увидим It worked!

Dev конфигурация построена. Можно коммитить.

Теперь если вы захотите отдать поиграться со своим проектом другу, то ему нужно будет сделать только git clone и vagrant up. Причем этот воображаемый друг получит не только исходники самого проекта, но и будет иметь представление о том, как его будут деплоить.

Кроме всего прочего по умолчанию наш проект крутится самостоятельно под управлением gunicorn+supervisor. Но что если мы хотим удаленно подебажить или мы хотим вернуть наш любимый autoreload изменений кода? Не вопрос:
vagrant ssh
supervisorctl stop my_app
/home/vagrant/my_app_env/bin/python /vagrant/manage.py runserver


Теперь мы можем спокойно редактировать код, и все изменения будут подхватываться django сервером автоматически.
И если у нас все еще временно подправлен hosts, то на запросы dev.my-app.com будет отвечать все тот же django сервер.

Prod конфигурация


Вот мы и добрались до самого главного. Будем считать, что деплоиться мы будем на prod-my-app.

Далее мы рассмотрим вариант деплоя в ситуации, когда у нас есть отдельный сервер для salt-master (далее просто мастер).

Копируем на мастер конфиги, добавляем в /srv/salt/top.sls
  ‘prod-my-app’:
    - nginx
    - python
    - supervisor
    - my_app


Или в нашем случае можно сделать вот так:
base:
  ‘*-my-app’:
    - nginx
    - python
    - supervisor
    - my_app


Далее делаем то же самое с файлом /srv/pillar/top.sls
base:
  '*-my-app':
    - my-app


В /srv/pillar/my_app.sls меняем переменные согласно нашей карте раскладки.

На prod-my-app ставим salt-minion. Подключаем salt-minion к salt-master (как это сделать читай тут).
Теперь на мастере можно запустить применение конфигов:
sudo salt prod-my-app state.highstate


Что осталось:

Доставка конфигов на мастер

Тут куча способов от rsync до git. Все зависит от вашей внутренней политики.

Доставка исходников проекта на prod-my-app

Тут опять же куча вариантов. Лично я делаю так: при помощи salt поддерживаю на prod-my-app репозиторий git на определенном коммите, хэш которого хранится в pillar, и если он меняется, то salt в конце работы запускает скрипты деплоя.

Честно говоря это не самый лучший способ, но он самый простой. В идеале создавать, например, нативные пакеты, или поднимать приватный pypi.

Ссылки


www.saltstack.com
www.vagrantup.com
hynek.me/talks/python-deployments — весьма полезная статья, которая содержит набор тезисов на тему деплоя python проектов.

PS
К сожалению очень многие вещи пришлось упустить, иначе бы статья раздулась до неприлично большого размера. Я старался упускать либо очевидные вещи, либо то, что легко найти в документации. Тем не менее задавайте вопросы в комментариях.
FINTER @FINTER
карма
41,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

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

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

  • 0
    Salt работает без агента или внутрь вагранта агент сперва ставится?
    • 0
      Там 2 демона: salt-master и salt-minion. При использовании Vagrant они ставятся автоматически.
      • 0
        уже появился salt-ssh
        • 0
          Ага, но он пока сыроват. Иногда попадаю на баги, хотя и редко.
          • 0
            их там тьма, начиная от неработающего root_fs :)
  • +1
    В конфигурации Vagrant можно еще указать директивы чтобы ставить только мастер или только миньон. Подробности тут: github.com/saltstack/salty-vagrant
  • +1
    как доставляются на энвайронмент креденшлы и разнообразные токены?
    есть аналог зашифрованных датабагов, как в chef?
    • 0
      Ну на мастер сервере все настройки хранятся в открытом виде для рута.
      Настройки делятся на переменные(pillar), и все остальное (salt).
      А все общения с воркерами происходят по шифрованному протоколу.

      Или нужно что-то еще?
      • 0
        как раз для того, чтобы каждый встречный-поперечный не читал все кренделя, и делали шифрованные датабаги в шефе. их можно коммитить в любой публичный или приватный репозиторий, не опасаясь, что кто-нибудь уведет пароль от продакшновой базы.
        а вы свои настройки с мастер-сервера храните только на мастер-сервере?
        как у вас происходит коллективная разработка?
        • 0
          Пароли и токены всякие лежат только на мастер сервере. Встречный поперечный видит их только тот, у кого есть судо уровня рута на мастер сервере (обычно это 2 человека).

          Идеология salt как раз подразумевает, что настройки типа паролей к базам, токены, хэши лежат в /etc/pillar. Туда стараются спихнуть все, что меняется между установками в разных условиях типа ip, hostname и пр.

          Все остальные конфиги лежат в /etc/salt — туда можно давать доступ хоть всем.

          Вот пример того, что будет лежать в репо:

          /etc/pillar/top.sls — корневой конфиг переменных с раскладкой по хостам
          base:
            'my-super-server':
              - super_app
          

          /etc/pillar/super_app.sls.sample
          super_app:
            db_name: blablabla
            db_pass: yahoo!@#$
          


          Эти два файлика будут лежать в репо.

          Дальше если нам нужно инсталлировать приложение на реальный my-super-server, то мы апаемся из репо, делаем копию super_app.sls.sample -> super_app.sls, расписываем там настоящие настройки (обычно это самый минимум: как раз логины, пароли, ключи) и запускаем синхронизацию состояний salt my-super-server state.highstate.

          Вроде все прозрачно и просто by design
          • 0
            т.е. вы храните креденшлы в репозитории, не опасаясь утечек?
            сильно.
            • 0
              Это как вы такой вывод сделали?
            • +1
              В предложении «делаем копию super_app.sls.sample -> super_app.sls» какое из слов непонятно?
  • 0
    Push или pull?
    • +1
      И так и так. Порты слушает только master, а minion подключается к нему хоть из-за NAT. После установления соединения мастер может делать push. Также можно периодически выполнять pull на minion и можно даже вообще без мастера.
  • 0
    Да и вообще, когда один разработчик работает на OS X, другой на Windows, а продакшен на Debian, то жди беды, это не считая того, что каждый делает работу по настройке окружения.


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

    Особенно это очевидно, если разработка, к примеру, под виндой, а продакшн сервер — *nix.
  • 0
    Если хочется деплоить/поднимать у себя на машние много хостов, то, на мой взгляд, проекты, основанные на Docker, интереснее. Например, Flynn. Правда он еще толком не вышел.
    • 0
      Докер — он вообще не про «хосты».
      • 0
        Да, про контейнеры. На каждом хосте может быть несколько контейнеров.
        • 0
          И не про контейнеры. Про контейнеры — это LXC. А докер — вообще про другое.
          • 0
            Вы меня заинтриговали. В чем же разгадка? Про что Docker?
            • 0
              Docker это система для изоляции приложений. И тот факт, что она работает поверх LXC ничего не значит.
              Вполне могла бы работать поверх того же virtualbox, как Vagrant.
  • 0
    А как Salt по сравнению с Ansible?
    • 0
      сравните примеры рецептов и выберите что больше нравится, у ансибл разве что веб-морда из коробки, хотя в салте она тоже есть, но отдельно
    • +1
      salt изначально спроектирован для управления огромными кластерами.

      Например, когда я смотрел на Ansimble, он ходил по ssh на каждую машину и выполнял там какие-то задачи. Мне такой подход не понравился, потому что теряется вся суть cluster orchestration.

      В salt задачи раздаются и выполняются через очередь сообщений, а исполняются уже на месте миньёнами. Причем в salt есть возможность построить дерево мастеров (salt-syndic), если машин о-о-очень много, хотя даже 1 мастер может обслуживать до тысячи машинок не напрягаясь.

      Ну и еще один момент: в salt гарантируется, что процесс конфигурирования на машине выполняется только один в любой момент времени. Вы и, скажем, ваш коллега одновременно не сможете запустить процессы конфигурирования, что как не трудно догадаться может все поломать. Возможно в Ansimble это как-то тоже контролируется, но в salt это by design.
  • +4
    Я делал презентацию про Salt и написал пару постов: "Developing Django project with SaltStack", "Developing & Deploying Django project with SaltStack". Возможно кому-нибудь будет полезно.
    • 0
      Ваша презентация — моё почтение! Сделано очень круто.

      Только вот вопрос возник. В последнем посту вы с git работаете через cmd.run. Вот стало интересно чем вас salt.states.git не устроил? Я им пользуюсь и вроде ок.
      • 0
        Спасибо, рад что вам понравилось.

        В посте есть объяснение:
        I have tried to use built in git.latest Salt state but it requires to configure git name and email on server
        Наверное я плохо сформулировал мысль. При вызове «git pull» GIT просил указать имя пользователя и почту, чтобы использовать их в merge коммите. Я решил не добавлять лишних настроек и использовать «git reset --hard origin/master».
  • 0
    Спасибо за пост. Наконец-то я наглядно(на примере) понял, для чего нужны pillar.
  • 0
    Подскажите, структура директорий/файлов как-то определена? salt/roots/salt имеет какое-то значение?
    • 0
      Никакого значения нет. Просто так исторически сложилось.

      Только не забудьте подправить Vagrantfile, если у вас изменится раскладка проекта.
  • 0
    А чем данный инструмент отличается, например, от Puppet?
    • 0
      Я знаком с Puppet лишь понаслышке.

      Salt и Puppet в плане силы «конфигурирования» состояний примерно равны.
      Salt кроме поддержки состояний умеет еще и оркестрацию. То есть фактически заменяет fabric.
      В отличие от fabric соль использует 0mq в качестве транспорта, поэтому работает в разы быстрее, но требует предустановленного minion на машинках.
      Не уверен, что в Puppet это есть.

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