Раздел для тех, кому нужно что-то собрать с помощью 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
Там собраны старые версии 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 — 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 должна существовать гибко конфигурируемая система сборки.
Проблема описания сборки и удобного читаемого языка для нее стара, как программирование.
Когда-то ее сильно недооценивали. Известно, что автор формата 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, гораздо серьезней, чем аналогичные проблемы под 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
На выходе мы получаем «каталог-террариум» со следующими папками:
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-модуль.
Что касается бинарных файлов, они копируются без обработки.
Основой плюс нашего подхода по сравнению с жесткой контейнеризацией (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