Ускоряем процесс разработки с помощью Vagrant

    image Как часто вам приходится разрабатывать и запускать приложение локально и упорно искать проблемы, потому что на продакшене приложение ведёт себя не совсем так, как вы этого хотели? А как часто вам присылают тикеты для решения проблемы в приложении, хотя на самом деле проблема именно в несовместимости версий разных приложений? А как долго вам приходится ждать виртуалку, когда для запуска новой версии приложения недостаточно ресурсов локальной машины? Для нас эти вопросы были довольно больными, и мы сломали тысячи копий в спорах, стараясь решить их. Практика показывает, что одним из вариантов для решения этих проблем может стать 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, просто в этом репозитории немного нестандартное расположение самого модуля. Но можно также использовать плагин Vagrant'a для librarian-puppet github.com/mhahn/vagrant-librarian-puppet

    Конечный вид 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(TM) SE Runtime Environment (build 1.7.0_55-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
    


    А также можно открыть WebUI RabbitMQ сервиса:


    Конечно, можно поискать нужные модули и с помощью Puppet установить всё, что душе угодно. Благо коммьюнити у Puppet довольно активное и большое. А также можно написать нужный модуль самому и использовать его. Выше дан лишь пример для одного сервиса и одной машины. Их может быть несчётное множество.

    Для ленивых в качестве бонуса файлики выложил на гитхаб: github.com/wl4n/vagrant-skel

    Надеюсь, эта статья поможет Вам уделять больше времени изменению кода, а не настройке окружения для него, и избавит от необходимости исправлять и воспроизводить магические баги Production-environment'а.

    Ссылка на аналогичную статью, но с использованием Chef Solo (из комментариев): habrahabr.ru/post/140714
    Maxifier Development
    34.72
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      +1
      1. Обычно вагрант хранится в отдельной репе (как и у вас, собственно), поэтому модули клонируются и хранятся в ней же (например, как git submodules). Происходит это соответственно на хостовой машине, запускающей вагрант. Это дает предсказуемость процесса (т.к. на момент запуска виртуалки мы знаем какая версия модуля у нас развернута) и отсутствие таких костылей как у вас (sh-скрипт). А уже по этим модулям puppet ставит git, но уже для нужд проектов, а не для себя самого.
      Ещё вариант — использовать плагины вагранта, например этот.
      2. Также не понятно, как вы решаете проблемы изолирования окружения для различных приложений. Если запуском нескольких виртуалок, то это довольно тяжело для хостовой машине разработчика будет. Мы для решения этой проблемы у себя пришли к использованию Docker внутри Vagrant.
        0
        1. Да, git submodules вариант, но как решение мне не очень нравится. Хочется иметь возможность не только с гита, но и с форжа ставить модули. Ну и в примере в статье был модуль oracle java в котором структура репозитория не совсем подходящая для такого использования.

        Вот за ссылку на плагин для вагранта спасибо, почему-то раньше я не видел его. Добавлю в статью.

        2. >Также не понятно, как вы решаете проблемы изолирования окружения для различных приложений.

        Вот это действительно загвоздка, нужно либо очень крутое железо, чтобы тянуло N виртуалок, либо как я уже сказал использовать другие провайдеры отличные от VB.

        >Мы для решения этой проблемы у себя пришли к использованию Docker внутри Vagrant.

        Да это тоже выход, но он убирает только затраты ресурсов на виртуализацию, если приложения достаточно прожорливые, то ситуацию не сильно спасет. Кстати совсем недавно Vagrant научился в Docker из коробки docs.vagrantup.com/v2/provisioning/docker.html
          0
          Добавлю ссылку на статью Быстрое развертывание среды разработки, тут больше про вариант с Chef-Solo.
            +1
            Добавил ссылку в статью.
            0
            Адово страдаю от того, что не могу на своей рабочей машине с древней убунтой 4летней давности (но идеально удобной в работе) использовать совместно с KVM vagrant. По крайней мере, за день победить это не смог.
              0
              К сожалению под KVM я не заводил Vagrant, может быть Вы кинете лог и ошибки в личку, то возможно смогу помочь.
              0
              Еще можно поднять себе маленькое Devstack облако на железе и поднимать виртуалки при помощи vagrant-openstack плагина.
                0
                Интересная штука этот DevStack, спасибо за ссылку.
                0
                Если смотреть с точки зрения альтернативной реализации подобного воркфлоу, то все тоже самое можно очень просто реализовать через связку FreeBSD+ezjail+ZFS snapshots.
                  0
                  Да возможен и такой враиант, но это уже скорее дело вкуса и предпочтений. И связка Vagrant+Docker выглядит все же удобнее, на мой взгляд.

                Only users with full accounts can post comments. Log in, please.