Быстрый расчёт сборочного окружения пакета (Павел Волнейкин, OSSDEVCONF-2023)
- Докладчик
- Павел Волнейкин
Во время выпуска и сопровождения дистрибутивов на базе Sisyphus и его бранчей часто возникает задача пересборки большого количества пакетов. Порядок следования сборочных заданий и их состав играет в этом случае немаловажную роль, поскольку неудачный порядок или состав создаёт паразитную нагрузку на сборочный сервер, снижает его КПД. Для того чтобы снизить её, состав и порядок сборочных заданий должны быть близки к оптимальному. Одним из шагов в этом направлении является оценка влияния результатов завершённого сборочного задания на весь репозиторий, то есть оценка изменений в условиях сборки пакетов, которые вызвало данное задание. Для того чтобы снизить время и ресурсы, необходимые для таких вычислений, потребовалось внести ряд изменений в программу работы со сборочными окружениями на базе Hasher. Обзору этих изменений и новых возможностей посвящён настоящий доклад.
Содержание
Видео
Презентация
Thesis
Что такое сборочное окружение исходного SRPM-пакета? Это множество всех пакетов, требуемых для полной его сборки. Множество формируется из двух частей. Первая часть — это базовое сборочное окружение, единое для всех пакетов. Вторая часть — это дополнительное подмножество пакетов, формируемое на основе информации, указанной в spec-файле исходного пакета (тег BuildRequires).
Получается, что полное сборочное окружение пакета известно заранее? Ведь первая часть — константа, а вторая указывается в самом пакете? Не совсем так. В~spec-файле перечисляются имена лишь тех дополнительных пакетов, которые непосредственно нужны для сборки. Но сами эти дополнительные пакеты тоже имеют свои зависимости, поэтому окончательное множество пакетов зависит от текущего состава всего репозитория. К тому же, иногда указание на дополнительные пакеты в spec-файле имеет не вполне однозначный характер. В этом случае спрогнозировать результат ещё сложнее.
До недавнего времени самым простым способом определить сборочное окружение пакета был такой: запустить сборку интересующего пакета, и после того, как сборочное окружение будет готово, прервать её. Полученное окружение можно было исследовать, в том числе и определить его состав. Те, кто вникал в эту задачу глубже, наверняка нашли, что используемая для сборки пакета команда hsh-rebuild имеет специальную опцию --install-only, позволяющую автоматически прерывать сборку пакета после установки всех требуемых зависимостей. Однако и в этом случае происходило фактическое получение файлов пакетов и их установка в сборочное окружение. А ведь задача определения состава сборочного окружения состоит лишь в том, чтобы получить сведения о входящих в него пакетах. Кажется, что её можно решить без фактической обработки файлов этих пакетов. И это действительно так. Тем более, что механизм вывода информации о подготовленном к установке наборе пакетов давно реализован в apt — программе, управляющей процессом установки пакетов в дистрибутивах ALT.
Таким образом, первый шаг состоял в том, чтобы задействовать этот механизм — команду apt-get --print-uris во время работы Hasher по подготовке сборочного окружения. Соответствующая опция --print-only была добавлена в скрипт hsh-install[1].
Действие её аналогично опции --install-only, но фактической обработки файлов пакетов не происходит. Вместо этого выводится список с подробной информацией о пакетах, которые должны были бы быть установлены.
Как выглядит процедура определения состава сборочного окружения пакета при использовании hsh-install --print-only?
Сперва разворачивается базовое сборочное окружение и выводится информация о входящих в него пакетах. После этого вместо фактической установки указанных в spec-файле дополнительных пакетов выводятся сведения о них и обо всех других пакетах, которые понадобились бы для их установки. Наконец, полученные сведения о составе базового сборочного окружения и обо всех дополнительных пакетах объединяются в один список.
Достоинство этой процедуры в том, что её очень просто оптимизировать. Действительно, если мы знаем, что состав репозитория не менялся, то, следовательно, нет и необходимости повторно разворачивать базовую сборочную среду — ведь в результате работы в режиме --print-only она не будет изменена. Это значит, что однажды развёрнутая базовая сборочная среда может быть использована повторно с различными исходными пакетами для вычисления соответствующих им наборов сборочных зависимостей.
Но это ещё не всё. Вторая очевидная оптимизация заключалась в том, чтобы отказаться от копирования исходного пакета в сборочную среду с последующей его распаковкой и вместо этого использовать только spec-файл, который довольно просто извлечь из исходного пакета заранее. В Сизифе нашлось лишь 12 пакетов, для которых данная оптимизация оказалась невозможной ввиду использования директивы %include.
С учётом этих двух оптимизаций вышеописанная процедура была реализована в виде скрипта под названием hsh-print-req.
Кроме очевидных оптимизаций, данная процедура имеет и одно серьёзное ограничение, касающееся не всех, но существенной части имеющихся в репозитории SRPM-пакетов. Дело в том, что часть исходных пакетов имеет так называемые предварительные сборочные зависимости. Их spec-файлы содержат директиву BuildRequires(pre). В Сизифе около 40% таких пакетов. По правилам, такие spec-файлы должны анализироваться в среде, где установлены все перечисленные в этой директиве пакеты. Иными словами, анализ spec-файла с директивой BuildRequires(pre) в базовом сборочном окружении потенциально может привести к ошибке. Это значит, что перед расчётом сборочных зависимостей в режиме --print-only в базовую сборочную среду должен быть доустановлен ряд пакетов. Сделать это можно. Но фактическая доустановка пакетов означает, что базовая сборочная среда изменяется. Получается, что 40% пакетов исключают возможность повторного использования сборочной среды? Это не совсем так.
Во-первых, исходным условием для вычисления состава сборочного окружения по-прежнему является базовая сборочная среда. Отличие состоит в том, что в одном случае в ходе вычисления эта среда не изменяется, а в другом — изменяется. Отсюда возникает задача тиражирования заранее подготовленной сборочной среды. Смысл её в том, чтобы растиражировать однажды построенную базовую сборочную среду и затем сэкономить время и ресурсы, используя готовые экземпляры при вычислениях.
Во-вторых, изменённая базовая сборочная среда тоже может быть использована повторно — для тех исходных пакетов, которые имеют соответствующий ей набор предварительных зависимостей (то есть зависимостей, указанных в BuildRequires(pre)). Простые исследования показали, что примерно на 7400 исходных пакетов приходится всего около 700 уникальных списков предварительных зависимостей. Это значит, что в среднем, каждое сборочное окружение, полученное доустановкой пакетов в базовое, может быть использовано повторно около 10 раз. Последующие исследования показали, что для некоторых популярных наборов предварительных зависимостей, это число превышает 200 раз.
Оба вышеуказанных замечания, если рассматривать их вместе, приводят к идее тиражирования не только базового, а любого заранее сформированного окружения. Как будет выглядеть процедура определения состава сборочного окружения пакета, если учесть всё сказанное? Примерно так:
- берём экземпляр базового сборочного окружения и spec-файл;
- определяем, какие предварительные зависимости имеет пакет;
- определив их, ищем экземпляр сборочного окружения, который им соответствует;
- если такого нет, то создаём его на базе экземпляра базового окружения;
- на базе подходящего сборочного окружения определяем конечный набор сборочных зависимостей (установленные в окружение пакеты + пакеты, которые должны были бы быть установлены).
Другой, потенциально более оптимальный вариант, состоит в том, чтобы
выбрав и подготовив окружение, провести на нём вычисления для всех
пакетов, которые требуют именно данного окружения. Практика покажет,
какой вариант будет лучше. Но как в одном, так и в другом случае,
присутствует задача простой идентификации состава сборочного
окружения. С одной стороны, такой идентификатор нужен для хранения и
поиска заранее подготовленных окружений. С другой стороны, тот же
самый идентификатор должен использоваться и по отношению к пакетам,
раз мы хотим установить взаимно однозначные соответствия между
окружениями и пакетами, которые требуют данных окружений.
Идентификации по составу сборочного окружения была реализована на основе двух скриптов: модифицированной версии уже упоминавшегося hsh-print-req и нового скрипта, получившего название hsh-print-pkgs.
Модификация скрипта hsh-print-req состояла в добавлении двух опций: --pre и --hash. Работа скрипта при указании их обоих состоит в том, что сперва определяется состав предварительного сборочного окружения, а затем вычисляется SHA1-хеш от этих данных. Состав предварительного сборочного окружения определяется точно так же, как и конечный набор сборочных зависимостей пакета — в режиме --print-only, но вместо списка пакетов из тега BuildRequires в этом случае используется список из тега BuildRequires(pre). Важно отметить, что данная операция тоже производится в базовом сборочном окружении, и это окружение не меняется в ходе неё. Это значит, что потенциально идентификатор предварительного сборочного окружения может быть получен для всех пакетов на базе одного и того же экземпляра базового окружения.
Новый скрипт hsh-print-pkgs попросту выводит состав заданного сборочного окружения, а с добавлением опции --hash — вычисляет SHA1-хеш от этих данных.
Для целей тиражирования сборочных окружений была усовершенствована процедура их кеширования, уже реализованная в Hasher. Усовершенствование заключается в более удобном использовании данной функции в том случае, когда архив рабочей директории Hasher сохраняется в произвольном месте, а также при восстановлении рабочей директории из такого архива. Дополнительные скрипты получили названия hsh-create-workdir, hsh-save-workdir и hsh-rm-workdir. Теперь сохранение состояния директории можно выполнить в любой момент, а не только совместно с подготовкой базового сборочного окружения.
Ещё одно нововведение заключается в возможности использовать один и тот же экземпляр рабочего окружения apt с несколькими сборочными окружениями, то есть с несколькими экземплярами рабочих директорий Hasher. Что это даёт? Всё это время мы исходили из того, что состояние репозитория не изменяется, пока идут вычисления. Иными словами, если мы поставили задачу определить состав сборочных окружений всех интересующих нас исходных пакетов, то это имеет смысл делать на одном и том же фиксированном состоянии репозитория. Это так. Но как можно упростить данную процедуру на следующей её итерации? Можно ли как-то выгодно повторить её на новом состоянии репозитория?
Здесь возможны два крайних случая: в первом случае изменения состава репозитория таковы, что они не затрагивают сборочную среду ни одного пакета. В такой ситуации нет необходимости отбрасывать заранее подготовленные сборочные окружения — их можно продолжать использовать повторно. В противоположном случае, наоборот, изменения затрагивают базовую сборочную среду и, следовательно, все заранее подготовленные и сохранённые сборочные окружения необходимо считать негодными и отбросить.
Может возникнуть вопрос: нужно ли вообще проводить новую итерацию по определению состава сборочных окружений пакетов в том случае, если известно, что изменения в пакетной базе не затрагивают ни базовое сборочное окружение, ни одно из заранее подготовленных предварительных окружений? Ответ: да, это имеет смысл. Существует вероятность, что несмотря на неизменный состав базовых окружений, вычисление окончательного состава сборочных зависимостей, которую выполняет apt, даст в новых условиях другой результат.
Как проверить, что изменения в пакетной базе не затрагивают того или иного сборочного окружения? Можно поступить так же, как и в обычной системе: сперва обновить информацию о доступных пакетах командой apt-get update, а затем определить состав очередного обновления командой apt-get dist-upgrade. Отсутствие обновлений покажет, что изменения в пакетной базе не затрагивают данное сборочное окружение. На чём здесь можно сэкономить? Очевидно, на процедуре обновления информации о доступных пакетах. Именно для этой цели служит модификация Hasher, получившая название shared aptbox. Благодаря ей apt в каждой из нескольких рабочих директорий Hasher будет работать со свежими сведениями о пакетах после однократного выполнения команды apt-get update. Это касается и проверки dist-upgrade. Технически это реализовано так: файлы с информацией о доступных пакетах хранятся в общей директории, а в конфигурационных файлах apt.conf в каждой из рабочих директорий Hasher даются ссылки на эту директорию.
На базе всех вышеописанных модификаций Hasher была написана утилита под названием depcalc, комплексно реализующая процедуру определения состава сборочного окружения пакета. При этом утилита автоматически создаёт и использует кеш сборочных окружений и имеет отдельные опции для обслуживания этого кеша. Другой набор опций служит для обновления информации о доступных пакетах с использованием возможностей shared aptbox.
Количественные характеристики работы этой утилиты я надеюсь представить в своём докладе на конференции.