Своя система сборки на Linux

    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
    • +3

      Puppet, chef, ansible?

      • +1
        Как вариант еще можно рассмотреть SaltStack
      • +3
        Рассматривали ли как вариант make и/или например ansible?
        • +1
          Честно, первый раз слышу о них) Может быть, если бы знал, то не сделал бы это. Спасибо, прочту, отпишусь
          • –1
            Ну make точно нет, так как проверки выполнения целей там нет, а мне она нужна был
            • +1
              М-м… ну вообще-то он даже синтаксисом на ваше решение похож :)
              Конечно в плане проверки «установлен ли пакет»… надо самому правило написать,
              но make более масштабная вещь, в каком-то смысле это не его уровень.
              Для автоматизации развёртывания это как раз ansible со товарищи…
              Так что Вы зря про «точно нет»…

              P.S. Просто он наиболее близок к bash, как я понял Вам это удобно. Все цели
              это по сути bash команды.
              • 0
                Я, если честно, с него и брал синтаксис. Изначально я и пытался сделать всё на make, но сильно с ним намучился. И у меня не получилось там сделать подобия проверки выполнения цели. Если я правильно понимаю, make был сделан для C/C++ и в основном работает с ними. Скажите, как можно сделать проверку выполнения цели в make?
                • 0
                  >> Если я правильно понимаю, 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 позволяет делать более сложные и иерархичные вещи. Его цель «управление зависимостями» сборки.
                  • 0
                    Спасибо, я понял. К сожалению, не всегда результатом работы будет файл) Наверное, ansible более интересный в этом плане. Буду копать в его сторону
                    • 0
                      Да конечно. От ansible не отговариваю, это очень удобная «штука».
                      Вам главное понять, что ansible и make не взаимоисключающие вещи )
                  • 0
                    make идеален, если вы преобразуете файлы. Соответственно, в его терминах — сокет или pid-файл — результат выполнения операции «перезапустить серверный процесс», если условием является config-файл.
                    Если скрипты миграции базы у вас пишут свои результаты работы в файл, а в качестве условия для них работают .sql — файлы — тоже все получится. Соответственно, если миграция нормально не применилась, скрипт должен завершиться с ненулевым кодом возврата.
            • +1

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


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

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

              • 0
                Вот про ansible я не знал) Сейчас пытаюсь понять — как это работает
              • +1
                Вы написали заново Ant / Phing
                Поздравляю.
                • 0
                  Вы правы, Ant я посмотрел и он мне понравился. Конечно, формат XML — это не очень, ИМХО) Ну и единственное, что он предназначен для Java. Это сразу же ограничивает круг применения, я об этом написал) А Phing, как я понял, для PHP
                  • 0
                    Phing — на PHP, а не для PHP. Что, впрочем, никак не ограничивает его область применения.

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

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

                    Насчёт переменных — я тоже люблю их выносить в начале, но в данном случае мне было удобнее иметь переменные перед основным кодом выполнения скрипта, а функции убрать подальше)
                    • 0
                      Сомнительное предприятие — не умеете писать на bash, но пришли на Хабр со статьей по самописному скрипту. Можно и Gentoo установить набором команд в одну строку, но зачем? Я не хочу отправлять Вас в Гугл на поиски, возможно Вы с ним в ссоре. Я попробую Вам помочь, если уж Bash, то держите: github.com/serghey-rodin/vesta.
                      Но все же лучше взгляните на ansible либо salt stack и придерживайтесь фразы «do not repeat yourself».
                      • 0
                        Ну таки я же сразу сказал, что хочу скорее отзывов и направлений на путь истинный) Всё же отзывов и решений я получил больше, чем просто бы запостил вопрос на SO) Да, я уже решил, что заменю монстра на ansible, но всё равно спасибо)
                        • 0
                          Я бы написал в личку, но мобильное приложение забрать не позволяет, либо я не знаю как это сделать, но все же есть toster.ru, на so ваш вопрос вероятно заблокируют с пометкой «дубль».
                          • 0
                            нет словаре слова Хабр, упустил.
                            *забрать — Хабр
                      • 0
                        Чем вам тот же питон не угодил в таком случае? Позвать системные утилиты чтобы поставить зависимость не составляет проблемы и на порядок понятнее bash-ада из разных вариантов сочетаний кавычек, скобок и символа доллара. Тут же на месте еще и интерактивную документацию впилить можно.
                    • +3
                      А еще есть такая штука как docker
                      • +3
                        пс… молчи, тут человек героически преодолевает
                      • 0

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

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.