Что: 7e1dbd0539c7ea5c6bd5e8831abeea4796da693e Когда: 2024-02-18 10:41:48+03:00 ------------------------------------------------------------------------ Темы: bsd djb redo zsh ------------------------------------------------------------------------ Новый проект: zwoki Целую неделю ни одной записи в блоге. Точнее была одна несколько дней назад об исправлении одной баги, но удалил, так как ничего не исправилось. А всё потому что полностью погружён в написание новой программы на работе, больше вообще ни на что не отвлекаясь. У нас работе нет devops-ов, нет достаточного кол-ва админов, вообще народу не шибко много. Поэтому если хочешь какую-то систему CI для сборок -- ну бери и делай. Виртуальные машины или железо тебе выделят, а вот дальше возись сам. И у нас было n-ое кол-во BuildBot установок. Часть из них уже никем не поддерживается и из-за изменений инфраструктуры и не в рабочем состоянии. Что-то на них подправить никто не знает как. На некоторых, к тому же, всё установлено через Nix, к которому мало у кого есть охота изучения. Даже те, кто прежде его использовал активно -- плюнули, из-за постоянных изменений в upstream-е, требующим постоянное обновление правил сборки (либо сидеть на версиях годовалых давностей и обновлять только собственные пакеты). Какие CI системы я знаю? В живую имел дело с Jenkins и BuildBot. Первый -- на Java и монструозен. Просто воспоминания о том как возиться с его правилами сборки -- отталкивают от одной мысли его использовать. Второй написан на Python. А это означает: ee3156341baf276877e601325bb9555ce5743fb1 что хрен его поставишь, не скачивая руками все его зависимости и вручную подсовывая в virtualenv. Если авторы не предоставили vendored зависимости (чего конечно же из Python разработчиков штатно никогда не делает (а вот в моём PyDERASN, кстати, все зависимости приложены в tarball)), то это просто неуважение к своему времени и силам в попытках это всё развернуть. Кроме того, современные версии BuildBot имеют WebUI требующий JavaScript -- поэтому если и ставить, то старьё. И задался вопросом: насколько это сложная задача (CI) и нельзя ли написать своё, раз нету готового, достаточно простого и удобного? Честно, то, что я постоянно за последние годы пишу какой-то софт как (sane) альтернативу имеющемуся -- самого бесит и раздражает. Но что поделать, если много софта не удовлетворяет, а переписывать выходит? По большей части новый CI framework уже написан, как и правила сборки нескольких проектов. К сожалению, делаю это полностью в рабочее время (благо, срочных задач пока нет, поэтому могу позволить), не обсуждал можно ли это выложить в виде свободного ПО. Поэтому пока это закрытая разработка внутри компании. На следующей неделе уже буду разворачивать на настоящем железе. Я хотел было сделать что-то похожее на BuildBot. Раздаются задачи, выполняются, отчитываются о каждом шаге, логируют, показывают кто там упал. И всё это проецируется в некий HTML dashboard со сводкой. Но сразу и требования родились: * оно должно работать на разных платформах: как минимум GNU/Linux (Astra, Debian) и FreeBSD * в идеале, slave выполняющий задачи, должен быть как можно более минималистичной ОС из коробки. Вот поставили голую FreeBSD или Astra -- и вот она уже должна мочь выполнять задачи раздаваемые. Чтобы можно было быстро вводить в строй новые машины для сборок BuildBot никоим образом не поможет с последним требованием. Если для сборки нужен PostgreSQL например -- ну или добавляй его сборку/установку в шаги сборки проекта, или ставь прямо в саму систему slave-а. Последним админы BuildBot-ов и занимались. Если появляется новый проект с совсем иными требованиями (например для сборки многого моего софта нужен redo), то это ставится в slave-ы вручную. Ну или через Nix, Ansible, Puppet, whatever. Но не средствами BuildBot. Нужно что-то, что позволяет собирать и устанавливать "пакеты". Хотелось бы, чтобы сборка проекта на первом шаге просто сказала что мне нужен "python", "postgres" и это как-то в эту сборку подсунулось. Да, примерно таким и Docker может заниматься. Nix тот же. Но Nix поддерживает только GNU/Linux (хотя когда-то давно была поддержка и BSD систем). Docker... спасибо, но нет, плюс его вроде бы и нет на FreeBSD (лень проверять, как и возиться с ним). Поэтому первым шагом я начал писать свой пакетный менеджер по сути. Правила сборки описываются в shell скрипте, который устанавливает программу в /well/known/permanent/path/hash-progname. А установка пакета, которая на практике будет происходить в некой $tmp директории сборки -- это просто вызов stow для создания symlink-ов из /well/known пути до программы в $tmp/local поддиректорию. Добавив $tmp/local/bin в $PATH, директории до библиотек и прочего -- можно удобно заиметь в своём окружении нужный софт. Всё это очень сильно напоминает то, что делает Nix. Так и есть. Так вышло, что я самостоятельно к этому пришёл. Но напомню, что Nix не кроссплатформенный и в нём уродский собственный функциональный язык для описания сборок. У меня же буквально shell скрипты выполняющие всё нужное по шагам. Можно ли сторонней программе легко и точно сказать от кого зависит тот, или иной пакет при сборке? В Nix можно, а у меня нельзя. Ну и где это создаст проблем? В том то и дело, что нигде. Как в redo: не запустив .do файлов -- никто не знает от чего они зависят. Здесь аналогично. Зато никакого нового языка, нового формата -- пиши как и на чём хочешь. Установка в /well/known/... путь на любой ОС (GNU, BSD) и создание $tmp/local stowed директории -- работает одинаково. Если есть особенности сборки для заданной архитектуры, то внутри правил сборки всегда есть $ARCH переменная и поэтому можно писать if [ $ARCH = ... ]. Пакет устанавливаться должен из единичного файла. Так я захотел, для удобства. Думал что можно обойтись просто созданием .tar-а с hash-progname-version директорией, которую распаковывать в /well/known/..., но понадобилась возможность хранить run-dependencies (для работы stow нужен perl, для tmux нужен libevent, и т.д.), точное имя пакета (чтобы сам пакет имел имя progname-version.tar), информация о сборке (из какого коммита взяты правила, как минимум). Поэтому начал рождаться и собственный формат пакетов. Я сначала подумал и придумал, а только потом пошёл смотреть что из себя представляют пакеты в Debian, RedHat, Gentoo, и т.д.. Оказалось, что я полностью схожим образом пришёл к точно такому же решению как и в Gentoo: https://www.gentoo.org/glep/glep-0078.html Архив с собранным пакетом находится внутри другого архива, в котором метаинформационные файлы: name, rundeps, buildinfo, и т.д.. К каждому файлу можно приложить .blake3 какой-нибудь, .sig/.asc подпись. Очень просто и удобно. Главное следить за тем, чтобы метаинформационные файлы находились в начале архива, чтобы до них можно бы было быстро достучаться при потоковой работе: curl http://pkg/progname-version | tar xfO - name | read name_with_hash Архив с программой, конечно же, может быть (и должен быть!) пожат. curl http://pkg/progname-version | tar xfO - bin | tar xfC - /well/known/$name_with_hash Ну а через tee и заранее вычитанные .blake3 файлы можно проверить и целостность на лету. Внешний архив не стоит сжимать, чтобы можно было бы быстрее производить поиск по нему. Машина для сборки пакетов из исходников не должна быть подключена к Интернету. Может, но не обязана. Поэтому я давно за практику взял разделение шагов скачивания исходного кода и его сборки. Для скачивания нужны git, wget и всякое такое. Для сборки они, как правило, не нужны. На одной машине можно запустить скрипты для скачивания, а на другой уже использовать результат сформированный в distfiles. Я не раз писал скрипты скачивания, в которых указывается URL до tarball-а и криптографический хэш для его "аутентификации". Плюс скачивал и добавлял в репозиторий и файлы с PGP подписями. Но я использовал единственный хэш -- SHA512. Было бы здорово указывать хэши, которые перечислены зачастую на страницах скачивания у проектов. Чаще всего это SHA256. А вот например для Go 1.4, который нужен для сборок более новых версий -- вообще только SHA1. Начал добавлять возможность указывать несколько хэшей... и меня осенило: ведь именно всем этим и занимается Metalink формат! c3ba3d2f29655d06dffe1ec836c9f0b98daec0c9, 2374b93f88e7a3222c0e91999306b259bd9e276c. Я же сам его уже давно создаю и выкладываю для всех tarball-ов своего софта. В нём можно указывать URL-ы для скачивания, разные хэши, встраивать подписи. Плюс он поддерживается и GNU Wget-ом и Aria2. В итоге для скачивания софта я просто добавляю в репозиторий .meta4 файлы и натравливаю на них wget/aria2c. Часть софта есть только в виде VCS репозиториев -- ну тут просто руками выполняются git fetch/clone/whatever и git-archive для создания tarball. Для формирования .meta4 я использовать свою meta4ra/meta4-create утилиту, но всё это можно без проблем проделать и вручную, ведь это же текстовый XML. Начал писать это всё на POSIX shell, чтобы на любой ОС можно было запустить. Я сделал всё что мог, но всё же дошёл до того, что пришлось для существенного облегчения жизни перейти на Z Shell. У меня много pipe-ов используется -- поэтому нужен pipefail включённый. Я думал что он де-факто есть в любом shell (e3a3ccff5507dd83913a0809b9525e3adabd64d2). Но как оказалось, POSIX ещё не вышел с ним, поэтому и dash (Debian shell по умолчанию) pipefail не добавляет! Да и если и добавит, на руках же куча старых версий дистрибутивов. Переписывать pipe-ы на всякие вызовы с FIFO файлами и прочими неудобствами я не собираюсь. Только из-за этого dash я вынужден был плюнуть на POSIX shell. А какая альтернатива? GNU Bash не имеет права на существование. *ksh? rc? fish? Наиболее разумным и требующим меньшего порога вхождения я считаю только zsh. Либо писать вообще не на shell, а на Perl, Tcl, whatever. С zsh хотя бы ещё существенно всё упрощается в плане экранирования аргументов (чего нет в bash: 30670475d5bc7b8601a555d33fad188602f96712). А вот скрипты для создания, раздачи, взятия, запуска задач -- всё это пока вышло написать на POSIX shell. Сами шаги сборки/проверки конкретного проекта -- можно писать на чём хочешь, как например и .do redo файлы. Шаги сборки -- просто исполняемые файлы, а значит и shebang будет прочтён. Если кто-то захочет написать на zsh их, то заранее в первых шагах достаточно выполнить pkg-install zsh и он появился в $tmp/local окружении, став доступным для следующего шага сборки написанного на zsh. Скрипт запуска задачи занимает примерно экран POSIX shell. А в нём и создание окружения, и установка stow+perl+zstd через tar xfO вызовы, чтобы дальнейшие пакеты уже можно устанавливать через pkg-install скрипт, учитывающий rundeps. Всё это запускается внутри tmux-а, дабы к упавшей сборке можно было бы подключиться и попасть буквально в её окружение где все падения и происходили. На самой системе slave-а иметь tmux не нужно -- он ставится тут же сразу же из самосборных пакетов. Скрипт запуска шагов сборки тоже занимает один экран. А это и перенаправления выводов, демон touch alive файла, проверка не слишком ли долго выполняется шаг (как в BuildBot -- если нет вывода в течении часа), убийство зависшей задачи, подчистка всего мусора, и т.д.. Скрипт создания задачи -- полэкрана. Задача это директория с $TASK_NUM:$PROJNAME:$REVISION:$ARCH[:$HOST] именем. Внутри есть code.tar и steps.tar. slave-ы не лазят самостоятельно в git-ы для получения кода который надо проверять. Всё это, ради разграничения полномочий, делается на абстрактном master. Результаты выполняющейся задачи это директория с таким же именем, с: alive 01step/ stderr.txt stdout.txt exitcode.txt ... и всё в таком духе. Вывод std* конечно же пропущен через tai64n. Если нужно сохранить какие-то артефакты сборки, то просто кладём файлы в эту директорию. Jenkins умел понимать особый XML формат с результатами прогона unittest-ов и умел анализировать эти XML между соседствующими сборками и показывать diff (стало падать на пять тестов меньше, и т.д.). Тривиально положить шагам XML-ку, а сторонними утилитами, пробегаясь по директориям с результатами, выполнять нужные вычисления. У меня нет ни одного демона (ну кроме shell скриптов крутящихся в while true аналоге). Абсолютно всё взаимодействие между master и slave происходит через операции на файловой системе. Соответственно, между ними поднимается NFS. Создание задачи: наполнение директории $TASKS/tmp/$task:, а дальше создание $TASKS/tmp/$task:$arch и жёсткие ссылки в каждую из этих директорий. Атомарное появление задач для slave-ов: mv $TASKS/tmp/$task:* $TASKS/cur/. Только один slave должен мочь взять задачу? mkdir $JOBS/$task:$arch -- только одной из машин это удастся сделать. Никаких RPC/API демонов, никаких curl вызовов -- только NFS, ФС и mkdir для атомарных операций. task-maker-ы -- программы срабатывающие на появление событий от git hook-ов, не обязаны запускаться на одной и той же машине (на master). По сути, нет такой роли как центральный master сервер. Должен быть общий NFS куда разные машины могут выкладывать свои разные $task-и. NFS с $JOBS-ами или с $PKGS -- могут быть и другими машинами, другими mountpoint-ами. Для удобства я для всех новых задач постоянно инкрементирую $TASK_NUM счётчик -- что-то типа уникального идентификатора задачи. Как это сделать атомарно, если всё что у нас есть это NFS? Проблема в том, что на нём из коробки не работают lock-и: https://serverfault.com/questions/66919/file-locks-on-an-nfs Ну точнее из коробки в FreeBSD на NFSv4 у меня они не сработали. Дальше не стал разбираться. Поэтому задачу с взятием значения счётчика я решаю через бесконечный цикл с mkdir-ом инкрементированного значения счётчика -- если это удалось сделать, то значит значение "наше", а другие пускай снова пробуют mkdir-ить его инкрементирующееся значение. Я сразу же условился не бояться copy-paste. Если что-то делается shell скриптами, то очень многое можно выносить, DRYить в разные маленькие скрипты. Помню, что берёшь какой-нибудь suckless проект, видишь пару дюжин скриптов -- и как то вот сразу отпадает желание разбираться в нём, даже просто прочитать названия этой кучи скриптов. Если во всех скриптах сборки проекта есть общая часть с созданием временной директории, trap-ом для её очистки, и всяким таким подобным -- я считаю ничего страшного чтобы это копировать между всеми скриптами. А то откроешь такой DRY файл: и видишь с десяток вызовов неизвестных тебе скриптов и функций и начинаешь прыгать по файлам чтобы понять что они делают. А без DRY ты просто видишь все шаги as-is. Надо стараться соблюдать некий баланс между удобочитаемостью и минимальным порогом вхождения и адекватностью объёма copy-paste. Это же касается и большого кол-ва переменных окружения неявно приходящих в скрипты и функции: ничего страшного чтобы постоянно писать $SKELBINS/$ARCH/$hsh-$name/bin -- всё равно за человека это делает текстовый редактор. Всё готово на 80-90%. Вышло значительнее меньше по коду чем я предполагал. Нужно конечно ещё делать и делать описания всяких пакетов и правил сборки того или иного проекта, но это уже рутина не влияющая на код самого framework-а. Проект, кстати, называется zwoki. Я начал отталкиваться от "2nd continuous integration" фразы, в итоге вышла "zwo" (zwei, два), "ki" (Kontinuierliche Integration, чтобы вышел не "zwoci", который фиг знает как правильно прочитать). Должно бы быть конечно "zweiteki" (zweite -- второй, 2nd), но уже длинновато. А так получился "цвоки". К сожалению, только почти через неделю, вчера перед сном, меня осенило другое: я даже не задумывался о применении redo. А ведь она обеспечивает и слежение за зависимостями между собираемыми целями и атомарно сохраняет результат работы. Надо посмотреть насколько это всё упрощает и возможно переделать сборку пакетов на использование redo. Не знаю почему мне сразу это в голову не пришло, ведь у меня уже нет проектов которые бы не использовали redo (или же в них просто shell скрипты и нет никаких зависимостей между целями сборки). ------------------------------------------------------------------------ оставить комментарий: mailto:comment@blog.stargrave.org?subject=Re:%20%D0%9D%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82:%20zwoki%20%287e1dbd0539c7ea5c6bd5e8831abeea4796da693e%29 ------------------------------------------------------------------------ Сгенерирован: SGBlog 0.34.0