Хабрахабр

snap & flatpack — трагедия общин

Лонгрид варнинг: вас предупредили, много букв.

Ubuntu очень, очень активно продвигает свой snap, gnome — flatpack. Уже давно ведётся разработка формата дистрибуции приложений, которые были "свободны" от общесистемных зависимостей. Давайте же подумаем про проблему, которую они хотят решить, и цену, которую они просят за решение этой проблемы. Оба обещают рай и свободу от rpm/deb.

Причин несколько: Никто в современном мире не может написать приложение, не используя чужого кода.

  • Многие библиотеки настолько серьёзны, что написать их функционал с нуля — это Грандиозная Задача. Примеры — поддержка unicode, рендеринга шрифтов, математики.
  • Другие библиотеки предлагают довольно скромный набор функций, но написанных так хорошо, что написать хотя бы так хорошо же, почти невозможно. Стандартные библиотеки языков программирования, различные реализации libc, etc.
  • Стоимость работы по взаимодействию с чужим кодом (чему посвящён этот раздел) чаще всего оказывается ниже, чем цена сопровождения своего кода. Плотность "багов на строк кода" с большой вероятностью будет сравнима, а "свои баги" надо самому же и ловить. Чужие (популярные) библиотеки имеют вероятность быть отлажены и исправлены чужими же руками.

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

Если вы пишите индустриальное приложение (приложение, которое похоже на "настоящее"), то: Пример для осознания масштаба драмы: допустим, ваше приложение принимает на вход две строки как опциональные аргументы и выводит их вместе, после нормализации.

  • Вам надо парсер командной строки
  • Который должен принимать юникод
  • И, возможно, давать пользователю подсказку, что он опечатался в имени аргумента
  • Что требует фонетического сравнения
  • И, возможно, регулярных выражений
  • В общем случае вам придётся поддерживать не только юникод, но и другие локали, что требует библиотеки поддержи локалей и ВСЁ то, что люди придумали в контексте локалей.
  • Конкатенация строк с нормализацией — ещё одно применение отдельной библиотеки юникода, сами вы такое не реализуете.
  • Вывод на экран (справки по командной строке, вашего результата) с большой вероятностью потребует поддержки ncurses — библиотеки, поддерживающей разные терминалы (можно обойтись текстовым режимом, но приложения часто используют цветовые возможности).
  • Тесты подразумевают использование тестового фреймворка, возможно, библиотеки для моков.

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

Много. Как вы думаете, сколько библиотек нужно, чтобы гарантированно отработать curl http(s)://...? Вы будете использовать одну, но зависимости ваших зависимостей — это ваши зависимости.

Обратите внимание, у нас появилось два важных слова "использование" и "реализация использования". При том, что использование библиотек неизбежно, само использование может различаться по реализации. В самом грубом виде — возможность вызвать код библиотеки, когда это нужно. Что значит "использование"? А вот реализации этого:

  • Мы можем скопировать код, который делает нужные нам операции. В виде куска кода (copy&paste), как отдельный модуль в языке программирования (объектный файл для компилируемых языков), либо как отдельный модуль (для интерпретируемых языков). Где-то тут же рядом идёт и "скопировать исходный файл библиотеки к себе в каталог с приложением". Какие проблемы это создаёт? Главная, основная проблема — мы теряем (навсегда) связь с оригиналом. Даже если автор оригинальной библиотеки исправит ошибку, мы не будем об этом знать. Более того, если мы просто скопировали код, то следующий человек, работающей над программой, даже не сможет узнать, что этот код "чужой". Фактически, мы срезали путь в вопросе "написать с нуля" и взяли чужое. Однако, мы срезали лишь кусочек, потому что если в этом коде будут ошибки (а они там не будут, они там есть), то их исправление потребует от исправляющего пойти и разобраться в сути проблемы до самого низа. Даже если разбирательство потребует чтения нескольких сотен тысяч строк исходного кода и сотен RFC (а так же комментариев о том, что реализации отличаются от RFC), другого пути у нас нет. Ключевой ошибкой в этом месте является то, что мы потеряли информацию о том, что это код чужой. Наличие комментариев в файле может помочь, но потребует деятельного и глубокого участия человека, потому что если мы напишем в комментарии "взято из libfoobar, src/lib/foo.c версии 364a51577f3782dbf8e5d2482ab941980357c492", то кому-то надо будет посмотреть, где libfoobar находится, какая там версия и что поменялось с предыдущей версии". Чтобы упростить этот процесс, нам нужна машиночитаемая метаинформация.
  • Если мы сопровождаем "чужой код" метаинформацией и используем программы для управления этим кодом (вместо копипаста), то это называется вендоринг, т.е. контролируемое включение чужого кода в свой код. Технически вендоринг может происходить на этапе исходного текста, линковки объектов в исполняемый файл, импорта модулей (в интерпретаторах) из состава приложения, или, даже, динамической линковки с "своей" версией библиотеки (об этом чуть позже).
  • Наконец, мы можем осуществлять динамическую линковку на этапе запуска приложения. Для компилируемых языков это обычные so'шки, для интерпретируемых — наличие модуля в общесистемном импорте. Если его могут импортировать несколько приложений, это это общая библиотека. Если приложение "принесло свой модуль", то библиотека "своя", даже если её интерфейс подразумевает "общую библиотеку". Например, если приложение использует "свою" версию so'шки, вне зависимости от того, отличается ли она от общей, или нет, то это вендоринг. А если импортируется системная, то это общая библиотека (shared library).

Я кратко приведу аргументы, они кратно обсуждались в множестве статей. В чём же различие между этими методами? Каждый из этих аргументов остаётся валидным не смотря на наличие соседних контраргументов:

  • Экономию памяти (оперативной и на диске) для so'шек, уменьшение размеров установленной системы. Чем больше приложений использует одну и ту же so'шку, тем большая экономия памяти. Соответственно, обратно, чем больше "своих" библиотек приносит приложение, тем оно "жирнее".
  • Спор о том, кто следит за уязвимостями — система (предоставляя обновления библиотеки) или автор приложения (вовремя обновляя его).
  • Разрешение конфликта зависимостей (вендоринг решает эту проблему так как общие библиотеки требуют внимания и аккуратности от всех участников процесса, иногда создавая непреодолимые трудности), тот самый легендарный dll hell.
  • Новые версии библиотек — либо они появляются по пожеланию авторов приложения, либо по решению авторов дистрибутива. В одном случае автор может принести нужную ему новую фичу, в другом случае, дистрибутив может принести улучшение существующего приложения за счёт поддержки чего-то нового в библиотеке (например, hidpi экраны начали правильно работать во всех приложениях, динамически линкующиеся с библиотеками qt/gtk).

Вместо этого я хочу сфокусироваться на социальных аспектах водораздела "всё своё" и "всё общее". Все эти вопросы многократно разбирались ранее.

Люди, определяющие какие общие библиотеки доступны в операционной системе диктуют производителям ПО какие общие библиотеки они могут использовать. Общие библиотеки — это кооперация, власть и ответственность. Если все приложения в дистрибутиве собраны с одинаковыми требованиями, то наступает благодать: если в какой-то библиотеке есть ошибка, мейнтейнер этой библиотеки обновляет версию, и исправление автоматически применяется ко всем приложениям. Многое ПО может использовать разные библиотеки, и указание на то, какую точно версию нужно использовать остаётся на усмотрение линкера (для компилируемых языков) или обработчика файла зависимостей (pip, bundler, etc). Если в конкретной ОС принято решение об отказе от старого протокола, каких-то модификациях (например, интерфейса пользователя), то эти изменения так же применятся для всех. Даже если приложение релизится раз в два года, фикс в условном openssl будет применён в течение недели. Это ли не благодать? Look&feel в общем стиле, который (быть может) может быть изменён пользователем один раз и для всех.

Власть и борьба за неё

А что, если какое-то приложение хочет очень-очень новую функцию из библиотеки, а все остальные приложения не хотят его использовать, потому что это, допустим, не LTS-релиз библиотеки, т.е. … Эта благодать требует, чтобы все приложения могли работать с выбранной версией библиотеки. А ведь дистрибутив может отказываться переходить на новые версии "из принципа", ибо мы обещали пользователям только багфиксы, а новые версии — только в следующей версии ОС, которая (вроде бы), выйдет через пол-года. она не достаточно стабильна? Да кто вы такие, чтобы мне рассказывать с какими версиями мне работать? И это вызывает сопротивление со стороны авторов приложения. Мне нужна libfoobar 3. Я автор, я так вижу. 10. 14-pre2 или старше, а не ваша древняя унылая libfoobar 3.

14-pre2. … В этот момент автор просто пишет в требованиях к приложению libfoobar>=3. Может быть. Мейнтейнер берёт и патчит требование, плюс удаляет код, который от этой библиотеки зависел. 16) не окажется в новой версии дистрибутива. Или просто отказывается принимать новую версию с такой зависимостью, пока эта зависимость (libfoobar 3.

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

Поддерживать более старые дистрибутивы, тестировать с разными библиотеками — это сложно. То же самое происходит в ситуации, когда есть несколько дистрибутивов, некоторые новее, некоторые старее. Так что вариант "отгрузить со своими библиотеками" возникает почти сразу же.

Это создаёт предпосылки для возникновения трагедии общин:

  • Каждому производителю (автору ПО) хочется отгружать так, как ему нужно. Подстраиваться под чужие правила (версии) — это трата усилий и времени, тем паче, что в мире много разных дистрибутивов
  • Пользователи хотят новые версии.

Помните про "благодать"? При этом, чем больше приложений идут со своими библиотеками, тем меньше польза от системных библиотек. Если общая библиотека используется 5 разными приложениями из 995 других, то польза этой библиотеки — 0. Чем менее она "всеобщая", тем меньше она благодать. Обидно, да. 5%. Причём, это задевает всех пользователей, даже тех, кто, в принципе, не имеет острой потребности в новой фиче — но если приложение доступно только в вендоринговом виде, то у пользователя нет вариантов.

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

Вот именно тут мы и приходим к спору rpm/deb VS snap/flatpack

GNOME уверен, что будущее за flatpack'ами. Ubuntu очень, очень сильно ратует за snap'ы. Всякие electron'ы, у которых с собой не только подкапотный браузер, но и подкапотная операционная система. Каждый из них — это фреймворк для глубоко индивидуалистических приложений. Общим выступает только ядро, т.е. Свой libc, свой libssl, свои регэкспы, свои ncurses, etc. Дай каждому своё ядро, и получится appliance в форме виртуалки. по сути это тот же контейнеризированное приложение, но для десктопа. Допишите метаданные — и получится контейнер Docker'а.

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

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

Ценность Ubuntu — не в усилиях мейнтейнеров Ubuntu, а в огромном репозитории ПО, идущем из Debian в форме, когда все приложения хорошо работают друг с другом усилиями тысяч мейнтейнеров отдельных пакетов, являющихся "владельцами" дистрибутива Debian. Ubuntu зависит от Debian куда больше, чем хотелось бы Canonical (компания, выпускающая Ubuntu). Добавим чуть-чуть маркетинга и фиксированный lifecycle, который по нраву энтерпрайзу, и получается отличный продукт. Canonical добавляет поверх этого свои усилия по полировке результата — и за это любима некоторыми.

… Который зависит от воли тысяч добровольцев где-то там.

Как разорвать эту зависимость? Что совершенно не устраивает почти любую коммерческую компанию. Чем больше своих приложений, тем меньше "взбрыки" в апстриме будут задевать компанию. Правильно, сделав свой комплект приложений. Достаточно вспомнить историю, когда голосование в Дебиан по поводу systemd похоронило upstart, разрабатывавшийся Canonical.

Не зря это тысячи мейнтейнеров. Но мейнтенить несколько десятков тысяч приложений, некоторые из которых — свой космос (erlang, go, perl, python, R, julia, etc), а некоторые — монстры в соответствующей предметной области (браузеры, emacs, tex, pacemaker, etc) — это неподъёмная работа.

А, пусть, авторы приложений сами мейнтейнят приложения. … И тут есть идея. Авторы получают свободу, Canonical — приложения, которые не зависят от Debian и которые хоть кто-то мейнтейнит бесплатно. Выдадим каждому по песочнице, пусть копаются. Пользователи получают...

… приложения, которые жирные, тяжёлые, у которых обновления нерегулярные и которые с лёгкостью могут держать уязвимости неисправленными годами… Зато некоторые из них shiny new.

Посмотрите на chefsdk. Представьте себе мир, в котором все всё везут с собой… Знаете как это выглядит? Внезапно, у нас внутри одного приложения два erlang'а и несколько десятков копий одних и тех же библиотек, чуть-чуть различающихся по версии. Он отгружает с собой внутри свою postgresql (со своими зависимостями), свой rabbitmq (который зависит от своего erlang'а), плюс chef-server тоже на erlang'е, так что у него тоже свой erlang. внутри между компонентами всё-таки есть общие библиотеки. Это не финальный вариант, т.к. Даже не в финальном виде это выглядит как 600Мб на одно приложение. Если мы их распилим дальше, то получится несколько десятков копий openssl и libc на одно приложение.

… Что, конечно, кратно больше, чем среднее приложение на electron.… И в 12 раз больше, чем целый mariadb сервер (целая СУБД!) или krita или gimp (огромные приложения для работы с графикой).

У меня на компьютере установлено 2000 пакетов (не считая -dev и lib)… 2000*300 = 600Гб (За средний размер получившегося я взял половину от chefsdk, т.к. А если каждый будет такой? Сейчас они занимают около 7Гб (включая ресурсы, вроде документации, текстур редакторов, шаблонов CAD-программ и т.д.). не все настолько ужасны по зависимостям).

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

Я это понимаю, и не одобряю. Я понимаю, зачем Canonical пушит snap.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Проверьте также

Закрыть
Кнопка «Наверх»
Закрыть