Операционная система FreeBSD в своем составе имеет обширную коллекцию утилит и инструментов, а для расширения имеющегося функционала сообщество разработчиков поддерживает коллекцию портов. Длительное время установка и обновление стороннего программного обеспечения осуществлялась посредством выполнения ряда команд в дереве портов и использования portupgrade, portmaster и т.п., что предполагало регулярную компиляцию софта из исходного кода с последующей установкой бинарных файлов в систему. При этом в системе отсутствовали удобные и гибкие средства формирования централизованного хранилища (репозитория) пакетов, что осложняло создание на FreeBSD типовых конфигураций. В 9 версии фряхи появился менеджер пакетов pkg, облегчивший установку стороннего софта из пакетов и управление ими (по аналогии как это сделано в популярных дистрибутивах Linux). Вместе с ним появилась утилита poudriere, предназначенная для автоматизированного создания пакетов из находящегося в портах программного обеспечения, а также его тестирования и т.п. Следует отметить, что poudriere осуществляет компиляцию портов в изолированной среде (клетках, jails), при этом целевой архитектурой может выступать любая поддерживаемая ОС FreeBSD. В тандеме утилиты poudriere и pkg предоставляют возможность создать свое хранилище пакетов, из которого можно распространять программное обеспечение со своими зависимостями, параметрами сборки, флагами компиляции и т.п.
Следует сделать несколько заметок о конфигурации моего сервера для сборки пакетов. Так, собственное хранилище организовано на FreeBSD версии 14.3. При этом poudriere выполняет все действия в клетке, а в качестве файловой системы для хранения портов, клеток и собранных пакетов используется ZFS. Для тех, кто не желает использовать ZFS статья также может оказаться полезной, поскольку приведенные здесь конфиги не потребуют существенных изменений.
На первом этапе необходимо создать и настроить клетку. В качестве инструкции по созданию клетки можно использовать эту статью, далее я приведу лишь ее конфиг.
# В общем блоке задаем глобальные параметры
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.consolelog = "/var/log/jail/console_${name}.log";
exec.clean;
mount.devfs;
# Базовый путь для клеток
path = "/home/jails/$name";
pkg {
# Исходный интерфейс в системе и параметры нового в клетке.
# В моей системе bridge0 включает интерфейс, смотрящий в локальную сеть.
$bridge_if = "bridge0";
$jail_ip = "192.168.1.10/24";
$gateway = "192.168.1.250";
$dataset = "vmstor/poudriere";
jid = 3; # явное указание номера клетки необходимо, чтобы его можно было использовать через переменную $jid
host.hostname = "srv-pkg.local";
children.max = 50; # разрешаем создавать в клетке не более 50 вложенных
# Нижеприведенные разрешения необходимы для нормальной работы poudriere
enforce_statfs = "1";
allow.mlock; # требуется для poudriere с версии 3.4.1
allow.chflags;
allow.raw_sockets;
allow.socket_af;
allow.mount;
allow.mount.devfs;
allow.mount.fdescfs;
allow.mount.linprocfs;
allow.mount.nullfs;
allow.mount.procfs;
allow.mount.tmpfs;
allow.mount.zfs;
sysvmsg = new;
sysvsem = new;
sysvshm = new;
# Настройки VNET. Первой директивой включаем виртуализацию,
# второй - указываем виртуальный либо физический интерфейс,
# который будет доступен в клетке для работы
vnet;
vnet.interface = "epair${jid}b";
# Для придания уникальности именам интерфейсов будем использовать идентификатор клетки
exec.prepare += "ifconfig epair${jid} create";
exec.prestart += "ifconfig epair${jid}a up descr 'jail ${name} side of the epair'";
exec.prestart += "ifconfig {$bridge_if} addm epair${jid}a";
# Так как указанный в vnet.interface интерфейс после старта клетки будет передан ей в управление,
# можно задать IP адрес и другие необходимые параметры
exec.start += "ifconfig epair${jid}b ${jail_ip} up descr 'jail side of the epair only visible in the jail'";
# Устанавливаем шлюз по умолчанию
exec.start += "route add default ${gateway}";
# На этапе остановки клетки необходимо почистить за собой и удалить дочерние интерфейсы,
# чтобы в дальнейшем при повторном запуске клетки не возникло ошибок
exec.poststop += "ifconfig ${bridge_if} deletem epair${jid}a";
# В случае удаления любого конца epair, второй удалится автоматически
exec.poststop += "ifconfig epair${jid}a destroy";
# После создания клетки подключаем файловую систему ZFS
exec.created += "zfs jail ${name} ${dataset}";
# Перед уничтожением клетки отключаем файловую систему ZFS
exec.release += "zfs unjail ${name} ${dataset}";
}
Файловая система ZFS создается заранее, до запуска клетки, и в ее свойствах параметр jailed должен быть включен. Например так:
zfs create -o mountpoint=/poudriere -o jailed=on -o atime=no -u vmstor/poudriere
После подготовки клетки вносим изменения в /etc/rc.conf и запускаем ее командой service jail start pkg, после чего устанавливаем в ней необходимый софт командой pkg install poudriere git. Основной конфигурационный файл лежит по пути /usr/local/etc/poudriere.conf, кроме этого в директории /usr/local/etc/poudriere.d можно создавать уникальные конфигурации под конкретную клетку, коллекцию портов и т.п. Например, для файла make.conf предусмотрены следующие форматы:
- /usr/local/etc/poudriere.d/make.conf
- /usr/local/etc/poudriere.d/<setname>-make.conf
- /usr/local/etc/poudriere.d/<tree>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-make.conf
- /usr/local/etc/poudriere.d/<tree>-<setname>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-<tree>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-<setname>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-<tree>-<setname>-make.conf
- /usr/local/etc/poudriere.d/hooks/plugins/<plugin>/make.conf
Остальные примеры уникальных конфигураций смотрите в мане к poudriere (ссылка на веб-версию приведена в конце статьи). Также отмечу, что утилита поддерживает события на разных стадиях создания пакетов, которые можно использовать для вызова скриптов. В данной статье указанные возможности poudriere не будут затронуты.
После установки необходимо изменить конфигурационный файл /usr/local/etc/poudriere.conf. В моей конфигурации он выглядит следующим образом (чтобы не нагружать статью буквами приведены только измененные параметры):
# Poudriere может использовать файловую систему ZFS для хранения клеток и коллекций портов.
# Для этого необходимо определить параметр ZPOOL. Если ZFS не используется, тогда
# раскомментируйте параметр NO_ZFS=yes.
#
#### ZFS
# Пул, на котором poudriere будет создавать все необходимые для работы файловые системы.
# Формат файловых систем следующий - ${ZPOOL}/${ZROOTFS}
#
# На пуле требуется не менее 7GB свободного пространства для работы poudriere.
#
ZPOOL=vmstor
### NO ZFS
# Для того, чтобы запретить ZFS раскомментируйте следующую строку
# NO_ZFS=yes
# Корневая файловая система в пуле (по умолчанию /poudriere)
ZROOTFS=/poudriere
# Хост, с которого будет выкачиваться дистрибутив FreeBSD для формирования клетки
# Скачивание дистрибутива осуществляется с помощью утилиты fetch, поэтому можно
# использовать любой поддерживаемый ею URL (даже file:///)
# Замените _PROTO_ на http, ftp и т.п.
# Замените _CHANGE_THIS_ на адрес имя хоста или IP адрес
FREEBSD_HOST=http://ftp.ru.freebsd.org
# По умолчанию в клетке отсутствует /etc/resolv.conf, поэтому вам необходимо
# указать файл с днас серверами, который будет скопирован в клетку по пути:
# /etc/resolv.conf.
RESOLV_CONF=/etc/resolv.conf
# Директория, в которой poudriere будет сохранять клетки и коллекции портов
BASEFS=/poudriere
# Ипользовать portlint?
USE_PORTLINT=no
# Параметры использования tmpfs(5)
# Можно указывать следующие параметры, разделенных пробелами:
# wrkdir - юзать tmpfs(5) для компиляции в WRKDIRPREFIX
# data - юзать tmpfs(5) для размещения служебных данных в cache/temp
# localbase - юзать tmpfs(5) для LOCALBASE (installing ports for packaging/testing)
# all - хранить все на tmpfs, в т.ч. оуркжения сборки, т.е. сами клетки.
# yes - объединяет параметры wrkdir и data
# no - отключить использование tmpfs(5)
# EXAMPLE: USE_TMPFS="wrkdir data"
USE_TMPFS="wrkdir data localbase"
# Ограничение размера раздела tmpfs для каждого экземпляра сборщика (в GiB)
# (default: none)
TMPFS_LIMIT=24
# Список пакетов, которые не будут собираться в WRKDIR, работающей на tmpfs
# Note that you *must* set TMPFS_BLACKLIST_TMPDIR
# EXAMPLE: TMPFS_BLACKLIST="rust"
TMPFS_BLACKLIST="rust"
# Ограничение памяти для каждой клетки, в которой будет осуществляться сборка пакетов (в GiB)
# (default: none)
MAX_MEMORY=24
# Директория, в которую будут сохраняться архивы с исходными кодами ПО при скачивании с инета.
# Если выставлено в "no", тогда poudriere необходимо предоставить директорию,
# в которой имеются данные архивы.
DISTFILES_CACHE=/usr/ports/distfiles
# Использовать ccache при компиляции. Для этого укажите путь для хранения кеша.
# Кеш разделяется между всемы клетками.
CCACHE_DIR=/home/poudriere/ccache
# Параллельная сборка.
#
# По умолчанию poudriere использует значение sysctl hw.ncpu для определения количества сборщиков.
# Вы можете переопределить это значение задав переменную PARALLEL_JOBS или флаг -J в командной строке.
PARALLEL_JOBS=2
# Формат архива, в котором будут сохраняться бинарные пакеты (возможные варианты: tar, tgz, tbz, txz, tzst)
# По умолчанию tbz
WRKDIR_ARCHIVE_FORMAT=txz
# По умолчанию сборка в многопоточном режиме отключена (MAKE_JOBS is disabled)
# Используйте данную директиву для разрешения сборки всех портов в мнопотоке.
ALLOW_MAKE_JOBS=yes
# В данной директиве определяется имя хоста в клетке. Некоторые приложения могут
# использовать данное имя в компиляции.
# This is a necessary setup for reproducible builds.
BUILDER_HOSTNAME=pkg.your-server.ru
# URL, по которому доступны POUDRIERE_DATA/logs. Данный адрес будет использован
# в ходе генерации URL в HTML при выполнении сборки.
URL_BASE=http://pkg.your-server.ru/
# Посредством данных директив можно указать порты, которые будут скачиваться с репозитория FreeBSD,
# чтобы не собирать их на своей машине. Скачивание будет происходить только в случае полного
# совпадения версии текущего порта и версии пакета на удаленном сервере.
PACKAGE_FETCH_BRANCH=latest
PACKAGE_FETCH_URL=pkg+http://pkg.FreeBSD.org/\${ABI}
# Список пакетов, которые poudriere будет пытаться вытянуть из репозитория FreeBSD.
PACKAGE_FETCH_WHITELIST="gcc* rust llvm*"
Подготовив конфигурационный файл создаем коллекцию портов, на базе которой будут собираться пакеты, а также клетку с целевой архитектурой, в которой будет производиться сборка пакетов.
poudriere ports -c -p head
poudriere jail -c -j 143amd64srv -a amd64 -v 14.3-RELEASE
После этого можно запускать сборщиков для компиляции программ и создания пакетов:
poudriere bulk -j 143amd64srv -p head editors/vim
Список компилируемых портов можно указать в файле, который имеет простой формат - каждый порт указывается на отдельной строке. Тогда запуск poudriere для компиляции портов по списку из файла будет выглядеть следующим образом:
poudriere bulk -j 143amd64srv -p head -f /path/to/file
Poudriere позволяет рекурсивно задавать опции компиляции портов, т.е. утилита будет автоматически задавать вам вопросы о том с какими опциями компилировать зависимости для требуемого вам ПО. Инициировать процесс задания опций к портам можно следующей командой (не забывайте периодически запускать ее после обновления коллекции портов):
poudriere options -j 143amd64srv -p head -f /path/to/file
Использовать собственное хранилище достаточно просто, для этого в системе необходимо отключить официальный репозиторий пакетов проекта FreeBSD и добавить свой. Указанное можно осуществить следующими командами:
echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
printf 'custom: {\n url: "http://your-server.ru/data/packages/142amd64srv-head",\n enabled: yes\n}' > /usr/local/etc/pkg/repos/Local.conf
В целом о создании собственного хранилища пакетов приведено достаточно информации. Дополню, что работу poudriere можно просматривать в реальном времени через веб-интерфейс. Примеры конфигурационных файлов для веб-серверов Apache и nginx находятся в /usr/local/share/examples/poudriere.
Конечно на данном этапе можно закончить статью, но нет предела совершенству, поэтому продолжаем автоматизировать процессы запуская poudriere по расписанию. Для более гибкого управления запуском сборщиков я написал скрипт на python, который читает информацию из конфигурационного файла о клетках с версиями систем и коллекциях портов, после чего запускает poudriere согласно заданным настройкам. Помимо этого скрипт также умеет создавать клетки и коллекции портов, если они отсутствуют.
#!/usr/bin/env python3.11
import argparse
import io
import os
import sys
import time
import subprocess
import json
# Загружаем конфигурационный файл и проверяем его синтаксис
def LoadConfig(ConfigPath):
CONF_REQ_PARAMS = (
'working_dir',
'poudriere_bin',
'poudriere_confdir',
'autocreate_ports',
'autocreate_jails',
'ports',
'systems'
)
if not os.path.isfile(ConfigPath):
sys.exit("Can't find config file on " + ConfigPath)
with open(ConfigPath, 'r') as jfile:
CFG = json.load(jfile)
for key in CONF_REQ_PARAMS:
if key not in CFG:
sys.exit('Syntax in config file is bad')
return CFG
# Функция возвращает результат вывода утилиты poudriere списком
def PoudriereList(FilePath, CmdName):
STD_FLAGS = ['-l', '-q', '-n']
ret = subprocess.run(
[FilePath, CmdName] + STD_FLAGS,
capture_output=True,
check=True,
text=True
)
return ret.stdout.strip().split('\n')
# Проверяет синтаксис конфигурационного файла
def CheckSysParams(Params):
flg = True
REQ_PARAMS = (
'build',
'version',
'arch',
'jname',
'ports',
'pkg'
)
for Key in REQ_PARAMS:
if Key not in Params:
flg = False
break
return flg
# Main
if __name__ != '__main__':
sys.exit("Can't be used as a module.")
ArgsParser = argparse.ArgumentParser()
ArgsParser.add_argument(
'-c', '--config',
help='path to config file',
type=str,
required=True
)
Args = ArgsParser.parse_args()
# Загружаем данные из конфигурационного файла
CONFIG = LoadConfig(Args.config)
# Определяем директорию куда будут записываться лог файлы
LOGS_DIR = CONFIG['working_dir'] + '/logs'
if not os.path.isdir(LOGS_DIR):
os.makedirs(CONFIG['logs'], exist_ok=True)
if not os.path.isfile(CONFIG['poudriere_bin']):
sys.exit("Can't find poudriere binary")
# Получаем список существующих клеток
JAIL_LIST = PoudriereList(CONFIG['poudriere_bin'], 'jails')
# Получаем список существующих портов
PORTS_LIST = PoudriereList(CONFIG['poudriere_bin'], 'ports')
# Сначала работаем с коллекцией портов (обновляем, создаем)
for Key in CONFIG['ports']:
if not CONFIG['ports'][Key].get('update', False): continue
logfname = 'ports-' + Key + '-' + time.strftime('%d-%m-%Y') + '.log'
pargs = [CONFIG['poudriere_bin'], 'ports']
if Key in PORTS_LIST:
pargs += ['-u']
elif CONFIG['autocreate_ports']:
pargs += ['-c']
ports_branch = CONFIG['ports'][Key].get('branch', 'main')
if ports_branch != 'main' and ports_branch != 'master':
pargs += ['-B', ports_branch]
PORTS_LIST.append(Key)
else:
sys.exit('Ports ' + Key + ' not exists')
pargs += ['-p', Key]
logf = open(LOGS_DIR + '/' + logfname, 'at')
proc = subprocess.Popen(pargs, stdout=logf, stderr=logf)
proc.communicate()
logf.close()
# После работы с коллекцией портов запускаем сборщиков. При этом если клетка
# отсутствует, тогда создаем ее
for Key in CONFIG['systems']:
if not CONFIG['systems'][Key].get('build', False): continue
if not CheckSysParams(CONFIG['systems'][Key]):
sys.exit('In ' + Key + 'config syntax is bad')
if not CONFIG['systems'][Key]['build']: continue
if CONFIG['systems'][Key]['ports'] not in PORTS_LIST:
sys.exit('Unknown name of ports: ' + CONFIG['systems'][Key]['ports'])
logf = None
logfname = Key + '-' + time.strftime('%d-%m-%Y') + '.log'
if not CONFIG['systems'][Key]['jname'] in JAIL_LIST:
if CONFIG['autocreate_jails']:
pargs = [CONFIG['poudriere_bin'], 'jails', '-c']
pargs += ['-a', CONFIG['systems'][Key]['arch']]
pargs += ['-j', CONFIG['systems'][Key]['jname']]
pargs += ['-v', CONFIG['systems'][Key]['version']]
logf = open(LOGS_DIR + '/' + logfname, 'at')
proc = subprocess.Popen(pargs, stdout=logf, stderr=logf)
proc.communicate()
else:
print(Key + ': jail ' + CONFIG['systems'][Key]['jname'] + ' not exists.')
continue
pargs = [CONFIG['poudriere_bin'], 'bulk']
pargs += ['-j', CONFIG['systems'][Key]['jname']]
pargs += ['-p', CONFIG['systems'][Key]['ports']]
if isinstance(CONFIG['systems'][Key]['pkg'], str):
if os.path.isfile(CONFIG['systems'][Key]['pkg']):
pargs += ['-f', CONFIG['systems'][Key]['pkg']]
else:
sys.exit('File ' + CONFIG['systems'][Key]['pkg'] + ' not found')
elif isinstance(CONFIG['systems'][Key]['pkg'], list):
pargs += CONFIG['systems'][Key]['pkg']
else:
sys.exit('pkg syntax in config is bad')
# Проверка необходима, т.к. файл может быть открыт на этапе создания клетки
if not isinstance(logf, io.IOBase):
logf = open(LOGS_DIR + '/' + logfname, 'at')
proc = subprocess.Popen(pargs, stdout=logf, stderr=logf)
proc.communicate()
logf.close()
sys.exit(0)
Конфигурационный файл к скрипту имеет формат JSON и может выглядеть следующим образом:
{
"working_dir": "/root/pkg",
"poudriere_bin": "/usr/local/bin/poudriere",
"poudriere_confdir": "/usr/local/etc/poudriere.d",
"autocreate_ports": true,
"autocreate_jails": true,
"ports": {
"head": {
"update": true,
"branch": "main"
},
"latest": {
"update": true,
"branch": "main"
}
},
"systems": {
"142amd64srv-head": {
"build": true,
"arch": "amd64",
"version": "14.2-RELEASE",
"jname": "142amd64srv",
"ports": "head",
"pkg": "/usr/local/etc/poudriere.d/142amd64srv.pkglist"
},
"143amd64srv-latest": {
"build": true,
"arch": "amd64",
"version": "14.3-RELEASE",
"jname": "143amd64srv",
"ports": "latest",
"pkg": "/usr/local/etc/poudriere.d/143amd64srv.pkglist"
}
}
}
Для автоматического запуска скрипта создаем задание в cron:
#min hour day month day of week command
0 3 * * 0 /root/pkg/build.py -c /root/pkg/config.json
В данном случае запуск скрипта будет осуществляться каждое воскресенье ночью. Настроив систему сборки портов по данной статье при необходимости внесения каких-либо изменений потребуется только исправить список компилируемых портов и конфиг скрипта, все остальное будет сделано автоматически. Помимо написания скриптов возможно использование систем непрерывной интеграции для автоматизации сборок портов, что позволит создать более гибкие и масштабируемые конфигурации.
Добавить комментарий