Выпуск 29. Июль 2015
← От редактора | Содержание | Инструменты для поиска зависимостей в скрипте →(R)?ex на практике
В жизни каждого человека настает момент, когда ему становится лениво делать одно и то же на удаленных серверах и хочется автоматизации
В решении этой тяжелой жизненной проблемы нам поможет (R)?ex. Rex представляет собой систему управления удаленными серверами и является аналогом Ansible, но, в отличии от него, написан на Perl и работает на всех платформах кроме Windows.
Первым делом установка. На момент написания статьи текущая версия на CPAN 1.2.1. Но устанавливать его лучше из репозитория пакетным менеджером вашего дистрибутива. Благо, сборки есть под все популярные дистрибутивы.
На данный момент основная проблема Rex — это слабая документация. Вроде все вещи описаны, но описаны где попало. Что-то есть на сайте, что-то на CPAN, что-то в списке рассылки — не очень удобно. Так что в этой статье не будет каких-то особо тайных знаний, в основном все просто будет сведено в кучу.
Первые шаги
Во первых, Rex можно использовать для простого запуска команд на удаленном сервере:
rex -H "example.com example.ru" -e "say run 'uptime'"
Здесь мы запускаем команду uptime
на серверах example.com и example.ru и получаем ее вывод в консоль.
Задачи в таком режиме выполняются последовательно, так что придется подождать, пока опросятся все сервера, особенно если их много.
Запуск из консоли это вариант если надо единоразово посмотреть: что там на серверах творится. Но постоянно набирать команды в консоли это решение для сильных духом и памятью. Мы же, как настоящие программисты, будем осваивать командные файлы.
По умолчанию Rex использует файл под названием Rexfile (удивительное совпадение).
Попробуем сделать что-то полезное.
Проверка синтаксиса
Первым делом укажу самое главное при написании командных файлов: проверка синтаксиса выполняется командой rex -T
.
При ее выполнении будет выведено примерно следующее:
bash-3.2$ rex -T
Tasks
checkout
deploy Deploy mysite
sync
Server Groups
myservers server1
В принципе, тут все очевидно. В случае ошибки синтаксиса появится обычное перловое сообщение об ошибке:
bash-3.2$ rex -T
syntax error at Rexfile line 22, near ""checkout" sub "
syntax error at Rexfile line 27, near "}"
Rexfile had compilation errors.
[2015-06-04 10:03:58] INFO - Exiting Rex...
[2015-06-04 10:03:58] INFO - Cleaning up...
Тоже все достаточно очевидно.
Режим отладки
В случае если с синтаксисом все правильно, но все равно ничего не работает — можно использовать режим отладки. Вызывается указанием -d
в параметрах задачи.
rex -d stalled-task
Выведет гигантскую портянку со всеми действиями Rex, подставленными переменными и ответами удаленного сервера.
Теперь создадим Rexfile, который будет выполнять команду deploy
. По этой команде Rex пойдет на удаленный сервер и сделает там git pull
для сайта. Простейший вариант деплоя.
use Rex -feature => ['1.0'];
user "rex"; #Пользователь на удаленном сервере
private_key "~/.ssh/id_rsa";
public_key "~/.ssh/id_rsa.pub";
key_auth;
desc "Deploy the blog on example.ru";
task "deploy", "example.ru", sub {
run "cd /srv/www/example.ru && git pull origin gh-pages";
};
Теперь можно сказать: rex deploy
.
Дальше Rex залогинится под пользователем rex
с использованием указанных ключей и выполнит команду, указанную в опции run
. Если пользователь на удаленном сервере совпадает с текущим, то первую секцию можно опустить. Будет использоваться текущий пользователь и его настройки ключей для ssh. Но если планируется использование команды через cron, то указывать надо обязательно, иначе ничего работать не будет. Так же при использовании через cron нужно указывать полные пути к ключам.
Это минимальный каркас командного файла для управления удаленным хостом. Дальше можно добавлять модули, расширять и углублять задачи.
Во первых, Rex с одинаковым успехом может управлять группой хостов как одним. Для этого добавим описание группы:
group myservers => "server1", "server2", "serverN";
И приведем объявление задачи к виду:
task "deploy", group => "myservers", sub { ... };
Так как Rex использует DSL на основе Perl, то в объявлении серверов отлично работают перечисления, и наш список серверов можно было сделать в виде:
group myservers => "server[1..10]";
В обычном режиме все операции с серверами будут производиться последовательно, что может занять достаточно приличное время. Для ускорения можно распараллеливать задачи, для этого служит опция parallelism($count)
где $count
это число тредов Rex. Каждый тред будет брать себе по серверу из списка и работать с ним. Правда, при этом смешивается вывод исполняемых команд (что логично), и определить что и где пошло не так, может быть сложно (если не настроить логгер и не использовать grep).
Чтобы выполнить команду локально, достаточно просто не указывать целевой сервер в атрибутах цели:
task "local_task", sub { ... };
Выполнит команду на хост машине.
Далее можно обновлять конфигурацию, копируя на хосты необходимые файлы (вы же не храните авторизационные данные в Git, правда?). Приведем содержимое раздела task к виду:
run "cd /srv/www/example.ru && git pull origin gh-pages";
file "/srv/www/example.ru/test.conf",
source => "files/test.conf";
Теперь при выполнении rex deploy
будет выполнен pull
из репозитория на удаленном сервере, а затем туда будет скопирован файл files/test.conf
.
Синхронизация файлов
Таким же образом можно выкатывать все приложение, не используя git pull
на удаленных хостах. Естественно, что делать это стоит не через описание всего приложения в секции file, а через старый добрый rsync.
Приведем наш Rexfile к виду:
use Rex -feature => ['1.0'];
# Подключаем модуль для работы с SCM, он поддерживает Git и SVN.
use Rex::Commands::SCM;
# Подключаем модуль для работы с rsync.
use Rex::Commands::Rsync;
user "rex"; #Пользователь на удаленном сервере
private_key "~/.ssh/id_rsa";
public_key "~/.ssh/id_rsa.pub";
key_auth;
# Объявляем репозиторий
set repository => "example",
url => "git@github.com:spb-pm/spb.pm.git",
type => "git",
username => "username",
password => "password";
desc "Deploy the site on example.ru";
task "deploy", "example.ru", sub {
# Делаем checkout из указанного выше репозитория
say checkout "example",
branch => 'gh-pages',
path => "site_repo";
# Заливаем файлы через rsync на удаленный хост
say sync "./site_repo/*", "/srv/www/example.ru", {
parameters => '--backup --delete',
};
};
Теперь при выполнении rex deploy
будет сделан git pull в директорию site_repo
, в текущей директории и ее содержимое будет через rsync перенесено на удаленный сервер. В блоке parameters => ...
задаются параметры rsync.
Через rsync можно получать и данные с удаленного сервера (может быть удобным для переодического получения логов и прочего в этом духе):
task "sync", "server01", sub {
sync "/var/www/html/*", "html/", {
download => 1,
parameters => '--backup',
};
};
В принципе, указанного выше уже достаточно, чтобы написать свой logstash в 10 строчек (на самом деле нет).
Передача параметров в задачу
Еще полезным моментом может быть передача параметров в задачи. Например, можно указать ветку, если мы разворачиваем тестовое окружение.
rex deploy --branch=test-branch --parameter2=foo
Пример секции task:
task "deploy", "myserver", sub {
my $params = shift;
say $params->{'branch'};
say run "uptime";
};
Собственно, стандартная перловая передача hashref с параметрами.
Обработка ошибок
Статус выхода команды run
доступен в переменной $?
. Так же для run
можно включить режим auto_die
. Что он делает, понятно из названия.
task "deploy", "myserver", sub {
say run "cp /tmp/foo /tmp/bar";
say run "...";
};
В случае отсутствия исходного файла для cp
операция не выдаст ничего, и выполнится следующая команда. Исправим это поведение:
task "deploy", "myserver", sub {
say run "cp /tmp/foo /tmp/bar", (auto_die => TRUE);
say run "...";
};
Теперь в случае ошибки мы получим красивый лог с описанием произошедшего.
sudo
Если нам нужно выполнить команду от root или пользователя, отличного от залогиненного, то надо использовать sudo
:
sudo_password 'sudo password';
task "whoami", "example.ru", sub {
say run "whoami";
sudo {
command => sub {
say run "whoami";
},
user => 'myuser',
};
};
Выведет 2 строчки:
rex
myuser
Ускоряем работу
Rex может кешировать инвентори для выполнения задач. Полезно, когда используется много вызовов sudo
или других медленных операций. Для этого надо вызвать его с опцией -c
:
rex -c deploy
После работы скрипта будет создан каталог .cache
, в котором будет хранится инвентарь для последующих вызовов скрипта.
Управление конфигурациями
Теперь рассмотрим вещи, ради которых действительно стоит использовать системы управления конфигурациями. По большому счету то, что приведено выше, реализуется небольшим bash-скриптом, который все знают и умеют, и нет особого смысла осваивать Rex
.
Модули
Для начала рассмотрим вопрос создания модулей. Нет смысла обсуждать, что это и зачем нужно, так что просто рассмотрим, как оно работает.
Модуль Rex фактически является стандартным модулем Perl. Соответственно, большинство требований аналогично. Модели размещаются в каталоге lib
директории, откуда выполняется Rexfile
.
Для своих проектов я использую практически типовой набор ПО для создания окружения. Напишем модуль, который будет поднимать это окружение.
Создадим проект:
mkdir lib && cd lib && rexify MyRex::OS --create-module
В документации написано, что должна создаться директория lib
с модулем и Rexfile
, но на практике не создается.
lib
└── MyRex
└── OS
├── __module__.pm
└── meta.yml
Вот такое получится в результате работы этой команды. Файл meta.yml
будет нужен только если вы собираетесь заливать свой модуль в репозиторий пакетов Rex. Файл __template__.pm
— это файл нашего модуля.
Модуль будет устанавливать на сервер все необходимые пакеты и разворачивать окружение, приведем его к виду:
package MyRex::OS;
use Rex -feature => ['1.0'];
task "prepare" => sub {
sudo sub {
pkg [
"build-essential",
"supervisor",
"mariadb-common",
"git",
"vim",
],
ensure => 'present';
};
};
1;
Команда pkg
возьмет заданный список пакетов, проверит, что они установлены, и, в случае отсутствия какого-либо пакета — доустановит его.
Опция ensure
говорит нам, что делать с пакетами:
present
— пакет должен быть установлен, какая версия, нам не важно;latest
— должен быть установлен пакет последней версии, если это не так — пакет будет автоматически обновлен;absent
— пакет не должен быть установлен, если пакет установлен — то он будет удален;2.3.4
— должна быть установлена конкретная версия пакета.
Обратите внимание на использование sudo
вокруг pkg
, по умолчанию все операции выполняются от текущего пользователя, и самостоятельно Rex
не будет переключаться в режим суперпользователя.
Создадим в корне проекта Rexfile
вида:
use Rex -feature => ['1.0'];
user "rex";
password "password";
sudo_password "password";
group "srv" => "192.168.1.102";
require MyRex::OS;
task "prepare", group => "srv", sub {
MyRex::OS::prepare();
};
1;
В Rexfile
мы объявляем группу для серверов и создаем команду prepare
, которая будет выполнять команду prepare
из модуля MyRex::OS
на нужных нам серверах и в нужном окружении.
Теперь при запуске rex prepare
на целевом сервере будут установлены все указанные выше пакеты.
Пришло время установить Perl.
Первым делом установим perlbrew
и CPAN в системный Perl. Добавим в блок sudo
в prepare
нашего модуля:
append_if_no_such_line "/etc/environment",
line => "LC_ALL=en_US.UTF-8",
regexp => [qr{^LC_ALL=}]; # this is an OR
run 'cpan -i App::perlbrew', (auto_die => TRUE);
Первая команда откроет /etc/environment
и проверит, что там задан язык для LC_ALL
. Если не задан, то выставит кодировку en_US.UTF-8
.
Зачем это надо? По умолчанию эта переменная не проставлена, и многие ssh-клиенты ее не устанавливают. Соответственно, при установке perl
мы получим кучу ругани и падение тестов, которые проверяют IO. На самом деле, для запуска задач через Rex
эта проблема не актуальна, так как он выставляет LC_ALL=C
, но при входе в консоль возможны варианты.
Вторая строка просто запускает cpan -i
.
Установим Perl. Эту операцию я вынес в отдельный таск, т.к. там используется куча run
, и простыня в prepare
получается уж слишком большой.
task "install_perl" => sub {
say "Installing perl";
run "perlbrew init", (auto_die => TRUE);
append_if_no_such_line "~/.bashrc",
line => "source ~/perl5/perlbrew/etc/bashrc";
run "source ~/perl5/perlbrew/etc/bashrc";
run "perlbrew install perl-5.20.2", (auto_die => TRUE);
say "Done";
};
Тоже ничего сложного, стандартный процесс установки из мануала perlbrew
. В append_if_no_such_line
мы просто дописываем строку, если ее не существует.
Вызов этого таска можно добавить вне блока sudo
в prepare
через do_task "install_perl"
. Либо вызывать самостоятельно, по большому счету это выполняется один раз, и особой разницы нет.
Теперь мы можем подключать этот модуль в Rexfile любого нового проекта и получать единообразное окружение одной командой.
Ну и всякие мелочи напоследок.
say
Вывод команды say на больших логах выглядит чудовищно. Если сохранить вывод run
в переменную и вывести его через say
, то будет чуть менее ужасно. Но он настраивается:
sayformat('[%D] %h %s');
[2015-06-15 10:58:22] 192.168.1.150 total 4
[2015-06-15 10:58:22] 192.168.1.150 drwxr-xr-x 3 root root 4096 Jun 15 09:22 perl5
Локальные задачи
Если внутри таска на удаленных серверах надо выполнить задачу на хост-машине — используется функция LOCAL(&)
:
task "mytask", group => "srv" sub {
# Это выполнится на удаленных серверах
say run "uptime";
# А это на локальном
LOCAL {
say run "uptime";
};
};
profile
Важным моментом при использовании Rex
являет тот факт, что он подключается к серверу в режиме nologin. В этом режиме не подгружаются файл .bashrc
и иже с ним. В данной ситуации есть три варианта действий:
- делать
source ~/.bashrc
в команде; - вручную задать все нужные переменные окружения в Rexfile;
- выставить настройку
source_global_profile(0|1)
.
Настройка делает то же что и первый пункт списка, но автоматически.
Вывод команды при ошибке
По большей части нам все равно, что пишут команды в консоль при выполнении, не все равно становится, когда что-то ломается. Но каждый раз переключать c run "uptime"
на say run "uptime"
не интересно и лениво. Для решения этой проблемы есть специальная команда — last_command_output
task "mytask", group => "srv" sub {
run "command-with-error";
if ($?) {
say last_command_output();
}
};
Для версий старше 1.2 — надо будет изменить заголовок файла на:
use Rex -feature => ['1.3', 'tty'];
поскольку в режиме notty не происходит перехват STDERR, а режим tty был отключен по умолчанию в каком-то из релизов 1.0+.
Заключение
В целом, Rex
является очень мощным инструментом прямо из коробки. Он позволяет обойтись без строительства велосипедов, и при этом получить полный контроль над управлением инфраструктурой самых разных масштабов.
Единственное что он пока не умеет — управление хостами на Windows, но если это не требуется — то дорога в мир Continuous Delivery для вас открыта :)
← От редактора | Содержание | Инструменты для поиска зависимостей в скрипте →