Ansible — популярная система для управления конфигурацией серверов, но не только: с помощью ansible можно также выкатывать (deploy) приложения на тестовые и боевые площадки. Разберем как это делается на примере.

Задача

Пусть у нас есть веб-приложение, исходные коды которого хранятся в системе контроля версий, например git.

Мы хотим реализовать механизм выката новых версий приложения на площадки, который удовлетворял бы следуюшим требованиям:

  • выкат должен происходить без перерывов в работе (без downtime);
  • если в процессе выката возникнут ошибки, выкат должен быть прерван и старая версия приложения должна продолжить работу;
  • должна быть возможность при необходимости откатиться к предыдущей версии веб-приложения;
  • процедура выката должна хорошо масштабироваться с увеличением числа серверов на площадке.

Метод

Один из способов выката без downtime основан на использовании специальной структуры каталогов. Этот способ, в частности, используется в Capistrano. Я впервые познакомился с ним в замечательном автосборщике Haru.

Каждая новая версия приложения выкатывается в отдельный каталог. Например, если сейчас выкачена версия v1.0.0, структура каталогов выглядит следующим образом:

/vagrant
├── releases
│   ├── current -> /vagrant/releases/v1.0.0
│   └── v1.0.0
│       └── index.html
├── shared
└── Vagrantfile

Папка releases содержит версии приложения. Каждая версия – копия приложения, соответствующая определенному тегу из репозитория. Символическая ссылка current указывает на текущую версию.

Файлы, которые должны быть доступны независимо от версии, например, загруженные пользователем картинки, сгенерированные файлы и т.п., помещаются в папку shared.

Процесс выката делится на три этапа:

  1. install: новая версия приложения извлекается из репозитория в отдельный каталог рядом с текущей версией;
  2. configure: на основе шаблонов генерируются необходимые файлы;
  3. link: переключение на новую версию путем изменения символической ссылки.

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

После успешного выката версии v1.0.1 структура каталогов примет вид:

/vagrant
├── releases
│   ├── current -> /vagrant/releases/v1.0.1
│   ├── v1.0.0
│   │   └── index.html
│   └── v1.0.1
│       └── index.html
├── shared
└── Vagrantfile

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

Реализация

Исходники сценарие можно посмотреть здесь.

Для выката версии приложения, помеченной тегом v1.0.1, можно воспользоваться следующей командой:

ansible-playbook -i develop deploy/webapp.yml -e deploy_tag=v1.0.1

Сценарий /vagrant/deploy/webapp.yml создает необходимые каталоги и запускает роль library, которая собственно и выкатывает приложение. В данном сценарии выкатывается только одна библиотека, но роль можно использовать несколько раз в одном сценарии, передавая URL репозитария и нужный тег через параметры.

- hosts: webapp
  any_errors_fatal: true

  pre_tasks:
    - name: make sure shared directories exist
      file: dest={{ item }} state=directory
      with_items:
        - "{{ path_releases }}"
        - "{{ path_shared }}"

  roles:
    - role: library
      library_name: example
      library_deploy_tag: "{{ deploy_tag }}"
      library_deploy_src: https://github.com/sergeytsivin/example-lib.git

Скрипт выката описан в файле ansible/deploy/roles/library/tasks/main.yml:

- name: checkout {{ library_name }} lib
  git:
    dest: "{{ library_deploy_dest }}"
    repo: "{{ library_deploy_src }}"
    version: "{{ library_deploy_tag }}"
    accept_hostkey: true
  tags:
    - install

- name: schedule linking {{ library_name }} lib
  command: /bin/true
  notify:
    - link {{ library_name }}
  tags:
    - link

Скрипт очень простой: извлекается версия из репозитория (этап install) и уведомляется обработчик, который должен произвести произвести переключение на новую версию (этап link). Использование обработчика гарантирует, что линковка всех библиотек (если их несколько) будет выполнена после того, как все библиотеки будут успешно установлены и ни один обработчик не будет вызван в случае возникновения ошибок.

Ansible последовательно выполняет команды сценария (playbook), но при этом каждая команда выполняется параллельно на нескольких машинах.

Обработчик ansible/deploy/roles/library/handlers/main.yml переключает символическую ссылку current:

- name: link {{ library_name }}
  file: src={{ item.src }} dest={{ item.dest }} state=link
  with_items:
    - src: "{{ library_deploy_dest }}"
      dest: "{{ library_deploy_current }}"

Благоларя тегам можно запускать этапы выката по отдельности, например для выполнения только этапа установки:

ansible-playbook -i develop deploy/webapp.yml -e deploy_tag=v1.0.1 --tags install

Вывод

Таким образом, с помощью ansible можно реализовать простой и удобный механизм выката, не прибегая к помощи пециализированных инструментов, таких как Capistrano. Дополительное преимущество Ansible – скорость выката на площадки, состоящие из многих серверов.

Я уже писал о том, как установить Jekyll на Ubuntu. Пришло время написать об установке на Mac OS X 10.11.4 (El Capitan).

На самом деле все очень просто и пост будет очень короткий.

Устанавливаем свежую версию ruby (установленная в комплекте 2.0 не подходит):

$ brew install ruby
$ ruby --version
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

Если показывается старая версия – перезапустите терминал.

Устанавливаем bundler:

$ gem install bundler
Fetching: bundler-1.12.5.gem (100%)
Successfully installed bundler-1.12.5
Parsing documentation for bundler-1.12.5
Installing ri documentation for bundler-1.12.5
Done installing documentation for bundler after 4 seconds
1 gem installed

Напомню содержимое Gemfile:

$ cat Gemfile
source 'https://rubygems.org'
gem 'github-pages'

Устанавливаем Jekyll:

$ bundle install
...
$ bundle exec jekyll -v
jekyll 3.1.6

Done :-)

Мой журнал использует Jekyll - программу для генерации сайтов на основе шаблонов, но без использования базы данных.

Чем привлекателен Jekyll? Вот некоторые его преимущества:

  • jekyll поддерживается GitHub Pages, таким образом Вы можете хостить свой сайт на Github абсолютно бесплатно;
  • jekyll использует привычные технологии: разметку markdown, шаблоны а-ля Django и CSS-препроцессор;
  • сайты, сгенерированные jekyll, работают очень быстро – ведь это просто статические страницы.

С jekyll легко начать работать, если он уже установлен на Вашем компьютере. В дистрибутив Ubuntu включена устаревшая версия jekyll. Если Вы планируете хостить свой сайт на Github Pages, желательно использовать соответствующую версию на локальном компьютере для пердварительного просмотра изменений перед публикацией. Для знатоков Ruby не составит труда установить необходимые версии ПО, для остальных может быть полезна эта заметка.

Установка Jekyll на Ubuntu 12.04

Ubuntu 12.04 поставляется с пакетом ruby-1.8 и ruby-1.9. На момент написания этой статьи git hub pages требовала использования Ruby 2.1+

Простой способ установить свежий Ruby из пакетов описан здесь:

sudo apt-add-repository ppa:brightbox/ruby-ng
sudo apt-get update
sudo apt-get install ruby2.1 ruby2.1-dev libssl-dev zlib1g-dev build-essential

Последние четыре пакета понадобятся для компиляции устанавливаемых gems.

Теперь устанавливаем bundler:

sudo gem install bundler

Создадим Gemfile:

source 'https://rubygems.org'
gem 'github-pages'
gem 'therubyracer'

Пакет therubyracer, встроенный в ruby интерпретатор javascript, необходим для запуска jekyll. Если не установить его, при запуске jekyll вываливается ошибка:

stsivin@tough:/www/sergeytsivin.github.io$ jekyll --version
/var/lib/gems/2.1.0/gems/execjs-2.4.0/lib/execjs/runtimes.rb:45:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)
    from /var/lib/gems/2.1.0/gems/execjs-2.4.0/lib/execjs.rb:5:in `<module:ExecJS>'
    from /var/lib/gems/2.1.0/gems/execjs-2.4.0/lib/execjs.rb:4:in `<top (required)>'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /var/lib/gems/2.1.0/gems/coffee-script-2.3.0/lib/coffee_script.rb:1:in `<top (required)>'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /var/lib/gems/2.1.0/gems/coffee-script-2.3.0/lib/coffee-script.rb:1:in `<top (required)>'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /var/lib/gems/2.1.0/gems/jekyll-coffeescript-1.0.1/lib/jekyll-coffeescript.rb:2:in `<top (required)>'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /var/lib/gems/2.1.0/gems/jekyll-2.4.0/lib/jekyll/deprecator.rb:46:in `block in gracefully_require'
    from /var/lib/gems/2.1.0/gems/jekyll-2.4.0/lib/jekyll/deprecator.rb:44:in `each'
    from /var/lib/gems/2.1.0/gems/jekyll-2.4.0/lib/jekyll/deprecator.rb:44:in `gracefully_require'
    from /var/lib/gems/2.1.0/gems/jekyll-2.4.0/lib/jekyll.rb:141:in `<top (required)>'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
    from /var/lib/gems/2.1.0/gems/jekyll-2.4.0/bin/jekyll:6:in `<top (required)>'
    from /usr/local/bin/jekyll:23:in `load'
    from /usr/local/bin/jekyll:23:in `<main>'

Теперь осталось установить необходимые gems:

bundler install

Если все прошло хорошо, можно запустить jekyll:

$ jekyll --version
jekyll 2.4.0

UPDATE 7 Feb 2016:

С тех пор, как написана эта заметка, многое изменилось:

  • вышел jekyll 3.0 и GitHub pages теперь использует эту версию
  • на моем компьютере установлена версия Ubuntu 14.04 (а скоро выйдет 16.04 :-) )

Ubuntu 14.04 все еще содержит устаревшую версию jekyll и ruby, таким образом, статья все еще актуальна. Свежие пакеты Ruby по-прежнему можно скачать из репозитория brightbox, но уже вышла версия Ruby 2.2 и даже Ruby 2.3 (я остановился на использовании 2.2):

sudo apt-add-repository ppa:brightbox/ruby-ng
sudo apt-get update
sudo apt-get install ruby2.2 ruby2.2-dev libssl-dev zlib1g-dev build-essential

Теперь устанавливаем bundler:

sudo gem install bundler

Создадим Gemfile – ‘therubyracer’ больше не требуется:

source 'https://rubygems.org'
gem 'github-pages'

Если Вы ранее устанавливали jekyll, в Вашем репозитарии, наверняка, есть Gemfile.lock. Чтобы получить последние версии пакетов, используйте команду:

bundle update

Запустите jekyll:

bundle exec jekyll -v
jekyll 3.0.2