image
Здравствуйте! Я давно не появлялся здесь в качестве оратора, но в этот раз я решил поделится кое-чем, что сделал сам, а также узнать — нужно это, не нужно, как можно доработать и вообще услышать любые отзывы о моих деяниях.

Мотивация


Проблема сборки и запуска проекта на разных машинах преследовала меня всегда. Для того, чтобы реалистично смоделировать работу разрабатываемого сайта на локальной машине нужно установить 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)


  1. ov7a
    25.09.2017 18:49
    +3

    Puppet, chef, ansible?


    1. Kryanush
      25.09.2017 20:21
      +1

      Как вариант еще можно рассмотреть SaltStack


  1. PavelVainerman
    25.09.2017 18:50
    +3

    Рассматривали ли как вариант make и/или например ansible?


    1. Ryder95 Автор
      25.09.2017 18:51
      +1

      Честно, первый раз слышу о них) Может быть, если бы знал, то не сделал бы это. Спасибо, прочту, отпишусь


    1. Ryder95 Автор
      25.09.2017 18:52
      -1

      Ну make точно нет, так как проверки выполнения целей там нет, а мне она нужна был


      1. PavelVainerman
        25.09.2017 18:57
        +1

        М-м… ну вообще-то он даже синтаксисом на ваше решение похож :)
        Конечно в плане проверки «установлен ли пакет»… надо самому правило написать,
        но make более масштабная вещь, в каком-то смысле это не его уровень.
        Для автоматизации развёртывания это как раз ansible со товарищи…
        Так что Вы зря про «точно нет»…

        P.S. Просто он наиболее близок к bash, как я понял Вам это удобно. Все цели
        это по сути bash команды.


        1. Ryder95 Автор
          25.09.2017 19:05

          Я, если честно, с него и брал синтаксис. Изначально я и пытался сделать всё на make, но сильно с ним намучился. И у меня не получилось там сделать подобия проверки выполнения цели. Если я правильно понимаю, make был сделан для C/C++ и в основном работает с ними. Скажите, как можно сделать проверку выполнения цели в make?


          1. 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 позволяет делать более сложные и иерархичные вещи. Его цель «управление зависимостями» сборки.


            1. Ryder95 Автор
              25.09.2017 19:28

              Спасибо, я понял. К сожалению, не всегда результатом работы будет файл) Наверное, ansible более интересный в этом плане. Буду копать в его сторону


              1. PavelVainerman
                25.09.2017 19:41

                Да конечно. От ansible не отговариваю, это очень удобная «штука».
                Вам главное понять, что ansible и make не взаимоисключающие вещи )


          1. mickvav
            25.09.2017 19:28

            make идеален, если вы преобразуете файлы. Соответственно, в его терминах — сокет или pid-файл — результат выполнения операции «перезапустить серверный процесс», если условием является config-файл.
            Если скрипты миграции базы у вас пишут свои результаты работы в файл, а в качестве условия для них работают .sql — файлы — тоже все получится. Соответственно, если миграция нормально не применилась, скрипт должен завершиться с ненулевым кодом возврата.


  1. Samouvazhektra
    25.09.2017 19:06
    +1

    а чем ansible то не угодил? Инфраструктура на нём, куча плейбуков для любых платформ и ПО, можно и для деплоя роли на нём, можно для деплоя fabric https://habrahabr.ru/post/141271/ https://habrahabr.ru/post/257671/


    чтобы его можно было запустить из любой среды Linux без дополнительных действий

    но его надо запускать с сервера, и если надо обслужить несколько серверов — ставить и запускать на каждом.
    А ansible стоит на твоей машине и не требует установки на сервера, можно запускать задачи на несколько серверов одновременно, можно ставить условия в зависимости от платформ, так же поддерживает состояния и не будет ставить то что уже установлено, и помимо прочего состояния поддерживаются для правок конфигов, баз, юзеров и т.п.


    1. Ryder95 Автор
      25.09.2017 19:09

      Вот про ansible я не знал) Сейчас пытаюсь понять — как это работает


  1. AlexLeonov
    25.09.2017 19:53
    +1

    Вы написали заново Ant / Phing
    Поздравляю.


    1. Ryder95 Автор
      25.09.2017 20:26

      Вы правы, Ant я посмотрел и он мне понравился. Конечно, формат XML — это не очень, ИМХО) Ну и единственное, что он предназначен для Java. Это сразу же ограничивает круг применения, я об этом написал) А Phing, как я понял, для PHP


      1. AlexLeonov
        25.09.2017 22:48

        Phing — на PHP, а не для PHP. Что, впрочем, никак не ограничивает его область применения.

        По крайней мере запустить сценарий на phing гораааздно проще, чем Ant.


  1. XakRU
    25.09.2017 20:15
    +2

    1. Ansible + Vagrant для автоматизации развертывания виртуальной машины, просто ansible для настройки продуктивного сервера.
    2. Поглядел Ваш скрипт, всегда считал хорошим тоном — выносить переменные в начало скрипта, у Вас они ни в начале, ни в конце, а где-то посередине и в разных местах.
    А вот тут(клац) я услышал голоса в голове, которые говорили прекрати на это смотреть или «убей их всех».
    Вы серьезно? 3 вложенных цикла for и там же куча if… else с ними. Как это развидеть? О мои глаза....!1

    Статья — очередной костыль, а не велосипед.

    Но когда окажется, что ваш bash-сценарий превысил в объеме сотню строк или вам потребовались средства, которыми bash не обладает, это будет означать, что настало время переходить к языку Perl или Python.
    ©Evi Nemeth


    1. Ryder95 Автор
      25.09.2017 20:20

      Во-первых, я реально не знаю, как обойтись здесь без вложенных циклов и if..else. Это не json, а просто текст, который надо было обрабатывать. Я не имею писать на bash, но взял именно его по той причине, которую описал в статье — очень хотелось иметь сборщик. который практически не требует зависимостей. К тому же, если в файлах сборки используется bash — то, наверное, правильнее писать на bash)
      Но я Вас услышал, действительно, хотелось бы перевести это в более наглядную форму, если это вообще имеет смысл. Всё же данная статья была не столько обзором, сколько просьбой спросить совета: «А правильно ли я сделал или есть уже штуки, решающие мою проблему?»

      Насчёт переменных — я тоже люблю их выносить в начале, но в данном случае мне было удобнее иметь переменные перед основным кодом выполнения скрипта, а функции убрать подальше)


      1. XakRU
        25.09.2017 22:38

        Сомнительное предприятие — не умеете писать на bash, но пришли на Хабр со статьей по самописному скрипту. Можно и Gentoo установить набором команд в одну строку, но зачем? Я не хочу отправлять Вас в Гугл на поиски, возможно Вы с ним в ссоре. Я попробую Вам помочь, если уж Bash, то держите: github.com/serghey-rodin/vesta.
        Но все же лучше взгляните на ansible либо salt stack и придерживайтесь фразы «do not repeat yourself».


        1. Ryder95 Автор
          25.09.2017 22:43

          Ну таки я же сразу сказал, что хочу скорее отзывов и направлений на путь истинный) Всё же отзывов и решений я получил больше, чем просто бы запостил вопрос на SO) Да, я уже решил, что заменю монстра на ansible, но всё равно спасибо)


          1. XakRU
            25.09.2017 22:47

            Я бы написал в личку, но мобильное приложение забрать не позволяет, либо я не знаю как это сделать, но все же есть toster.ru, на so ваш вопрос вероятно заблокируют с пометкой «дубль».


            1. XakRU
              25.09.2017 22:49

              нет словаре слова Хабр, упустил.
              *забрать — Хабр


      1. domix32
        26.09.2017 11:42

        Чем вам тот же питон не угодил в таком случае? Позвать системные утилиты чтобы поставить зависимость не составляет проблемы и на порядок понятнее bash-ада из разных вариантов сочетаний кавычек, скобок и символа доллара. Тут же на месте еще и интерактивную документацию впилить можно.


  1. demimurych
    25.09.2017 20:56
    +3

    А еще есть такая штука как docker


    1. de1m
      25.09.2017 21:08
      +3

      пс… молчи, тут человек героически преодолевает


  1. foxmuldercp
    28.09.2017 18:55

    Для начала, насчет джанги не знаю, но рельсы могут работать в двух режимах — отладки и продакшена. в режиме отладки перечитывается код контролеров, моделей, вьюшек, роутов,
    кроме конфига приложения, ручками только миграции делать.
    В режиме продакшена выключается дофига всего отладочно лишнего, и как запустил код (в том же докере), так оно и будет жужжать.
    У меня кстати, это вот в продакшене жужжит с nginx + passenger. время ответа убыстряется в разы