Недавно появилась необходимость автоматизировать внедрение приложений из GIT на сервера.
В данной статье я решил описать свой опыт внедрения.
Поступило интересное задание по установке/обновлению приложения на серверах компании имея следующие данные:
* Приложение расположено в GIT
* Версия приложения можно узнать командой "git tag"
* Список серверов и путь где должно находиться приложение
Поскольку исторически так сложилось, что в компании используется RPM-based OS, то, IMHO, в данном случае наиболее правильным решением было реализовать упаковку приложения в RPM-пакет с последующим распространением его через puppet. Соответственно puppet устанавливает ПО и накатывает необходимый конфигурационный файл по шаблону.
Процесс настройки системы:
- Настройка yum-репозитория
- Установка и настройка nginx
- Скрипт автоматического build с размещением его в yum-репозиторий
- Автоматическая проверка обновлений и привязка к созданию build
- Настройка puppet
Настройка yum-репозитория
Тут оказалось всё просто. Поскольку мы собираемся использовать внутренний yum-репозиторий с доступом только с наших серверов, то подписывать его PGP/GPG-ключами нет необходимости.
Создаём папку где будут располагаться наши RPM-пакеты и создаём пастой репозиторий:
# yum install createrepo
# mkdir -p /storage/v0.repo/6/x86_64/RPMS/
# ln -s /storage/v0.repo/6 /storage/v0.repo/6.2
# createrepo /storage/v0.repo/6/x86_64
Создаём конфигурационный файл /etc/yum.repos.d/v0.repo со следующим содержанием:
[v0]
name=v0 Yum Repo
baseurl=http://v0.example.com/$releasever/$basearch/
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/v0.key
По команде "# yum repolist" видим в списке наш репозиторий:
repo id repo name status
v0 v0 Yum Repo 7
Установка и настройка nginx
Установка проста: "yum install nginx"
В конфигурационный файл nginx, в секцию http, добавляем строку: "include /etc/nginx/vhosts/*.conf;"
Далее, все файлы в папке /etc/nginx/vhosts/ с окончанием в имени ".conf" будут считаться конфигурационными файлами nginx.
Создаём файл /etc/nginx/vhosts/v0.repo.conf со следующим содержанием:
server {
listen 80;
server_name v0.example.com;
autoindex on;
root /storage/v0.repo;
# ограничим доступ к репозиторию
allow 62.152.38.0/24;
allow 89.17.37.102;
deny all;
}
Запустим nginx и пропишем его в стартовых скриптах:
# /etc/init.d/nginx start
# chkconfig nginx on
Скрипт автоматического build с размещением его в yum-репозиторий
Для начала устанавливаем необходимые пакеты:
yum install rpmdevtools
Привожу сам скрипт с комментами: /home/build_user/recordsapp_build.sh
#!/bin/bash
#
# example to START: "./recordsapp_build.sh 1.8"
# 1.8 - version of recordsapp
# скрипт необходимо стартовать с параметром -- номером версии
# подготавливаем дерево build
# по умолчанию создаётся ~/rpmbuild/{BUILD,RPMS, SOURCES, SPECS, SRPMS}
rpmdev-setuptree
# скачиваем приложение из GIT с указанием номера версии
git clone -q git@git.example.com:recordsapp recordsapp-$1
# Копируем необходимые файлы (в нашем случае это
# стартовый скрипт приложения sendstats) в папку SOURCES
cp recordsapp-$1/stats/sendstats rpmbuild/SOURCES
# упаковываем приложение
tar cf recordsapp-$1.tar recordsapp-$1
# перемещаем архив в SOURCES
# П.С. можно реализовать упаковку сразу в нужное место
# (like: tar cf ./rpmbuild/SOURCES/recordsapp-$1.tar recordsapp-$1)
# и обойтись без предыдущего и текущего шага
# П.П.С. оптимизировать буду после.
mv recordsapp-$1.tar ./rpmbuild/SOURCES
# чистим мусор
rm -rf recordsapp-$1
# подготавливаем spec-файл для создания RPM-пакета
# и помещаем в соответствующую директорию
spec="./rpmbuild/SPECS/recordsapp.spec"
##### BEGIN - .spec - описываем название и текущуюю версию
echo "Name: recordsapp" > $spec
echo "Version: $1" >> $spec
#### Дописываем остальные необходимые данные
cat >> $spec << EOF
Release: av
Summary: recordsapp. Creator CENSORED. Owner CENSORED
Summary(ru): recordsapp - созданно CENSORED. Владелец CENSORED
Group: Applications/Multimedia
License: GPL2
Source0: %{_sourcedir}/%{name}-%{version}.tar
Source1: sendstats
BuildArch: noarch
BuildRoot: %{_tmppath}/
URL: http://example.com/
Packager: Alexey <CENSORED>
Requires: coreutils php php-common php-gd
Autoreqprov: no
%description
recordsapp. Creater: <CENSORED>. Owner: <CENSORED>
%description -l ru
recordsapp - созданно <CENSORED>. Владелец <CENSORED>
%prep
%setup
%build
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/var/www/recordsapp
cp -r . $RPM_BUILD_ROOT/var/www/recordsapp
#mkdir -p $RPM_BUILD_ROOT/etc/httpd/conf.d
mkdir -p $RPM_BUILD_ROOT/etc/init.d
cp %SOURCE1 $RPM_BUILD_ROOT/etc/init.d
%files
%defattr(-,root,root)
/var/www/recordsapp
/etc/init.d/sendstats
#%config(noreplace) /var/www/recordsapp/config/env.php
#%config(noreplace) /var/www/recordsapp/config/envs/prod.php
%clean
rm -rf $RPM_BUILD_ROOT
EOF
##### ==== END of SPEC-file
# создаём rpm
rpmbuild -ba rpmbuild/SPECS/recordsapp.spec --quiet
# Помещаем приложение в репозиторий
sudo mv rpmbuild/RPMS/noarch/recordsapp-$1* /storage/v0.repo/6/x86_64/RPMS/
#sudo createrepo --verbose /storage/v0.repo/6/x86_64/
sudo createrepo --quiet --update /storage/v0.repo/6/x86_64/
rm -rf /home/build_user/rpmbuild
Проверяем:
"$ yum --disablerepo=* --enablerepo=v0 info recordsapp" — покажет последнюю версию приложения
"$ yum --disablerepo=* --enablerepo=v0 provides recordsapp" — покажет все версии приложения, находящиеся в репозитории (может понадобиться при поддержке версионности: откаты на предыдущую версию и т.п.)
recordsapp-1.1-av.noarch : recordsapp. <CENSORED>. Owner <CENSORED>
Repo : v0
Matched from:
Автоматическая проверка обновлений и привязка к созданию build
В данном разделе необходимо отметить, что получить версию приложения из GIT можно разными способами. Также можно по-разному и проверять обновления.
Чтобы разработчики сами могли «выкатывать» приложение, была составлена договорённость по получению версий через 'git tag'
Скрипт проверки версии "~/.build_recordsapp/check.sh":
#!/bin/bash
#
# скрипт проверки версионности по 'git tag'
# запуск build с номером новой версии
#
# забираем приложение с сервера GIT:
git clone -q git@git.example.com:recordsapp /home/build_user/.build_recordsapp/recordsapp > /dev/null 2>&1
# обновляем приложение (не нужно, если мы каждый раз забираем приложение
# и удаляем исходники после обработки)
cd /home/build_user/.build_recordsapp/recordsapp && git pull > /dev/null 2>&1
# смотрм последнюю версию git tag - нам выдаёт v1.1
# - в нашем случае необходимо отрезать лишнюю "v"
recordsapp_TAG=`cd /home/build_user/.build_recordsapp/recordsapp && git tag | sed 's/v//g' | tail -n1`
# just for test:
#recordsapp_TAG="1.6"
# проверяем последнюю версию
recordsapp_RPM=`sudo yum --disablerepo=* --enablerepo=v0 info recordsapp | grep "Version :" | sed 's/Version ://g' |head -1`
# сравниваем версии в репозитории с версией tag
# и отправляем результат в buil
if [ $recordsapp_TAG == $recordsapp_RPM ]; then
echo YES > /dev/null
exit 0;
else
echo NOT
cd /home/build_user && ./recordsapp_build.sh $recordsapp_TAG
exit 0;
fi
Настройка puppet
Не буду описывать детальную настроку puppet — этого добра полно в поисковиках.
Куда писать версию приложения?
Поскольку я не уверен, как правильно надо делать… Слишком гибок в настройках Puppet. Можно в манифест отдельной ноды, можно в общий манифест, можно в темплейт. Ещё не совсем понял идеологию Паппета.
Представляю как это у меня реализовано на момент окончания тестирования:
production/manifests/site.pp:
#...
import "hosts/v0.example.com.pp"
import "hosts/v7.example.com.pp"
#...
production/manifests/hosts/v0.example.com.pp
node "v0.example.com" {
#... Сервер 1 - с указанием конкретной версии релиза приложения
$recordsapp_ip = '127.0.0.113'
$recordsapp_ver = '1.4-av'
class { "recordsapp_hard":
recordsapp_hard_template => "default",
}
}
production/manifests/hosts/v7.example.com.pp
node "v0.example.com" {
#... dev-сервер с указанием последней версии для тестов приложения
$recordsapp_ip = '127.0.0.117'
$recordsapp_ver = 'latest'
class { "recordsapp_hard":
recordsapp_hard_template => "default",
}
}
production/modules/recordsapp_hard/manifests/init.pp
class recordsapp_hard ( $recordsapp_hard_template = "default" ) {
class { 'yum_v0::default': yum_v0_template => "default" }
package {
"recordsapp":
ensure => $recordsapp_ver,
}
file { "/var/www/recordsapp":
ensure => directory,
recurse => true,
owner => "httpd",
group => "httpd",
# mode => "755",
}
file { "/var/www/recordsapp/panel/tmp":
ensure => directory,
owner => "httpd",
group => "httpd",
mode => "0755",
}
file { "/var/www/recordsapp/config/env.php":
owner => "httpd",
group => "httpd",
mode => "0644",
content => template("recordsapp_hard/$recordsapp_hard_template/env.php.erb"),
}
file { "/var/www/recordsapp/config/envs/prod.php":
owner => "httpd",
group => "httpd",
mode => "0644",
content => template("recordsapp_hard/$recordsapp_hard_template/prod.php.erb"),
}
exec { "recordsapp-chmod-fix":
path => ["/usr/bin", "/usr/sbin"],
command => 'find /var/www/recordsapp -exec chmod 0644 {} ; ; find /var/www/recordsapp -type d -exec chmod 0755 {} ;',
refreshonly => true,
}
}
production/modules/recordsapp_hard/templates/default/env.php.erb
# .... тут настройки приложения
production/modules/recordsapp_hard/templates/default/prod.php.erb
# .... и тут настройки приложения
#...
define("SELFIP", "<%= recordsapp_ip -%>");
#...
В результате мы получаем достаточно гибкую систему управления версиями.
На dev-серверах мы имеем последний релиз приложения без участия системного администратора. Разработчик может сам развернуть приложение на тестовом сервере обновив tag в git, а на сервера production устанавливается конкретный оттестированный релиз теми же средствами, но с указанием конкретного номера версии в манифесте puppet.
Автор: hard0ff