Интеграция Jenkins и Vagrant для сборки проектов в виртуальном окружении
Недавно передо мной встала задача автоматизировать сборку одного проекта, разработанного на Haskell-фреймворке Yesod. Особенных проблем с разработкой в гетерогенном окружении (Windows + Linux) не возникало, а все подводные камни успешно обходились, пока дело не дошло до разворачивания проекта в рабочем окружении. В качестве хостинга мы используем DigitalOcean — вполне хорошее и дешёвое решение, и к нему тоже до сих пор никаких претензий до сих пор не возникало. Но вот на этот раз возникли проблемы...
Haskell компилируется в нативный код. Это значит, во-первых, что получаемые бинарники достаточно быстро работают (что
хорошо), а во-вторых — что они требуют компиляции в среде, приближенной к реальной (что уже не так хорошо) или
кросскомпиляции. Ранее мы применяли очень простую практику — для простеньких "домашних" проектов стенд, на котором
приложение развёрнуто, сам его и компилирует. Пока мы работали со Scala и sbt
— это вызывало минимальные проблемы
(пришлось немножко подтюнить shell-скрипт для sbt
, чтобы он отдавал JVM побольше системной памяти). А вот с Haskell не
срослось совсем — для сборки простейшего сайта на Yesod (а, вернее, каких-то нужных для него библиотек) требуется более
1 гигабайта виртуальной памяти! Когда cabal
(сборочный инструмент Haskell) эту память не может найти, он просто
падает.
Ну что ж, прошлось подходить к проблеме более серьёзно. Сборку приложения было решено проводить в контролируемых условиях, а выкладывать только бинарники. Проще всего с точки зрения поддержки проводить сборку на той же операционной системе, на которой приложение впоследствии запускается. Значит, будем для сборки использовать 64-битную Ubuntu 14.04 Trusty.
Какой самый удобный способ иметь портабельную повторяемую контролируемую среду с другой операционной системой? Конечно же, это Vagrant! А какое средство является самым приемлемым для того, чтобы контролировать процесс сборки? Конечно же, это Jenkins.
В этом посте я расскажу, как их друг с другом подружить, ну и организовать полноценное разворачивание приложения с виртуального стенда Vagrant на облако DigitalOcean (разумеется, рекомендации будут пригодны и для других облачных провайдеров и просто машин).
Настраиваем целевую машину
Я — сторонник подхода Continous Deployment, когда свежие версии программного обеспечения автоматически разворачиваются на рабочих машинах прямо из репозитория с кодом. Поэтому важной частью интеграции является настройка целевой машины (т.е. машины в облаке DigitalOcean) для того, чтобы она могла безопасно принимать сборки приложения со сборочного стенда.
Встречаются рекомендации об организации собственного репозитория с dev-пакетами, куда сборочная машина бы складывала обновления, а некая активность на целевой машине бы их оттуда загружала и переустанавливала. Я пока что не достиг такого мастерства в Linux-инфраструктуре, чтобы разворачивать собственные репозитории с пакетами, так что используем более простой способ — старый добрый проверенный SSH.
В первую очередь стоит создать нужных пользователей на целевой машине, назначить им права и выдать пользователю, который будет осуществлять деплой, доступ по SSH. Если для обновления вашего приложения требуется перезапускать какие-то сервисы, то этому пользователю также следует выдать разрешение на их перезапуск.
Я не буду давать конкретных советов по настройке этого аспекта, а просто дам ссылку на инструкцию в сети. Приватную часть ключа нужно будет передать на виртуальную машину-сборщик, а публичную — оставить в облаке.
Настройка Vagrant
Теперь приступим к настройке Vagrant (первым делом его, конечно же, следует установить). Базовым механизмом настройки
подотчётных виртуальных машин Vagrant является т.н. provision — это
когда Vagrant выполняет пользовательские скрипты в окружении виртуальной машины, или предпринимает другие действия по её
настройке (в зависимости от провайдера — помимо shell поддерживаются chef и puppet). Ну а общие параметры виртуальной
машины можно настроить прямо в Vagrantfile
— основном файле любого образа Vagrant. Вот пример того Vagrantfile
,
который я применяю для сборки своего проекта:
# -*- mode: ruby -*-
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.provision "shell", path: "provision.sh"
config.vm.provider "virtualbox" do |vb|
vb.name = "vagrant-trusty64"
vb.memory = 2048
end
# Disable the default ssh forwarding and enable another one
config.vm.network :forwarded_port, guest: 22, id: "ssh", disabled: true
config.vm.network :forwarded_port, guest: 22, host: 2210
end
Особенно важными являются строки config.vm.network
— они отключают умолчальное перенаправление порта ssh
и форсят
его перенаправление на порт 2210. По умолчанию Vagrant перенаправляет порт sshd
виртуальной машины на 2222 порт
хоста. Если этот порт занят — он использует другой свободный порт. Если же у вас несколько виртуальных машин запускается
одновременно, то в итоге бывает сложно предсказать, кто из них на каком порту сидит. Поэтому важно фиксировать порт, по
которому будет доступен сервер ssh виртуальной машины.
А вот скрипт provision.sh, который отвечает за установку пакетов и настройку окружения виртуальной машины (обратите
внимание, этот скрипт будет запущен от имени root
):
# Копируем ssh-ключи и настраиваем known hosts, чтобы работал git:
cp /vagrant/id_rsa /home/vagrant/.ssh/
cp /vagrant/id_rsa.pub /home/vagrant/.ssh/
cp /vagrant/known_hosts /home/vagrant/.ssh/
chown -R vagrant /home/vagrant/.ssh/
chmod 700 /home/vagrant/.ssh/ && chmod 600 /home/vagrant/.ssh/*
# Устанавливаем пакеты для того, чтобы пользоваться свежим компилятором Haskell, и обновляем инструменты:
sudo add-apt-repository -y ppa:hvr/ghc
apt-get update
apt-get install -y cabal-install-1.22 ghc-7.8.3 git postgresql postgresql-server-dev-9.3 openjdk-7-jre-headless zlib1g-dev
ln -s /usr/bin/cabal-1.22 /usr/bin/cabal
echo export PATH=/opt/ghc/7.8.3/bin:/home/vagrant/.cabal/bin:$PATH >> /home/vagrant/.profile
sudo -Hu vagrant PATH=/opt/ghc/7.8.3/bin:/home/vagrant/.cabal/bin:$PATH cabal update
sudo -Hu vagrant PATH=/opt/ghc/7.8.3/bin:/home/vagrant/.cabal/bin:$PATH cabal install alex happy yesod-bin
# Создаём postgresql-окружение, нужное для тестирования:
sudo -u postgres psql -c "create user \"codingteam-site\" password 'codingteam-site';"
sudo -u postgres psql -c "create database \"codingteam-site_test\" owner \"codingteam-site\" encoding 'utf-8';"
# Создаём workspace для Jenkins:
mkdir /opt/jenkins
chown vagrant /opt/jenkins
Настройка Jenkins
Наконец-то переходим к самому главному — к настройке Jenkins. Jenkins сам по себе умеет работать в режиме кластера - когда есть один главный узел, управляющий группой дочерних slave'ов. Запуск и взаимодействие со slave-ами может происходить в различных режимах, но нас интересует один из них — когда на slave по SSH закачивается специальная программа, которая и будет управлять slave'ом.
Однако прежде, чем мы сможем подключиться по SSH, нужно сначала запустить машину. Для этой цели есть несколько "облачных" плагинов для Jenkins, которые реализуют взаимодействие с различными облачными провайдерами, а также Vagrant и т.н. Scripted Cloud. К сожалению, плагин с поддержкой Vagrant на текущий момент морально устарел, так что мы будем использовать именно Scripted Cloud. Он позволяет осуществлять интеграцию с любого рода внешними исполнителями, которых можно запустить и остановить при помощи скриптов.
Для начала составим скрипты для запуска и остановки облака. Я использую для них PowerShell и размещаю их прямо в
каталоге с Vagrantfile
. Вот эти скрипты:
# Start.ps1
cd $PSScriptRoot
vagrant up
# Stop.ps1
cd $PSScriptRoot
vagrant halt
Остаётся только создать новое "облако" с помощью Scripted Cloud, указать ему эту пару скриптов, отметить параметры подключения — и всё, система готова к работе. Scripted Cloud можно настроить таким образом, чтобы он стартовал машину по требованию и выключал её, когда она не нужна.
Разворачивание собранных артефактов
Для разворачивания собранных артефактов я использую стандартные программы ssh
и scp
для копирования артефактов и
перезапуска сервисов. Вот пример скрипта, который обновляет артефакты приложения и перезагружает его:
ssh -p 2222 user@remotecomputer.ru sudo /sbin/stop site-user || true
ssh -p 2222 user@remotecomputer.ru rm -rf /opt/site/*
scp -P 2222 -r static user@remotecomputer.ru:/opt/site/static
ssh -p 2222 user@remotecomputer.ru mkdir /opt/site/config
scp -P 2222 config/client_session_key.aes user@remotecomputer.ru:/opt/site/config/client_session_key.aes
scp -P 2222 dist/build/site/site user@remotecomputer.ru:/opt/site/site
ssh -p 2222 user@remotecomputer.ru sudo /sbin/start site
Заключение
Управление облачной сборочной средой — вовсе не такое уж сложное дело, и на самом деле достаточно просто можно автоматизировать даже комплексные сценарии разворачивания приложений и сайтов. При этом такого рода автоматизация значительно сокращает количество ручной работы и, как следствие, количество человеческих ошибок при разворачивании.