Создание проекта на Erlang и его упаковка в deb-пакет
11 марта 2014
Примем за рабочую теорию, что мы здесь все взрослые и осознаем важность использования менеджера пакетов. Есть много способов упаковать приложение, написанное на Erlang, в deb, rpm или еще какого формата пакет. Здесь я опишу один из множества таких способов, основанный на использовании утилиты FPM.
В общем, я выложил на GitHub’е шаблон минимального проекта на Erlang, способного упаковываться в deb-пакет. К проекту прилагается Perl-скрипт, генерирующий на его основе новый проект, также способный упаковываться в deb-пакет. Если вы не располагаете большим количеством времени, просто взгляните на тамошний README.md. В нем расписано, как этим хозяйством пользоваться. Здесь же я расскажу чуть более подробно, как создаются такие проекты вручную почти с нуля, а затем уже перейдем и к автоматизации.
Как вы уже поняли, нам потребуется FPM. Это очень удобный скрипт на Ruby, предназначенный для создания deb, rpm и прочих пакетов. Устанавливается он следующим образом:
sudo gem install fpm
Теперь создадим новый проект. Пусть это будет приложение, отвечающее за хранение неких прав доступа. Назовем его AccessDB.
Заводим новый git-репозиторий и говорим:
cd accessdb
wget https://github.com/afiskon/erl-min-prj/raw/master/.gitignore
wget https://github.com/afiskon/erl-min-prj/raw/master/Makefile
wget https://github.com/afiskon/erl-min-prj/raw/master/rebar.config
wget https://github.com/afiskon/erl-min-prj/raw/master/rebar
chmod u+x rebar
./rebar create-node nodeid=accessdb
Здесь нет особой магии. Тянется rebar. В мире Erlang считается хорошей практикой держать его прямо в репозитории приложения. Таким образом, кто угодно может собрать проект, без долгого и мучительного выяснения, что такое rebar и где его вообще взять. В rebar.config в качестве единственной зависимости указывается lager. Редкое приложение на Erlang нынче обходится без него. Содержимое .gitignore довольно предсказуемо. В Makefile помимо традиционных вещей содержится небольшой сценарий для сборки deb-пакета с помощью FPM.
Разумеется, можно делать проекты на Erlang, заполняя эти файлы, а также файлы, речь о которых пойдет ниже, вручную. Но в какой-то момент это начинает надоедать, так что здесь я беру все почти готовое. «Почти», потому что пару строк все-таки придется поменять. Полное содержимое всех этих файлов я здесь не привожу. Кому интересно, может ознакомиться с ним самостоятельно.
Первые три строчки в Makefile правим на:
DESCRIPTION="AccessDB"
HOMEPAGE="http://eax.me/erlang-deb-package/"
В файле .gitignore заменяем строчку /phonebook/
на /accessdb/
.
Создаем новое приложение:
cd apps/accessdb
../../rebar create-app appid=accessdb
cd ../..
На одной Erlang’овой ноде может крутится, и, как правило, крутится, более одного приложения (которое OTP application). Хорошей практикой нынче считается складывать все приложения в каталог apps, а все зависимости проекта держать в каталоге deps.
В rebar.config заменяем:
"apps/phonebook"
]}.
… на:
"apps/accessdb"
]}.
Когда вы создаете новые приложения, их нужно добавлять в этот список.
В файле apps/accessdb/src/accessdb.app.src в список applications добавляем lager, правим vsn и description. В результате должно получится что-то вроде:
[
{description, "AccessDB"},
{vsn, git},
{registered, []},
{applications, [
kernel,
stdlib,
lager
]},
{mod, { accessdb_app, []}},
{env, []}
]}.
В списке applications должны быть перечислены все приложения-зависимости вашего приложения. Обратите внимание, что в vsn вместо конкретной версии мы написали «git». За счет этого версия приложения будет генерироваться на основе тэгов в git. Это удобно, ибо не приходится при каждом изменении приложения обновлять его версию в .app.src-файле.
Далее:
wget raw.github.com/afiskon/erl-min-prj/master/files/vars.config
wget raw.github.com/afiskon/erl-min-prj/master/files/vars-dev.config
Эти файлы содержат значения переменных, подставляемых в разные другие файлы. Значения, понятное дело, разные, в зависимости от того, собираем ли мы приложения для боевого окружения или же разрабатываем его на dev-сервере. Ну, например, на боевом сервере логи нужно писать в /var/log/accessdb, а на dev-сервере — просто где-то в локальном каталоге.
В обоих файлах меняем первую строчку на:
Теперь тянем в каталог files скрипты init, postinst и postrm:
wget https://raw.github.com/afiskon/erl-min-prj/master/files/postinst
wget https://raw.github.com/afiskon/erl-min-prj/master/files/postrm
Здесь init — это тот самый скрипт, который запускается при выполнении команд типа sudo service accessdb start|stop
. Скрипты postinst и postrm запускаются менеджером пакетов при установке и удалении пакета соответственно. Первый заводит пользователя и группу, под которыми будет работать приложение, создает необходимые каталоги, и так далее, а второй производит обратные действия. Все эти скрипты довольно тупые, но требуют аккуратного написания и хорошего тестирования.
Во всех трех скриптах меняем имя проекта и его описание, в том числе правим закомментированные строчки в файле init. Затем говорим:
wget https://raw.github.com/afiskon/erl-min-prj/master/files/app.config
cd ..
В файле app.config (или sys.config, на самом деле его имя не так уж важно) хранятся настройки всех приложений в проекте. Те самые, которые доступны через функции application:get_env/2 и другие. Шаблон, которым мы здесь воспользовались, просто содержит довольно очевидные настройки для lager.
В файле reltool.config находим строчки (они там в разных местах):
{copy, "files/accessdb", "bin/accessdb"},
{copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
{copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"}
… и заменяем их соответственно на:
{template, "files/accessdb", "bin/accessdb"},
{template, "files/app.config", "etc/app.config"},
{template, "files/vm.args", "etc/vm.args"}
Здесь мы говорим (1) искать приложения в каталогах apps и deps, (2) подставлять в файлы accessdb, app.config и vm.args переменные из файлов vars.config или vars-dev.config, а также (3) меняем пути, по которым будут доступны итоговые app.config и vm.args.
Будьте внимательны! Легко не заметить, например, что файл sys.config заменяется на app.config.
Наконец, в files/accessdb находим строчки:
RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}
RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc
PIPE_DIR=/tmp/$RUNNER_BASE_DIR/
RUNNER_USER=
RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log
… и заменяем их на:
RUNNER_BASE_DIR={{runner_base_dir}}
RUNNER_ETC_DIR={{runner_etc_dir}}
PIPE_DIR={{pipe_dir}}
RUNNER_USER={{runner_user}}
RUNNER_LOG_DIR={{runner_log_dir}}
Это просто sh-скрипт, через который будет запускаться наша AccessDB. Увы, его версия, сгенерированная rebar’ом, не очень хорошо определяет значения некоторых переменных (где искать конфиги, куда писать логи и тп). Поэтому мы решаем явно задавать соответствующие значения в файлах vars.config или vars-dev.config, и просто подставлять их в сей скрипт.
Самое время все это хозяйство закоммитить:
git commit -am 'AccessDB Initial Commit'
git push origin HEAD
git tag 0.1.0
git push origin 0.1.0
Создание тэга обязательно, так как он используется в качестве версии deb-пакета.
Затаив дыхание, говорим:
Если все было сделано правильно, вы увидите приглашение:
Дважды жмем Ctr+C. В файле accessdb/log/console.log должно быть записано что-то вроде:
Application accessdb started on node 'accessdb@127.0.0.1'
Теперь попробуем собрать deb-пакет:
Должен появиться файл accessdb_0.1.0_amd64.deb. Устанавливаем и запускаем приложение, затем цепляемся к нему по remsh:
sudo service accessdb start
erl -name remsh@127.0.0.1 -setcookie accessdb -remsh accessdb@127.0.0.1
Вновь должны увидеть приглашение REPL’а. Дважды нажимаем Ctr+C.
Останавливаем сервис:
Настройки AccessDB лежат в /etc/accessdb, логи пишутся в /var/log/accessdb, само приложение лежит в /usr/lib/accessdb. Теперь проверим, как работает удаление.
Сначала останавливаем epmd:
Дело в том, что может не получится удалить пользователя accessdb, пока есть приложения (например, epmd), работающие под ним. Но при этом включить в deb-пакет скрипты, останавливающие epmd, мы не можем, так как это может помешать другим работающим локально Erlang-приложениям.
После этого мы можем сказать:
sudo apt-get purge accessdb
Несмотря на то, что все описанные действия занимают не более десяти минут времени (я замерял), постоянно повторить их скучно и чревато ошибками. Поэтому я написал скрипт, который делает все то же самое, только быстро и без ошибок. Пользоваться им очень просто:
chmod u+x new-erl-srv
./new-erl-srv mynewservice "My New Service" http://eax.me/
Несколько финальных замечаний.
Лично у меня не возникает потребности, скажем, в rpm пакетах, да и особо негде тестировать соответствующий функционал. Но если у вас такая потребность есть, вы можете попробовать сконвертировать полученный deb-пакет, используя утилиту alien. Или, что еще лучше, вы можете послать мне пуллреквест, добавляющий поддержку сборки rpm (напоминаю, FPM это умеет) и других пакетов.
Некоторые программисты любят включать в пакеты документацию, конфиги для Nginx и так далее. Я не уверен, что это правильно. Документацию (например, man-pages) лучше упаковывать в отдельный пакет, а также выкладывать в сети, где ее проиндексируют поисковые системы и любой желающий сможет просмотреть без установки каких-либо пакетов. Что же касается конфигов для Nginx, нехорошо привязывать пользователя к конкретному веб-серверу. Лучше держать эти конфиги в открытом репозитории, откуда их сможет скачать любой желающий. Всякие же настройки фаервола, квоты и так далее должны настраиваться Chef’ом, а не включаться в пакет.
В общем, если вам хочется добавить в генерируемые пакеты что-то помимо, скажем, списка зависимостей, скорее всего, вы делаете что-то не так. Либо привязываетесь к конкретному, довольно специфичному, окружению, либо из тупого, как валенок, пакета, делаете что-то раздутое и громоздкое, либо попросту решаете свою задачу не теми средствами.
Ссылки по теме:
- FPM используют в Echo и у них тоже есть свои скрипты, но только для генерации rpm;
- У Basho есть нечто похожее под названием node_package, но оно что-то какое-то сложное и, видимо, сильно заточено под их нужды;
- Если вам не нравится Ruby, попробуйте EPM от Максима Лапшина;
- Уже после написания этой заметки выяснилось, что есть похожий инструмент под названием tetrapack;
- Репозиторий FPM на GitHub’е;
Существенную помощь при написании этой заметки оказал @kpy3. Также не обошлось без помощи со стороны товарищей @defnull и @sum3rman. За что всем им огромное спасибо!
Как всегда, я буду рад вашим вопросам и дополнениям.
Метки: Erlang, Linux, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.