Skip to content

Generate Portable Linux Applications, just portable folders

License

Notifications You must be signed in to change notification settings

belonesox/terrarium_assembler

Repository files navigation

Инсталляция (Crash Course)

Раздел для тех, кому нужно что-то собрать с помощью TA

  • Клонируйте проект с любого репозитория, где вы читаете этот текст.
    • Так будет его проще обновлять с помощью git pull, сейчас проект TA в фазе активных изменений.
  • Запустите в папке проекта
sudo python3 -m pip install -e .
  • Ваш линукс пользователь должен быть в sudoers, без подтвержения паролем (гуглите «sudoers NOPASSWD»)
    • Запустите — скорее всего поставит нужные системные пакеты в ваш линукс → → проверено на Ubuntu 22.04 (там есть сложности, см ниже.) и Fedora (от FC36)
terrarium_assembler systeminstall
  • Этих пакетов мало, практически абсолютный минимум, все остальное он скачает и будет ставить в контейнеры и виртуальные окружения.

  • Опции и все такое долгая тема, но скорее всего, если у вас есть спек проекта (project.yml) и его надо собрать, то запустите в папке этого проекта

terrarium_assembler --stage-checkout project.yml

ну а все сборки-пересборки:

terrarium_assembler --stage-all project.yml

Сложности с Ubuntu

Там собраны старые версии podman, включая совсем сломанные. Например, если у вас версия

podman 3.4.4+ds1-1ubuntu1.22.04.2, то она сломана (видно, как проблемы с sudo), и надо даунгрейдится

sudo apt-get install podman=3.4.4+ds1-1ubuntu1 -y

Управление и опции комадной строки

Чтобы добится одновременно гибкости и верифицируемости сборки, без запутанной магии классических систем сборок (make-Scons-…, непонятно что происходит, зачем и где)

  • все операции разбиты на некие пронумерованные шаги-фазы, с понятным отношением предшествования
  • для каждой такой фазы генерится bash-файл, с номером и названием фазы в названии, где прописано, какие операции выполняются, этот файл можно поправить, скопировать для экспериментв и т.п.
  • операции
    • идемпотентны
    • по возможности сохраняются состояние, чтобы не повторятся, если «вроде как» исходные данные не изменились (не скачивать заново пакеты, если список и опции такие же, не пересобирать проект, если не изменился код и т.п.) — хотя это не дает 100% гарантии.
  • названия шагов специально длинные,
    • пытаются быть обьясняющими (внутри шелл-файла в комментариях расширенное объяснение)
    • можно их отфильтровывать и «скипать» по отдельным словам (см. дальше)
    • то, что они длинные — не проблема, для скриптов можно записать, для интерактива можно просто кликнуть по шелл-файлу в файловом менеджере или воспользоваться подсказкой — т.е. это почти как вызов из меню.
  • есть опции-комбо, типа --stage-all, если спросить
 terrarium_assembler --help 

про каждую такую он расскажет из чего она состоии:

  --stage-all           stage-init-box-and-repos + stage-download-base-packages + stage-install-base-rpms + stage-download-rpm-packages + stage-
                        install-rpms + stage-save-file-rpmpackage-info + stage-download-base-wheels + stage-init-python-env + stage-checkout-
                        sources + stage-build-wheels + stage-download-wheels + stage-compile-pip-tars + stage-install-wheels + stage-build-
                        python-projects + stage-build-go + stage-save-sofiles + stage-pack + stage-post-pack + stage-make-packages
  --stage-rebuild       stage-init-box-and-repos + stage-install-base-rpms + stage-install-rpms + stage-save-file-rpmpackage-info + stage-init-
                        python-env + stage-build-wheels + stage-compile-pip-tars + stage-install-wheels + stage-build-python-projects + stage-
                        build-go + stage-save-sofiles + stage-pack + stage-post-pack + stage-make-packages

Т.е. самая простая опция это «--stage-all», если проект еще не чекаутился, то лучше сначала вызвать «--stage-checkout», потом «--stage-all».

  • Можно вызывать опции, ссылаясь на номера шагов или их интервалы через запятую:
    • это не очень надежно для скриптов, номера могут чаще переименовываться при обновлении TA, чтобы «воткнуть новые шаги»
    • но очень удобно для ad-hoc-вызовов, вот прямо здесь и сейчас.

Например, сломался почему-то контейнер сборки, видите непонятную ругать со словами «контейнер», можно пересобрать контейнер-платформу быстро, типа

  terrarium_assembler dmi-release.yml --steps=0-7

или, если вы знаете, что пакеты уже выкачаны и хотите пропустить (теоретически, оно не должно перевыкачивать, но возможно вы добавили какой-то пакет в спек, но сейчас это неважно, и вы хотите пропустить шаги скачивания)

  terrarium_assembler dmi-release.yml --steps=0,3,7

Ну или наоборот, не трогать контейнер, но полностью пересобрать питоновый виртуаленв, скомпилировать проекты и сделать из них сборку и пакеты

terrarium_assembler dm-release.yml --steps=21-27,40-59

Еще есть возможность проскипать шаги, используя словарный фильтр. Например, вы без интернета, и не хотите, чтобы гигабайты скачанных пакетов пропали, но пересобрать контейнер надо, и чекаутить проекты не хотите:

  terrarium_assembler dmi-release.yml --steps=0-7 --skip-words=download,checkout

И самое важное слово, которого надо избегать (пока я не вернулся из отпуска — «audit») — там хитрые действия, которые выкачают сотни гигов, займут ваш комп на сутки, и пойдут несколько другим, нестандартным путем...

Вот это примерно, что и «--stage-all»

  terrarium_assembler dmi-release.yml --steps=0-59 --skip-words=audit

Зачем все это, обоснование архитектурных решений

К 2020 году исполнилось 30 лет языку Python, и при этом он стал самым популярным языком в рейтинге TIOBE.

За это время Python, рожденный как идеальный язык для обучения, начиная с уровня младших школьников, стал использоваться профессионалами во всех областях: как в программировании, так и в широком спектре научных областей, будь то астрономия, биоинформатика или статистика, — в буквальном смысле везде.

Можно долго рассуждать о причинах такого успеха: повлиял ли простой синтаксис, понимаемый не только профессиональными разработчиками, но и любым неглупым пользователем, «дзен питона», ориентированный на удобство не компьютера, а пользователя, максимальная простота и читаемость, удобство для пользователя в разработке и отладке или что-то ещё.

На самом деле, это неважно, а важно то, что сейчас Python — самый удобный язык (по крайней мере, для прототипирования и разработки высокотехнологических приложений), ибо в нем есть «батарейки» практически для всех областей высоконаучной разработки.

Если раньше для разработки и прототипирования новых алгоритмов нужно было прибегать к использованию специализированных «пакетов для математиков» — систем типа Mathematica, Mathcad, Mapple и т.п. — с переписыванием их для реального использования на языки промышленной разработки (C/C++/Java), то сейчас разработку новых алгоритмов принято вести на Python, причем заменяя или дополняя привычные «статьи с псевдокодом», алгоритмы в которых невозможно проверить и исследовать без реализации, на Jupyter-ноутбуки, являющиеся гибридами чередующегося текста, формул и работающего и проверяемого кода на Python, выдающего верифицируемые графики и визуализации. Более того, движение paperswithcode.com, и множество статей типа «Transparency and reproducibility in artificial intelligence» констатируют текущий кризис воспроизводимости научных результатов и фактически призывают, чтобы все научные результаты были в вышеизложенном формате.

Причем разработку новых алгоритмов на Python можно вести от первых идей, через бурю множества итераций, до выкатки в реальное использование, таким образом получая обратную связь по тестированию непосредственно от пользователей и итерационно улучшая алгоритмы без долгих циклов переписывания на другие языки и фреймворки. И лишь в крайних случаях, для небольших и критических кусков кода, прибегая к ускорению и реализации в виде нативных C-модулей.

Стоит отметить, что сам интерпретатор Python существует практически под все платформы и операционные системы. По этой причине не сразу становятся видны проблемы: «почему бы коду на Python не работать на чём угодно, от привычных x86 до ARMов, MIPSов и Эльбрусов, от древних версий Windows до сертифицированных российских линуксов?»

К сожалению, с этим все непросто:

  • Базовый интерпретатор Python, действительно, как правило, есть везде.
  • Но сборка всех необходимых стеков Python-модулей — серьезная проблема, по крайней мере, для старых версий Windows и малораспространенных линукс-дистрибутивов (к которым относятся и российские сертифицированные линукс-дистрибутивы), пакетная база которых в десятки раз меньше, чем у популярных дистрибутивов с большим сообществом мейнтейнеров (Fedora-Debian-Ubunta), а используемые базовые библиотеки серьезно устарели, так что собрать современный прикладной стек поверх них попросту невозможно.
  • Попытка сделать «единый дистрибутив-пакет» с минимумом вариаций (windows/linux) не сильно менее проста, особенно в связи со слабой обратной бинарной совместимостью (libc, ld.so) линуксов и отсутствием поддержки в старых сертифицированных линуксах технологий изоляции и контейнеризации (docker/flatpack/snapd), которые могли бы эту проблему решить.
  • Особенно, когда собирается много питон-пакетов-проектов, со своими зависимостями, и надо достаточно целостно уметь собирать некую «монорепу» с разрешением общих зависимостей, и возможностью манипуляции ими («переход на новый numpy-scipy-opencv…»)
  • При этом, часто надо добиться и продемонстрировать (аудит) прозрачность сборки, чтобы не было ни одного бинарного-исполняемого файла, не имеющего исходников — для тех, кто привык не задумывась ставить PIP-пакеты с скомпилированными бинарными библиотеками, может быть не очевидно, что чтобы пересобрать все это с нуля, нужно по сути «собрать линукс-дистрибутив».

Для решения этих задач был реализован комплекс управления сборкой Terrarium Assembler с версиями под Linux и Windows.

Основная идея:

  • Программировать используя все возможности питона, всю его магию, включая необъятное число незаменимых на других языках библиотек
    • Без ограничений Cython/Numba/Pyston и других компиляторов и транспайлеров, работающих с ограниченной версий Python.
  • Собрать для кода все требуемые зависимости на Python и системном уровне.
  • Скомпилировать Python-код в бинарный, через транспайлинг в C/C++, добившись ускорения и монолитности.

Основная идея

Проблемы и решения

Спрятать исходный Python-код, и получить ускорение

Основная реализация Python — CPython, наиболее распространённая, де-факто эталонная реализация языка программирования Python, является интерпретатором байт-кода, написан на C. Разработка CPython идет уже больше тридцати лет: сотни тысяч коммитов, миллионы строк кода, полторы тысячи контрибьюторов только в разработке ядра, не говоря уже о сотнях тысяч пакетов.

Есть и альтернативные реализации, включающие компиляцию Python-кода

  • в более эффективный байткод PyPy — ускоренная, альтернативная реализация Python,
  • в JIT-компиляцию Pyston — ускоренная, (до 30% по сравнению с CPython) реализация Python),
  • в байт-код Java-машины — Jython.

Однако все эти решения, как правило, приводят к потере совместимости с огромными стеками научных модулей и при этом не решают проблему односторонней компиляции со сложным восстановлением.

Тем не менее в последние годы появился быстро развивающийся проекта Nuitka (произносится как «ньютка», см. доклад автора фреймворка с разъяснением произношения), названный немецким программистом в честь своей русской жены («Аньютка»), который:

  • В целом, совместим с CPython-стеком: можно использовать практически всё, что можно установить в стандартный CPython (дистрибутивы Python в Linux-дистрибутивах, Anaconda);
  • Транслирует Python-код в запутанный автогенерируемый С/C++ код, после чего компилируется стандартными С/C++ компиляторами (gcc, clang, msvc); в результате, благодаря огромному опыту оптимизации C++ компиляции, часто достигается ускорение — бенчмарки производительности Nuitka в сравнении со стандартными CPython лучше в разы, а местами и на порядки;
    • При этом получается (особенно в коммерческой версии), запутанный бинарный код, из которого чрезвычайно сложно восстановить исходные алгоритмы: сложнее даже, чем при декомпиляции алгоритмов, написанных непосредственно на С/C++;
  • При сборке анализирует зависимости от скомпилированных библиотек (DLL, .so) и автоматически собирает каталог со всеми зависимостями.

Разумеется, не все так идеально, как может быть:

  • Система вычисления зависимостей модулей работает с помощью статического анализа и зачастую
    • пропускает необходимые Python-модули, без которых ничего не будет работать;
    • наоборот, глубоким замыканием «приносит» модули, которые не используются при нормальной эксплуатации программ, но при этом занимают много места в результирующей сборке.

Все это можно решить с помощью различных «головоломных» опций, индивидуальных для каждой собираемой утилиты. Поэтому, для удобства, поверх Nuitka должна существовать гибко конфигурируемая система сборки.

Гибкая конфигурация — YAML + Jinja templates

Проблема описания сборки и удобного читаемого языка для нее стара, как программирование.

Когда-то ее сильно недооценивали. Известно, что автор формата make-файлов не осилил сходу написать правильный парсер, учитывающий переводы строк и отступы, и заложился на необходимость использования символов табуляции для вложенных правил, тем самым обрекая на десятилетия вперед миллионы программистов на поиск труднонаходимых ошибок.

Были и эпохи мучительно читаемых XML-файлов, изобретаемые DSL-форматы с самодельными парсерами. В целом, сейчас наибольшее распространение имеют JSON- и YAML-конфигурации.

При этом читаемость безусловно лучше у YAML, ведь, как и Python, он использует стандартную, встроенную в восприятие человека способность понимать структуру текста, основываясь на отступах и пропусках — то, что человечество освоило с момента изобретения печатного слова.

Однако формат YAML не идеален. К сожалению, в нем нет конфигурируемой гибкости, когда можно в определении одного параметра ссылаться на значение другого. Что-то подобное пытались в свое время реализовать с помощью концепции «YAML anchors» («Node Anchors»), но, к сожалению, это работает только для очень малого количества случаев.

А это просто необходимо — чтобы поменяв одну цифру, например, в версии чего-то (платформы, модуля, версии Python), получить полную и независимую перестройку процесса сборки: выкачку новой версии, выкачку всех модулей, специализированную сборку и перекомпиляцию.

Ранее, подобные задачи в сборочных системах решались с помощью препроцессора, такого как cxx или m4.

Сейчас ситуация не сильно изменилась: в самой модной системе управления конфигурациями, Ansible, используется комбинация YAML и шаблонов Jinja.

Мы также применили схожий подход: перед разбором YAML-файла выполняется многократное Jinja-препроцессирование с подстановкой YAML-ключей верхнего уровня.

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

Jinja-шаблоны, даже без YAML, у нас также активно применяются в Linux-сборке (см. далее) для препроцессирования конфигурационных файлов, скриптов выполнения

Проблемы сборки под Linux и их решение

Проблемы обеспечения переносимой сборки под Linux, гораздо серьезней, чем аналогичные проблемы под Windows. Собственно, настоящий успех и популярность Windows обеспечивается в большей степени огромными инвестициями в переносимость и поддержку приложений под предыдущие версии.

Здесь речь идёт и о совместимости интерфейсов: все интерфейсы (API/ABI/…) Windows проектируются «с запасом», с дополнительными полями, которые можно для чего-то использовать в следующих версиях, не ломая API/ABI и обеспечивая обратную совместимость.

По большому счету, можно считать, что каждая следующая версия Windows содержит в себе, как матрешка, все предыдущие, что, по сути, позволят запускать любые скомпилированные приложения, написанные хоть десятки лет назад под 32-битную Windows 95, под современной 64-битной Windows 10.

В Linux, к сожалению или к счастью, подход другой. API/ABI всех библиотек, включая самые ключевые, такие как «libc», постоянно меняются, примерно раз в два года приводя к мажорным изменениям и несовместимости при попытке запустить приложения, скомпилированные со старой версией. Многим хорошо знакомы ошибки такого типа:

  Error: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found 

Если сравнивать Windows-подход, когда, по сути, каждое приложение несет с собой почти все необходимые версии библиотек, а система несет с собой все версии библиотек и интерфейсов всех предыдущих версий, с Linux-подходом, где в системе все должно быть целостно: только минимальный набор библиотек только нужных версий — то однозначно нельзя выбрать победителя.

  • Linux-подход:

    • Преимущества: Минимизация объема диска под библиотеки и минимизация объема памяти под загруженные версии библиотек (всегда загружена одна версия каждой библиотеки). Максимальный уровень переиспользования. Собственно, поэтому Linux-инсталляции, даже со всеми поставленными приложениями, целостно скомпилированными мейнтейнерами, занимают на порядки меньше места и едят меньше памяти, чем Windows-инсталляции со схожим набором приложений.
    • Минусы: Нужно регулярно перекомпилировать приложения под новые версии системных интерфейсов (ядра, базовые библиотеки, такие как «libc»). При этом решать регулярно проблему «dependency hell». Или тащить все с собой, но это непросто (далее мы рассмотрим, как это принято делать).
  • Windows-подход:

    • Преимущества: Однажды собранное и скомпилированное приложение, скорее всего, будет работать всегда.
    • Минусы: Приходится тащить кучу версий одних и тех же библиотек, что может привести к «DLL Hell». К тому же потребление диска и памяти будет большое.

«Dependency Hell» vs «DLL Hell» — это классическая дилемма разработки и выкатки, и надо признать, что в последние годы в большей части промышленной разработки, где не надо считать каждый байт памяти и хранилища (встроенные системы и т.п.), проблема стала решаться в пользу той идеи, которая утверждает, что «приложение должно нести все свое с собой», вне зависимости от системы. Такого подхода придерживаются и Java-приложения со скомпилированными JAR-/WAR-файлами, и множество методов контейнеризации (docker, crio, openvz), давшие возможность микросервисной архитектуре, где, по сути, даже для минимальной функциональности, реализуемой мини-командой за неделю, в контейнере идет своя версия библиотек ОС и весь нужный стек разработки.

И даже в десктопных Linux-приложениях, даже в популярных дистрибутивах, где тысячи мейнтейнеров поддерживают целостную сборку почти всех возможных opensource-приложений, появились платформы типа AppImage, Flatpack и Snap, не говоря уже о Wine-bottles для Windows-приложений, запускаемых под Linux.

Так что для редких, высокотехнологических-научных приложений, которым, увы, «не с кем» в системе разделять свои библиотеки машинного обучения и распознавания образов, вполне разумно использовать подход «все с собой», особенно когда стоит задача выкатывать их на целый спектр часто очень древних «сертифицированных линуксов», в которых нет и никогда не будет того что нужно.

Самое грустное в том, что на этих системах нет и тех технологий «пакетной» или «контейнерной» доставки — ни docker/openvz, ни AppImage, ни Snap, ни Flatpack. Недавно только объявили, что в скоро в следующей сертифицированной версии одного такого дистрибутива docker, но ведь еще придется ждать лет 10-15, пока заменят старые рабочие места с предыдущими версиями сертифицированных систем и он будет на всех рабочих местах. Впрочем, docker не панацея, когда, наоборот, приложение должно активно взаимодействовать с окружением ОС: читать-записывать файлы, перехватывать работу с экраном и принтером и т.д. К сожалению, надо признать, что ничего готового нет и придется делать что-то свое.

Собственно, большинство разработчиков решений под сертифицированные версии так и делают, маскируя притаскивание «своей системы» под обычные RPM-пакеты, причем принесенные библиотеки даже «разрушают» целевую систему.

Но обычно, встречаются линуксы c RPM- и DEB-пакетами и надо было просто сделать, чтобы приложение

  • ставилось простым копированием, без доступа к интернету;
  • запускалось и настраивало все что нужно само;
  • работало под все сертифицированные линуксы.

На выходе у нас будет каталог-«террариум» — что-то вроде контейнера, но с возможностью выхода за его пределы: возможность вызвать утилиты целевой системы, возможность работы с файлами и оборудованием. А «террариумом» он у нас называется, ибо основное его наполнение — «земноводные», скомпилированные или поставляемые в исходниках Python-приложения.

Проблема вычисления зависимостей

Для решения проблемы зависимостей, какие библиотеки потребуются вашему приложению, разумно взять систему распространенного Linux-дистрибутива с максимальным числом пакетов, после чего «выравнять» спецификации сборки, указывая требуемые пакеты в соответствии с этим дистрибутивом и используя внутренние инструменты мейнтейнеров этого дистрибутива для вычисления зависимостей.

Как платформу, мы выбрали RPM-дистрибутивы серии Fedora Core, но технически можно использовать как базовый [любой RPM-based дистрибутив](http://ru.wikipedia.org/wiki/Список_дистрибутивов_Linux#Основанные_на_RPM — список RPM-дистрибутивов), в котором есть утилита dnf repoquerу, позволяющая вычислять зависимости пакетов.

Проблема «локинга» интерпретатора библиотек

Эта проблема связана с некоторой гибкостью форматов исполняемых файлов под Linux.

Если «Portable_Executable» — формат выполнения файлов под Windows достаточно универсальный (любая версия Windows поймет и сможет исполнить скомпилированный windows-bin-файл, и благодаря обратной совместимости, которую мы уже обсуждали выше, все скорее всего будет хорошо), то ELF — формат выполняемого файла под Linux более хитрый, содержащий жесткую ссылку на «интерпретатор-загрузчик», как правило что-то специфичное для конкретного дистрибутива типа /lib64/ld-linux-x86-64.so.2. Тогда, соответственно, если мы хотим получить нечто замкнутое и работающее в целевой системе, надо:

  • взять из сборочной системы интерпретатор — это будет наш интерпретатор, назовем его ld.so;
  • взять все библиотеки-зависимости;
  • запатчить, используя утилиту patchelf, все исполняемые файлы, которые мы берем с собой, чтобы они были завязаны на наш «интерпретатор»;
  • для запуска нашего запатченного bin-файла изнутри целевой системы нужно запускать его, запуская наш интерпретатор и передавая ему запатченный exe-файл в качестве параметра.
#!/bin/bash
x="$(readlink -f "$0")"
b="python3.8"
d="$(dirname "$x")/.."
ldso="$d/pbin/ld.so"
realexe="$d/pbin/$b"
ulimit -S -c unlimited 
export GI_TYPELIB_PATH="$d/lib64/girepository-1.0"
export GDK_PIXBUF_MODULE_FILE="$d/lib64/gdk-pixbuf-2.0/2.10.0/loaders.cache"
export GDK_PIXBUF_MODULEDIR="$d/lib64/gdk-pixbuf-2.0/2.10.0/loaders"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$d/lib64"
LANG=C LC_ALL=C PYTHONPATH="$d/local/lib/python3.8/site-packages:$d/local/lib64/python3.8/site-packages:$PYTHONPATH" exec -a "$0" "$ldso" "$realexe" -s "$@"

Однако, если идет вызов утилиты изнутри другой бинарной утилиты, то патч не нужен, и ее можно положить просто копированием.

Какие утилиты отобрать из пакетов, какие из них запатчить (определяется регулярными выражениями), а какие просто скопировать, определяется следующей секцией файла спецификаций:

bin_regexps:
  need_patch:
    - /usr/bin/python3.\d
    - /usr/bin/bash
    - /usr/bin/pdftoppm
    - /usr/lib/cups/filter/imagetopdf
    - /usr/lib/cups/filter/imagetoraster
    - /usr/lib/cups/filter/pdftopdf
    - /usr/bin/gs
    - /usr/bin/tesseract
    - .*screenmark
    - .*dmextract-printer
    - .*dmextract-screen
    - .*smoketest

  just_copy:  
    - /usr/bin/gs
    - /usr/local/bin/dmprinter  #need rewrite

Структура Linux-террариума

На выходе мы получаем «каталог-террариум» со следующими папками:

  • lib64 — Сюда попадают все динамические библиотеки, которые мы вычислили по зависимостям;
  • pbin — Сюда попадают все выполняемые файлы, которые мы нашли в пакетах; они попали под отбор регулярных выражений «need_patch», мы их запатчили под наш интерпретатор-загрузчик;
  • bin
    • usr/bin — Утилиты, попавшие под «just_copy», просто скопированные;
    • ebin — Скрипты вызова наших утилит из системы-носителя.

Могут быть дополнительные каталоги, файлы данных, тестов, конфигураций. Технически, шаблон для сборки террариума определяется в параметре, указывающем на каталог шаблонов:

templates_dirs: 
    - linux-deploy-template

Каждый файл в этом каталоге, если он текстовый, считается шаблоном и обрабатываться с помощью Jinja-шаблонов, включая само имя, так что файл с именем python{{ python_version_1 }}.{{ python_version_2 }} превращается, например, в python3.10.

Таким образом, внутри скриптов запуска, лежащих в «ebin», можно делать, например, такие блоки:

{% if release %}
b="screenmark"
{% else %}
b="python{{ python_version_1 }}.{{ python_version_2 }}"
{% endif %}

…

{% if release %}
LANG=C LC_ALL=C PYTHONPATH="$d/local/lib/python{{ python_version_1 }}.{{ python_version_2 }}/site-packages:$d/local/lib64/python{{ python_version_1 }}.{{ python_version_2 }}/site-packages:$PYTHONPATH" exec -a "$0" "$ldso" "$realexe" "$@"
{% else %}
LANG=C LC_ALL=C PYTHONPATH="$d/local/lib/python{{ python_version_1 }}.{{ python_version_2 }}/site-packages:$d/local/lib64/python{{ python_version_1 }}.{{ python_version_2 }}/site-packages:$PYTHONPATH" exec -a "$0" "$ldso" "$realexe" -m screenmark "$@"
{% endif %}

Т.е. в случае установленной опции «release» — запускать скомпилированный выполняемый файл, а в противном случае (режим «отладки и сырого питона») — запускать соответствующий Python-модуль.

Что касается бинарных файлов, они копируются без обработки.

Terrarium Adapter: Запуск файлов из целевой операционной системой

Основой плюс нашего подхода по сравнению с жесткой контейнеризацией (docker/flatpack и т.п.) — возможность напрямую обрабатывать файлы/устройства целевой системы и вызывать ее утилиты, отсутствующие в нашем «террариуме».

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

Причем указать мы это не можем: запуск утилит может происходить где-то в глубинах используемых нами Python-пакетов, форкать и патчить которые, нам не хватит никаких сил.

Для решения этой проблемы есть Python-пакет terrarium_adapter — пакет обеспечения внешних вызовов и остальной совместимости Linux-террариума.

Достаточно подключить его внутри нашей программы на python…

import terrarium_adapter

… и с помощью технологии monkey-патчинга все вызовы внешних утилит через модуль «subprocess» будут проходить хитрый анализ, который позволяет:

  • в случае наличия у в нашем террариуме утилит с таким именем вызывать именно их;
  • в противном случае хитрым образом запускать внешние утилиты из целевой системы.

Концептуальная схема для понимания

Концептуальная схема для понимания

Несколько пакетов из одного проекта, для разных ролей.

Вообще, это не очень правильный путь, т.е. мы не используем путь «супераппа», из которого единым образом генерится клиент-сервер- и т.п. → это путь к глухому монолиту (где практически невозможно быстро вносить изменения, либо не сломав все, либо без сверхдолгих перетестирований всего), т.е. если проекты имеют разные жизненные циклы (обновления, требования к тестированию и надежности) — то лучше сразу их разделять на разные проекты, ибо несмотря на общесть используемых проектов-исходников тут возникает куча удобной гибкости, когда и что обновлять, и перетестировать.

Не говоря уж о дичайшей сложности «дополнительного уровня косвенности», который сделает и так непростой спек-файл вовсе невменяемо сложным.

Но пока оставлена некая (возможно экспериментальная) возможность сгенерить из одного результирующего каталога сделать несколько пакетов, которые ставят почти одно и то же в одну папку — отличаясь только одним — файлом «isodistr.txt» (где прописано название пакета), который должен сообщить «в какой роли участвует этот пакет».

Т.е. например, можно сделать пакет, который включает в себя все конфиги для условно некой базы с интерфейсами и отдельно прокси-сервисами. Все сразу. И его можно использовать в разных режимах — все сервисы на одной ноде, часть сервисов (только прокси), (только база) и т.п.

Один из вариантов — ставить этот пакет везде, на все ноды дополнительно конфигурируя в какой роли его использовать (хоть в /etc/xxx/config.yml, хоть в /etc/xxx/config.yml класть, хоть куда). Все это развертывание можно делать хоть Ansiblом-SoltStackом-Puppet—, да хоть rsync c ssh, или тупыми инструкциями для документации.

Но, возможно, чем-то полезный вариант, это

  • когда по одной сборке сразу делаем несколько пакетов, отличающихся только названием и файлом «isodistr.txt» (версии, таймстампы — все одинаковое)
  • эти пакеты ставятся на соответсвующие ноды (хоть из одного репа, хоть как-то селектируясь вилдкардами)
   ssh proxy.site.gov apt-get install -y "product-proxy*.deb"
   ssh frontend.site.gov apt-get install -y "product-frontend*.deb"
   ssh db.site.gov apt-get install -y "product-database*.deb"

Тогда настроечным скриптам инсталляции-запуска надо посмотреть на всегда имеющийся isodistr.txt, чтобы понять, в какой роли весь этот софт уже оказался на этой ноде.

Такая штука сейчас делается «самыми легкими касаниями — если заменить строковый label в спеке на список типа:

label:
  - dmic
  - dmic-balancer
  - dmic-proxy
  - dmic-storage

About

Generate Portable Linux Applications, just portable folders

Resources

License

Stars

Watchers

Forks

Packages

No packages published