Ускоряем процесс разработки с помощью Vagrant
Как часто вам приходится разрабатывать и запускать приложение локально и упорно искать проблемы, потому что на продакшене приложение ведёт себя не совсем так, как вы этого хотели? А как часто вам присылают тикеты для решения проблемы в приложении, хотя на самом деле проблема именно в несовместимости версий разных приложений? А как долго вам приходится ждать виртуалку, когда для запуска новой версии приложения недостаточно ресурсов локальной машины? Для нас эти вопросы были довольно больными, и мы сломали тысячи копий в спорах, стараясь решить их. Практика показывает, что одним из вариантов для решения этих проблем может стать Vagrant. Vagrant — это что-то вроде обёртки над системой виртуализации или, если угодно — DSL. Наиболее часто используют VirtualBox, но драйвера есть и для VmWare и даже для Amazon EC2. Более подробную информацию, а так же как его установить и начать работу, можно найти на официальном сайте — www.vagrantup.com
Vagrant и тестирование окруженияУ нас есть несколько собственных приложений, которые общаются между собой через шину RabbitMQ и используют такие вещи, как MongoDB и MySQL. Тестирование отдельных приложений, запускаемых в синтетической среде, далекой от реального окружения, не показывало нам поведения связки приложений в реальном окружении при обновлении какого-либо одного элемента. А изменения в окружении вообще никак не показывало, потому что QA и staging окружения были далеки от реального. Конечно, это не избавляет нас от необходимости проводить модульное тестирование.Поробуем решить эту проблему с помощью Vagrant.
Для наглядности покажу такую схему:
Описание вокрфлоу:
Разработчик вносит какие-либо изменения окружения в репозиторий и отправляет код на review другим разработчикам; TeamCity забирает изменения, прогоняет модульные тесты; TeamCity запускает Vagrant на BuildAgent’е, который инициирует запуск нескольких виртуальных машин и запуск Puppet агента; На виртуальные машины накатывается изменённое окружение и стабильные версии тестируемых приложений; Запускаются тесты, или QA проверяет работу приложения; Это может помочь проверить до отправки на Product изменения в environment: работу новых версий сторонних приложений, Puppet-манифесты, кастомные скрипты, потерю связи приложений, падения сервисов, эмулирование бизнес-процессов и тд. А еще мы точно не получим ситуации в стиле «ааа, мы забыли поставить пакет на сервер» или «блин, АПИ изменили, и теперь приложения друг с другом не работают». Ну и стоит обратить внимание, что в этой схеме используется Puppet Master вместо Puppet Standalone, именно для повторения реального окружения.В нашем случае осложнением задачи является то, что могут быть использованы Amazon EC2, Openstack или вообще VmWare ESXi, что опять не поможет отловить баги и ситуации, связанные с ними. Единственным выходом в этой ситуации я вижу использование всей этой связки, но с провайдером, отличным от VirtualBox. А из этого есть небольшой минус — заставить Vagrant работать с другими провайдерами не всегда тривиальная задача. Ну и еще, не всегда есть машинка для билдагента, которая потянет на себе N виртуалок с приложениями, которым нужно очень много ресурсов для работы.
Ну, а вся прелесть заключается в том, что у нас есть проект в репозитории, у которого есть история изменений, которые проходят review и все изменения, вплоть до установки вима, тестируются до попадания на продакшен. Ну и никто не мешает нам таким же образом проверять и внешние ресурсы, обновлять базы данных, шину и всякие фронтенды на node.js. Все зависит от фантазии и желания.
Дальше в статье будет описано, как использовать Vagrant, чтобы реализовать приведенную схему.
Запуск простого сервиса Обычный usecase для Vagrant: поднять сервис и окружение и посмотреть, как работает мой код в этом окружении. Особенно если требуется посмотреть какую-то платфомозависимую штуку. Ну и плюс, Vagrant позволяет не разводить зоопарк на рабочей машине, не держать одновременно пачки софта и сервисов разных версий для разных потребностей и проектов.Одна из самых приятных фич для меня — это поддержка Puppet, как в master, так и в standalone режимах. Конечно, Vagrant умеет и Chef, но мы в нашей компании используем Puppet, поэтому для меня выбор очевиден. Я буду использовать Puppet Standalone, или по-простому puppet apply.
Первым делом переходим в каталог проекта и делаем vagrant init, в результате получаем файл Vagrantfile, который содержит описание наших виртуалок. Я предпочитаю использовать один раз настроенный файл и копировать его из проекта в проект, изменяя необходимые секции и конфиги. В файле ниже Vagrantfile одной VM. Например, настроим RabbitMQ и установим Oracle Java.
Vagrantfile: # -*- mode: ruby -*- # vi: set ft=ruby:
# Vagrantfile API/syntax version. Don’t touch unless you know what you’re doing! VAGRANTFILE_API_VERSION = »2»
Vagrant.configure (VAGRANTFILE_API_VERSION) do |config| # All Vagrant configuration is done here. The most common configuration # options are documented and commented below. For a complete reference, # please see the online documentation at vagrantup.com.
# Every Vagrant virtual environment requires a box to build off of. config.vm.box = «precise64»
# The url from where the 'config.vm.box' box will be fetched if it # doesn’t already exist on the user’s system. # config.vm.box_url = «http://domain.com/path/to/above.box»
config.vm.box_url = «http://files.vagrantup.com/precise64.box»
# Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing «localhost:8080» will access port 80 on the guest machine. # config.vm.network: forwarded_port, guest: 3000, host: 3000
# Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network: private_network, ip:»192.168.33.10»
# Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network: public_network
# If true, then any SSH connections made will enable agent forwarding. # Default value: false config.ssh.forward_agent = true
### Define VM for RabbitMQ config.vm.define «rmq», primary: true do |rmq|
# Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # rmq.vm.provider: virtualbox do |vb| # Don’t boot with headless mode vb.gui = false
# Use VBoxManage to customize the VM. For example to change memory: vb.customize [«modifyvm», : id,»--memory»,»1024»] end
# Networking options rmq.vm.network: private_network, ip:»192.168.100.5» rmq.vm.hostname = «rmq.example.com» end end Этого конфигурационного файла будет достаточно для запуска машины с обычной Ubuntu 12.04 LTS без каких-либо установленных сервисов и программ. Но я человек ленивый, и мне не хочется каждый раз руками устанавливать и настраивать софт для на виртуалке, а хочется не тратить время и сразу начать гонять код или проводить какие-либо тесты.Добавим в Vagrantfile в описание ноды секцию:
rmq.vm.provision: puppet do |puppet| puppet.manifests_path = »./vagrant.d/manifests» puppet.manifest_file = «site-rmq.pp» puppet.module_path = »./vagrant.d/modules» puppet.options = »--fileserver=/vagrant/vagrant.d/fileserver.conf --verbose --debug» end И, соответственно, нужно создать в нашем проекте каталоги ./vagrant.d/modules, ./vagrant.d/manifests, ./vagrant.d/files и файлы ./vagrant.d/manifests/site-rmq.pp» и ./vagrant.d/fileserver.conf.
Каталог modules содержит файлы модулей, которые мы будем использовать; каталог manifests содержит манифест site-rmq.pp Puppet’а нашей виртуалки; files — каталог с файлами, которыми управляет fileserver; fileserver.conf — файл для использования fileserver и подкладывания каких-либо специфичных файлов в нашу виртуалку.
Но если мы хотим использовать сторонние модули для Puppet’a, то их нужно установить перед запуском puppet apply. Для этого я ничего лучше, чем использовать скрипт, не придумал; если есть умельцы, которые расскажут мне, как поставить модули, а потом использовать их в манифестах с помощью одного только Puppet’а, буду признателен. =)
Добавляем секцию для провизионинга виртуалки shell-скриптом:
# Enable shell provisioning config.vm.provision «shell», path:»./vagrant.d/pre-puppet.sh» И создаем скрипт в каталоге ./vagrant.d:
pre-puppet.sh: #!/bin/bash
# This script installs modules for puppet standalone
echo »[Info] Running pre-puppet.sh for install modules»
if [ «x$(dpkg -l | grep -E '^ii\s+git\s')» == «x» ] then echo »[Info] Installing git» apt-get -y install git || (echo »[Error] Cant install git» && exit 0) else echo »[Info] git already is installed, skipping» fi
if [ «x$(gem list librarian-puppet|grep -v LOCAL)» == «x» ] then echo »[Info] Installing librarian-puppet» gem install librarian-puppet || (echo »[Error] Cant install librarian-puppet» && exit 0) else echo »[Info] librarian-puppet already is installed, skipping» fi
if [ ! -e Puppetfile ] then
cat > Puppetfile << EOF #!/usr/bin/env ruby #^syntax detection
# Warning! # Do not edit this file, check pre-puppet.sh script! #
forge «http://forge.puppetlabs.com»
# use dependencies defined in Modulefile #modulefile mod 'puppetlabs/rabbitmq' mod 'saz/timezone' mod 'saz/locales' mod 'jpuppet/java-git', : git => «git://github.com/jpuppet/java.git» mod 'jfryman/nginx' EOF
fi
mkdir -p /vagrant/vagrant.d/modules echo »[Info] Installing puppet modules» librarian-puppet install --path=/vagrant/vagrant.d/modules/ || (echo »[Error] Cant install modules» && exit) rm Puppetfile*
# Ugly hack for java cp -r /vagrant/vagrant.d/modules/java-git/modules/java /vagrant/vagrant.d/modules
exit 0 В скрипте с репозитория ставятся git и librarian-puppet и потом с помощью librarian-puppet устанавливаются нужные нам модули. Более подробно о возможностях librarian-puppet можно найти на github: github.com/rodjek/librarian-puppet Важно установить git заранее, иначе мы не сможем использовать librarian-puppet для установки модулей с git-репозиториев. А так же пришлось сделать небольшой хак с копированием модуля для установки Java, просто в этом репозитории немного нестандартное расположение самого модуля.Конечный вид Vagrantfile: # -*- mode: ruby -*- # vi: set ft=ruby:
# Vagrantfile API/syntax version. Don’t touch unless you know what you’re doing! VAGRANTFILE_API_VERSION = »2»
Vagrant.configure (VAGRANTFILE_API_VERSION) do |config| # All Vagrant configuration is done here. The most common configuration # options are documented and commented below. For a complete reference, # please see the online documentation at vagrantup.com.
# Every Vagrant virtual environment requires a box to build off of. config.vm.box = «precise64»
# The url from where the 'config.vm.box' box will be fetched if it # doesn’t already exist on the user’s system. # config.vm.box_url = «http://domain.com/path/to/above.box»
config.vm.box_url = «http://files.vagrantup.com/precise64.box»
# Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing «localhost:8080» will access port 80 on the guest machine. # config.vm.network: forwarded_port, guest: 3000, host: 3000
# Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network: private_network, ip:»192.168.33.10»
# Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network: public_network
# If true, then any SSH connections made will enable agent forwarding. # Default value: false config.ssh.forward_agent = true
# Enable shell provisioning config.vm.provision «shell», path:»./vagrant.d/pre-puppet.sh»
### Define VM for RabbitMQ config.vm.define «rmq», primary: true do |rmq|
# Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # rmq.vm.provider: virtualbox do |vb| # Don’t boot with headless mode vb.gui = false
# Use VBoxManage to customize the VM. For example to change memory: vb.customize [«modifyvm», : id,»--memory»,»1024»] end
# Networking options rmq.vm.network: private_network, ip:»192.168.100.5» rmq.vm.hostname = «rmq.example.com»
# Enable provisioning with Puppet stand alone. Puppet manifests # are contained in a directory path relative to this Vagrantfile. # You will need to create the manifests directory and a manifest in # the file base.pp in the manifests_path directory. # rmq.vm.provision: puppet do |puppet| puppet.manifests_path = »./vagrant.d/manifests» puppet.manifest_file = «site-rmq.pp» puppet.module_path = »./vagrant.d/modules» puppet.options = »--fileserver=/vagrant/vagrant.d/fileserver.conf --verbose --debug» end end end Теперь мы можем описать нашу ноду в манифесте: site-rmq.pp: # # This manifest describes development environment # RabbitMQ-server #
class { 'timezone': timezone => 'Europe/Moscow', }
class { 'locales': locales => ['ru_RU.UTF-8 UTF-8'], }
# apt-get update # --------------------------------------- class apt_install { exec {'update': command => 'apt-get update', path => '/usr/bin', timeout => 0, } → package {[ 'vim', ]: ensure => installed, } }
# RabbitMQ service class rabbitmq_install { class { ':: rabbitmq': service_manage => false, port => '5672', delete_guest_user => true, } rabbitmq_user { 'developer': admin => true, password => 'Password', } rabbitmq_vhost { 'habr': ensure => present, } rabbitmq_user_permissions { 'developer@habr': configure_permission => '.*', read_permission => '.*', write_permission => '.*', } rabbitmq_plugin {'rabbitmq_management': ensure => present, } }
class java_install { class { «java»: version => »1.7», jdk => true, jre => true, sources => false, javadoc => false, set_as_default => true, export_path => false, vendor => «oracle», } }
# Include classes include apt_install include timezone include locales include rabbitmq_install include java_install Теперь достаточно в каталоге нашего проекта сделать: vagrant up и подождать несколько минут, в зависимости от скорости подключения к интернету. (При первом запуске выкачается образ Ubuntu)Теперь можно зайти в нашу виртуалку:
vagrant ssh И проверить, например, установленную Java:
> vagrant ssh Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0–23-generic x86_64)
* Documentation: https://help.ubuntu.com/ Welcome to your Vagrant-built virtual machine. Last login: Sat May 17 12:28:08 2014 from 10.0.2.2 vagrant@rmq:~$ java -version java version »1.7.0_55» Java™ SE Runtime Environment (build 1.7.0_55-b13) Java HotSpot™ 64-Bit Server VM (build 24.55-b03, mixed mode) А так же можно открыть WebUI RabbitMQ сервиса:
Конечно, можно поискать нужные модули и с помощью Puppet установить всё, что душе угодно. Благо коммьюнити у Puppet довольно активное и большое. А так же можно написать нужный модуль самому и использовать его. Выше дан лишь пример для одного сервиса и одной машины. Их может быть несчётное множество.
Для ленивых в качестве бонуса файлики выложил на гитхаб: github.com/wl4n/vagrant-skel
Надеюсь, эта статья поможет Вам больше уделять время на изменения кода, а не настройку окружения для него, и избавит от необходимости исправлять и воспроизводить магические баги Production-environment’а.