Здравствуйте! Я давно не появлялся здесь в качестве оратора, но в этот раз я решил поделится кое-чем, что сделал сам, а также узнать — нужно это, не нужно, как можно доработать и вообще услышать любые отзывы о моих деяниях.
Мотивация
Проблема сборки и запуска проекта на разных машинах преследовала меня всегда. Для того, чтобы реалистично смоделировать работу разрабатываемого сайта на локальной машине нужно установить Web-сервер, Application-сервер, возможно, к ним присоединится какой-нибудь ещё промежуточный сервер, установить базу данных, настроить базу данных. Для того, чтобы установить тестовый сайт на тестовый сервер, нужно проделать такую же работу. И позже тоже самое с рабочим сервером.
Кажется, что проблема решается легко — напиши все команды в файл и просто запускай его везде. Решение относительно хорошее, но не идеальное, и вот почему. К примеру, на одном из серверов уже установлены нужные пакеты и база данных там готова. Но не до конца, к ней не применены последние миграции. Придётся открывать файл с командами и вытаскивать оттуда нужные, дабы не получить ошибку или чтобы что-то не сломать.
Но это не такая серьёзная проблема, большую проблему я определил для себя при работе с Django. Django, как известно, при его запуске висит в памяти, и если код изменён — эти изменения никак не повлияют на сайт. Приходится постоянно перезапускать сервер. Несложно. А если изменены модели, нужно ещё и миграции создать и применить? А если настройки веб-сервера изменены, то нужно и их ещё применить и перезапустить веб-сервер? А если всё вместе, а проект я открывал месяц назад и абсолютно не помню, что я там менял и мне бы «сделать хорошо», но я не хочу утомительно вбивать все команды? А если проект огромный и я не хочу тратить время на лишние команды при запуске и сборке? И таких «А если...» может быть тьма тьмущая.
Решение пришло само — нужна автоматизация, сборщик проектов. Конечно же, на Linux. Погуглив, я нашёл уйму сборщиков проектов… Для одного языка или одной технологии. Ничего действительно универсального, чтобы прописал команды — и он их по надобности запускает — нет. Есть cmake, но его я не взял, потому что придумал решение получше)
В этот момент была создана первая схема велосипеда. Сначала я написал в файл все команды, но при малейших изменениях перезапуск всего занимал длительное время — это раздражало. Сначала смирился. Потом хотел, чтобы у скрипта были настройки, написал в первых строках переменные и описал алгоритм их изменения через аргументы запуска скрипта. Потом мне всё же захотелось не выполнять некоторые команды, если этого не требуется, и я сделал функции проверки. Потом пришла идея разграничить команды, некоторые объединить друг с другом.
Объединённые команды я назвал «target». В скрипт отправляется имя цели, а потом она выполняется. Оказалось, что некоторые цели неспособны выполнится без выполнения других целей — так появилась иерархия. Потом функции проверки команд превратились в функции проверки целей. Потом захотелось упростить работу с установкой пакетов, и была создана сущность «package».
В общем, процесс разработки я могу описывать долго — это, наверное, скучно.
Результат
Итоговым рабочим вариантом получился bash скрипт в 400 строк, который я назвал xGod. Я так его назвал, потому что этот файл стал незаменим для меня при работе, как воздух.
Как работает xGod:
Запускается из консоли — bash ./xgod build.xg run
build.xg — это файл сборки, в котором прописаны все цели и дополнительные функции
run — это цель, которую нужно выполнить
Из чего состоит build.xg:
1. Из обычных строк на языке bash — они выполняются последовательно по мере считывания файла
2. Из целей
Например:
target syncdb: virtualenv createmysqluser
source "$projectpath/venv/bin/activate"
python3 "$projectpath/manage.py" makemigrations
python3 "$projectpath/manage.py" migrate
deactivate
syncdb — название цели; virtualenv createmysqluser — это цели, которые надо выполнить до выполнения цели syncdb, так называемые зависимости; всё остальное — это обычный bash код, которым и достигает саму цель.
3. Пакеты:
Например:
package gunicorn: python
all:
name: python3-gunicorn
gunicorn — название пакета (или цели, потому что для скрипта это такая же цель); python — зависимость; all — это название дистрибутива, к которому применяются вложенные настройки, all означает, что данные настройки применяются ко всем дистрибутивам без исключения, в данный момент реализована поддержка только debian и ubuntu, потому что с другими я не работал; name — это название пакета, используемое для установки.
4. Функции проверки:
Например:
check syncdb()
# any code
return 1 # or return 0
endcheck
Функция проверки позволяет проверить, нужно ли выполнять цель syncdb или нет. Сохраняется и выполняется она как обычная функция, возвращает 1 (если цель надо выполнять) или 0 (если цель не надо выполнять)
Также была написана система поддержки расширений. Цели package как раз-таки являются расширениями. Синтаксис расширений не сильно отличается от синтаксиса файлов сборки, в нём могут присутствовать:
1. Обычные команды на языке bash
2. Обязательно функция действия.
Например:
action
# any code with $1
endaction
Это функция принимает на вход имя цели и выполняет её по своим правилам. Все внутренности цели она может получить из переменной ${TARGETS[$1]}
3. Функция проверки цели
Например:
check
# any code with $1
return 1 # or return 0
endcheck
Также получает на вход имя цели и проверяет, нужно ли её выполнять. Если нужно, то обязана вернуть 1, а если нет, то 0
Ещё применения
Применение данного скрипта может быть более крупным, чем просто сборка и запуск проектов из нулевого состояния машины. Например, у меня есть собственный набор пакетов, который я хочу видеть каждый раз при установке системы. Каждый раз в новых дистрибутивах меняют набор стандартных пакетов, поэтому после установки я не знаю, есть ли такие пакеты в системе или нет. Конечно же, я могу узнать, но мне лень. Намного проще набить все нужные пакеты в скрипт и одной командой запустить их установку. Те, которые уже есть в системе, он пропустит, а те, которых нет — установит. Всё просто.
В следствии такого применения скрипта его главным условием было — это минимальные зависимости для запуска. Поэтому вместо Python или C++ он написан на bash — чтобы его можно было запустить из любой среды Linux без дополнительных действий. Единственный минус — bash должен быть не меньше 4 версии, так как там ассоциативные массивы не поддерживаются.
Ссылку на код оставлю здесь.
Комментарии (26)
PavelVainerman
25.09.2017 18:50+3Рассматривали ли как вариант make и/или например ansible?
Ryder95 Автор
25.09.2017 18:51+1Честно, первый раз слышу о них) Может быть, если бы знал, то не сделал бы это. Спасибо, прочту, отпишусь
Ryder95 Автор
25.09.2017 18:52-1Ну make точно нет, так как проверки выполнения целей там нет, а мне она нужна был
PavelVainerman
25.09.2017 18:57+1М-м… ну вообще-то он даже синтаксисом на ваше решение похож :)
Конечно в плане проверки «установлен ли пакет»… надо самому правило написать,
но make более масштабная вещь, в каком-то смысле это не его уровень.
Для автоматизации развёртывания это как раз ansible со товарищи…
Так что Вы зря про «точно нет»…
P.S. Просто он наиболее близок к bash, как я понял Вам это удобно. Все цели
это по сути bash команды.Ryder95 Автор
25.09.2017 19:05Я, если честно, с него и брал синтаксис. Изначально я и пытался сделать всё на make, но сильно с ним намучился. И у меня не получилось там сделать подобия проверки выполнения цели. Если я правильно понимаю, make был сделан для C/C++ и в основном работает с ними. Скажите, как можно сделать проверку выполнения цели в make?
PavelVainerman
25.09.2017 19:26>> Если я правильно понимаю, make был сделан для C/C++ и в основном работает с ними.
Нет. Это универсальная система, не привязанная к языкам. Есть совершенно разные проекты, которые собираются при помощи make. Триада «configure && make && make install» это очень древняя вещь ) Но т.к. make действительно уже старенький, новые проекты уже строят на чём-то более современном… cmake в частности.
>> Скажите, как можно сделать проверку выполнения цели в make?
Ну может я не совсем понимаю Вашу задачу. make — более рассчитан на присутствие или отсутствие файлов, атрибуты их изменения. Соответственно ваши «цели» должны что-то такое «производить» в виде файлов, чтобы можно было понять
что уже всё есть. Ну например пусть будет проверка установлен ли паке mypack в системе
mypack.dep: xxx install mypack && touch mypack.dep all-local: mypack.dep ...
В этом примере при команде make all пакет установится только первый раз,
т.к. в случае успеха будет создан файл mypack.dep, то при повторном make, эта команда не будет исполнена. Как-то так (я не очень большой специалист в make).
P.S. Это примитивный пример, make позволяет делать более сложные и иерархичные вещи. Его цель «управление зависимостями» сборки.Ryder95 Автор
25.09.2017 19:28Спасибо, я понял. К сожалению, не всегда результатом работы будет файл) Наверное, ansible более интересный в этом плане. Буду копать в его сторону
PavelVainerman
25.09.2017 19:41Да конечно. От ansible не отговариваю, это очень удобная «штука».
Вам главное понять, что ansible и make не взаимоисключающие вещи )
mickvav
25.09.2017 19:28make идеален, если вы преобразуете файлы. Соответственно, в его терминах — сокет или pid-файл — результат выполнения операции «перезапустить серверный процесс», если условием является config-файл.
Если скрипты миграции базы у вас пишут свои результаты работы в файл, а в качестве условия для них работают .sql — файлы — тоже все получится. Соответственно, если миграция нормально не применилась, скрипт должен завершиться с ненулевым кодом возврата.
Samouvazhektra
25.09.2017 19:06+1а чем ansible то не угодил? Инфраструктура на нём, куча плейбуков для любых платформ и ПО, можно и для деплоя роли на нём, можно для деплоя fabric https://habrahabr.ru/post/141271/ https://habrahabr.ru/post/257671/
чтобы его можно было запустить из любой среды Linux без дополнительных действий
но его надо запускать с сервера, и если надо обслужить несколько серверов — ставить и запускать на каждом.
А ansible стоит на твоей машине и не требует установки на сервера, можно запускать задачи на несколько серверов одновременно, можно ставить условия в зависимости от платформ, так же поддерживает состояния и не будет ставить то что уже установлено, и помимо прочего состояния поддерживаются для правок конфигов, баз, юзеров и т.п.
AlexLeonov
25.09.2017 19:53+1Вы написали заново Ant / Phing
Поздравляю.Ryder95 Автор
25.09.2017 20:26Вы правы, Ant я посмотрел и он мне понравился. Конечно, формат XML — это не очень, ИМХО) Ну и единственное, что он предназначен для Java. Это сразу же ограничивает круг применения, я об этом написал) А Phing, как я понял, для PHP
AlexLeonov
25.09.2017 22:48Phing — на PHP, а не для PHP. Что, впрочем, никак не ограничивает его область применения.
По крайней мере запустить сценарий на phing гораааздно проще, чем Ant.
XakRU
25.09.2017 20:15+21. Ansible + Vagrant для автоматизации развертывания виртуальной машины, просто ansible для настройки продуктивного сервера.
2. Поглядел Ваш скрипт, всегда считал хорошим тоном — выносить переменные в начало скрипта, у Вас они ни в начале, ни в конце, а где-то посередине и в разных местах.
А вот тут(клац) я услышал голоса в голове, которые говорили прекрати на это смотреть или «убей их всех».
Вы серьезно? 3 вложенных цикла for и там же куча if… else с ними. Как это развидеть? О мои глаза....!1
Статья — очередной костыль, а не велосипед.
Но когда окажется, что ваш bash-сценарий превысил в объеме сотню строк или вам потребовались средства, которыми bash не обладает, это будет означать, что настало время переходить к языку Perl или Python.
©Evi NemethRyder95 Автор
25.09.2017 20:20Во-первых, я реально не знаю, как обойтись здесь без вложенных циклов и if..else. Это не json, а просто текст, который надо было обрабатывать. Я не имею писать на bash, но взял именно его по той причине, которую описал в статье — очень хотелось иметь сборщик. который практически не требует зависимостей. К тому же, если в файлах сборки используется bash — то, наверное, правильнее писать на bash)
Но я Вас услышал, действительно, хотелось бы перевести это в более наглядную форму, если это вообще имеет смысл. Всё же данная статья была не столько обзором, сколько просьбой спросить совета: «А правильно ли я сделал или есть уже штуки, решающие мою проблему?»
Насчёт переменных — я тоже люблю их выносить в начале, но в данном случае мне было удобнее иметь переменные перед основным кодом выполнения скрипта, а функции убрать подальше)XakRU
25.09.2017 22:38Сомнительное предприятие — не умеете писать на bash, но пришли на Хабр со статьей по самописному скрипту. Можно и Gentoo установить набором команд в одну строку, но зачем? Я не хочу отправлять Вас в Гугл на поиски, возможно Вы с ним в ссоре. Я попробую Вам помочь, если уж Bash, то держите: github.com/serghey-rodin/vesta.
Но все же лучше взгляните на ansible либо salt stack и придерживайтесь фразы «do not repeat yourself».Ryder95 Автор
25.09.2017 22:43Ну таки я же сразу сказал, что хочу скорее отзывов и направлений на путь истинный) Всё же отзывов и решений я получил больше, чем просто бы запостил вопрос на SO) Да, я уже решил, что заменю монстра на ansible, но всё равно спасибо)
domix32
26.09.2017 11:42Чем вам тот же питон не угодил в таком случае? Позвать системные утилиты чтобы поставить зависимость не составляет проблемы и на порядок понятнее bash-ада из разных вариантов сочетаний кавычек, скобок и символа доллара. Тут же на месте еще и интерактивную документацию впилить можно.
foxmuldercp
28.09.2017 18:55Для начала, насчет джанги не знаю, но рельсы могут работать в двух режимах — отладки и продакшена. в режиме отладки перечитывается код контролеров, моделей, вьюшек, роутов,
кроме конфига приложения, ручками только миграции делать.
В режиме продакшена выключается дофига всего отладочно лишнего, и как запустил код (в том же докере), так оно и будет жужжать.
У меня кстати, это вот в продакшене жужжит с nginx + passenger. время ответа убыстряется в разы
ov7a
Puppet, chef, ansible?
Kryanush
Как вариант еще можно рассмотреть SaltStack