Attention! I'm apologize, but it is automatic machine translation of the text. You can improve it if will send to me more correct version of the text or fix html pages via GITHUB repository.
Сборка своих репозиториев ПО для FreeBSD через cbsd cpr
Общая информация
//Draft, unformatted
Одна из бесконечных задач системных инженеров заключается в работе по установке и поддержанию в актуальном состоянии программного обеспечения серверов и рабочих станций.
Также, в целях безопасности и надежности, желательно иметь сборки на своих ресурсах, а не публичных серверах. Даже официальные репозитории подвержены различным инцидентам и взломам, в результате которых вы можете оказаться в ситуации, когда ПО срочно нужно, а репозиторий который вы не контроллируете — недоступен.
Помимо этого, сам процесс обновления должен подразумевать возможность отката на предыдущее состояние, если по каким-то причинам новые версии работают некорректно.
Хорошо, когда для решения этой задачи используется резервное копирование или, что гораздо практичнее, использовать снэпшот файловой системы: например, перед работами сделать ZFS snapshot для клетки, и при любых проблемах после обновления в течении секунды вернуть состояние обратно.
Однако, занимаясь работой по построению инфраструктуры для собственного репозитория, вы можете заложить возможность версионности на уровне репозитория, что при комбинировании с подстраховкой на клиенте сводит риск попасть в неблагоприятное положение на нет.
Когда я начинал писать cbsd cpr, уже давно были известны аналогичные функционалы в виде TinderBox и Poudriere, однако в каждом из этих решений мне чего-то не хватало.
Требования, которые предьявляла моя задача:
- Сборка производится по заранее сгенерированныму списку ПО.
- Сборка каждого отдельного репозитория/версии производится в изолированном окружении (включая собственный кеш ccache (опционально) для компиляции, который накапливается при каждых повторных сборках в пределах версии и ветки);
- При сборке окружения, используется база и ядро, используемые cbsd, соответственно, при необходимости иметь более свежую версию базы или ядра, можно воспользоваться cbsd repo для получения объектов из репозитория, или cbsd csup/buildworld/kernel* для сборки с нуля.
- Каждый набор ПО (или один и тот же список) может использовать свою собственную версию опций портов (/var/db/ports) и персональный make.conf файл и иметь именованный репозиторий, например в таком виде:
- nox — WITHOUT_X11, набор ПО для серверного окружения, без поддержки X — этот набор ПО используется на production-серверах.
- devnox — тот же набор ПО и опция WITHOUT_X11, но софт собран с дебагом и поддержкой DTRACE; этот набор ПО используется на dev-клетках, где идет разработка и профилирование проектов.
- xorg — X11-based GUI приложения. Данный набор содержит xorg, kde, gnome, мультимедия, офисные приложения, IDE для разработки и прочий инструментарий, характерный для рабочих станций.
- Иметь возможность использовать больше одного билдера ПО, в этом случае сценарии репозитория и опции портов реплицировалась (например через csync2) на соответствующие билдеры: если в процессе сборки репозитория nox вы выбрали некоторые опции для одного порта, то собирая тот же репозиторий nox на другом сервере или другой версии, вывода окна со списом опций уже не будет — он синхронизируется с сервера, которая прошла этот путь первой.
- Иметь возможность задействовать для компиляции свободные ресурсы серверов через distcc. В этом случае, на нужных серверах скачивалась и запускалась клетка distcc с минимальным nice, что при сборке не мешало работать остальным сервисам.
- Иметь версионность каталогов, куда сохраняются новые сборки. В один момент времени только один сет является активным, переключение происходит сменой симлинка. Кроме этого, при наличии нескольких download серверов, требуется время для полной синхронизации, поэтому проливка осуществляется в неактивную (slave) зону и по факту полной сихронизации, одномоментно переключается.
- Конечные хосты забирают готовые пекеджи не с билдеров, а с соответствующих downloads-серверов, которых может быть несколько. Таким образом, сервера отдающие пекеджи, не зависят от здоровья самих билдеров.
Протоколирование работы скрипта ведется в /tmp каталоге chroot окружения: — build.log — общий лог процесса сборки — packages.log — лог на этапе pkg create — port_log* — временные файлы лога компиляции индивидуального порта, удаляющиеся автоматически при успешной сборке порта
- Каждая версия репозитория может иметь свой собственный E-mail для уведомлений о начале-конце сборки.
- Каждую сборку можно запустить с параметром pause=1, что по окончанию сборки система будет ожидать нажатия клавиши. А пока это не сделано — можно зайти в chroot окружение, просмотреть проблемы и исправить их вручную.
- Каждый билдер может быть на своей Major веткии и архитектуры и специализироваться на сборках только для текущей Major ветки и архитектуры для минимизации cross-{build,version} проблем (cbsd cpr скрипт позволяет через ver=X.Y тем не менее собирать фиксированную версию)
- порты монтируются в chroot окружение из мастер системы, либо каждое окружение имеет свою копию портов.
Схематичный рисунок сборочной фермы:
Для комфортной работы напишем переключатель версионности каталогов.
Принцип работы данного скрипта простая. Допустим, у нас есть локация /home/web/pkg.bsdstore.ru/ для хранения пекеджей, где:
/home/web/pkg.bsdstore.ru/master — это SymbolicLink на активный set с билдами. Этот же путь является Docroot для WEB сервера и его имя никогда не меняется.
/home/web/pkg.bsdstore.ru/1 — первый физический каталог, содержащий один набор данных
/home/web/pkg.bsdstore.ru/2 — второй физический каталог, содержащий предыдущую (или следующую) версию набора данных
симлинк master будет указывать лишь на какой-то конкретный каталог, при каждом запуске скрипта указывая по кругу на следующий.
#!/bin/sh # When use with config file, follow variable must be set, sample: # _PATH="/usr/symlinks" # _RANGE="1 2 3" # _MASTER_LINK="master" # _SLAVE_LINK="slave" # _ACTION="next" # _X=1 #if action=set # _Y=2 #if action=set # # or by command line: # ./symlink_changer -p /usr/symlinks -r "1 2 3" -m master -s slave -a next # err() { exitval=$1 shift echo "$*" 1>&2 exit $exitval } get_symlink() { local _res [ -z $1 ] && return 1 _res=`readlink ${_PATH}/${1}` [ $? -ne 0 ] && return 1 printf `basename ${_res}` } get_next() { local _cur _test _first _count _test="${_PATH}/${1}" _cur=0 _count=0 for i in ${_RANGE}; do [ ${_count} -eq 0 ] && _first=${i} ## store first element [ ${_cur} -eq 1 ] && printf ${i} && return 0 [ "${_PATH}/$i" = "${_test}" ] && _cur=1 _count=$(( _count + 1 )) done printf ${_first} } get_prev() { local _cur _test _first _count _REVSLOTS _test="${_PATH}/${1}" _cur=0 _count=0 _REVSLOTS=`echo ${_RANGE} | tr " " "\n" |sort -r` for i in ${_REVSLOTS}; do [ ${_count} -eq 0 ] && _first=${i} ## store first element [ ${_cur} -eq 1 ] && printf ${i} && return 0 [ "${_PATH}/$i" = "${_test}" ] && _cur=1 _count=$(( _count + 1 )) done printf ${_first} } # create or change new layout by # $1 - new dir for master link # $2 - new dir for slave sym_action() { local _masterdir _slavedir [ -z "${1}" -o -z "${2}" ] && return 1 _masterdir="${_PATH}/${1}" _slavedir="${_PATH}/${2}" [ ! -d "${_masterdir}" ] && mkdir "${_masterdir}" [ ! -d "${_slavedir}" ] && mkdir "${_slavedir}" # ln -sf not work correctly here - create symlink in old master folder cd ${_PATH} rm -f "${_PATH}/${_MASTER_LINK}" && /bin/ln -s "${1}" "${_MASTER_LINK}" rm -f "${_PATH}/${_SLAVE_LINK}" && /bin/ln -s "${2}" "${_SLAVE_LINK}" } usage() { echo "$0 -c confpath -p path -r \"range\" -m masterlink_name -s slavelink_name -a action [-x dir1] [-y dir2]" echo "action must be: next, prev, set" echo "when action = set, x/y = is new masterdir/slavedir" exit } # MAIN() while getopts "c:p:r:m:s:a:x:y:" opt; do case "$opt" in c) _conf="$OPTARG" ;; p) _path="$OPTARG" ;; r) _range="$OPTARG" ;; m) _master_link="$OPTARG" ;; s) _slave_link="$OPTARG" ;; a) _action="$OPTARG" ;; x) _x="$OPTARG" ;; y) _y="$OPTARG" ;; *) usage ;; esac shift $(($OPTIND - 1)) done [ -n "${_conf}" -a -f "${_conf}" ] && . ${_conf} [ -n "${_path}" ] && _PATH=${_path} [ -n "${_range}" ] && _RANGE=${_range} [ -n "${_master_link}" ] && _MASTER_LINK=${_master_link} [ -n "${_slave_link}" ] && _SLAVE_LINK=${_slave_link} [ -n "${_action}" ] && _ACTION=${_action} [ -n "${_x}" ] && _X=${_x} [ -n "${_y}" ] && _Y=${_y} [ -z "${_ACTION}" ] && err 1 "Give me action" [ -z "${_PATH}" -o -z "${_MASTER_LINK}" -o -z "${_SLAVE_LINK}" -o -z "${_RANGE}" ] && err 1 "not all neccesary variable has been set" cd ${_PATH} || err 1 "Cant cwd to ${_PATH}" # init area _curmaster=`eval get_symlink ${_MASTER_LINK}` _curslave=`eval get_symlink ${_SLAVE_LINK}` case "${_ACTION}" in "next") _master=`eval get_next ${_curmaster}` _slave=`eval get_next ${_master}` ;; "prev") _master=`eval get_prev ${_curmaster}` _slave=${_curmaster} ;; "set") _master="${_X}" _slave="${_Y}" ;; *) err 1 "No action set" ;; esac sym_action ${_master} ${_slave}
…
WIP(Work in progress)