В предыдущей статье мы кратко рассмотрели средства автоматизации развертывания ПО и управления его жизненным циклом, а сейчас хотим поделиться лайфхаками использования RPM. 

Система репозиториев

Можно свалить все ПО в кучу, но мы предлагаем разделить репозитории на несколько групп:

  • good-repo — репозиторий с открытым безопасным ПО. Здесь должны находиться пакеты свободного софта, которые не конфликтуют с основными репозиториями вашего дистрибутива. good-repo можно подключать на всех машинах инфраструктуры по умолчанию;

  • bad-repo — репозиторий с закрытым или способным вызвать конфликты программным обеспечением. Обычно он подключается для конкретных машин, если для них есть собранный и протестированный пакет (группа пакетов);

  • ugly-repo — репозиторий с самописным ПО, который при желании можно разделить на 2 или 3 репозитория: ugly-repo-prod и ugly-repo-stage для двух сред разработки.

Система репозиториев для нескольких площадок

Вы можете автоматизировать сборку как угодно, например, доставлять пакет сразу на несколько зеркал, но зачем, если есть такой замечательный инструмент как nginx?

   location / {
        root /path/to/repo;
        try_files $uri @central-repo;
        autoindex on;
    }
    location @central-repo {
        proxy_pass http://central-repo.example.com;
        proxy_store /path/to/repo/$uri;
    }

Теперь вы можете отправить файлы при сборке в центральный репозиторий и мгновенно получить обновленную версию на зеркалах.

Организация репозиториев сборки (RPM)

Мы прекрасно понимаем, что все нужно держать в CVS, в том числе разумно отправить туда и “кухню” сборки. Возможны разные подходы, но чаще всего spec хранят вместе с основным репозиторием. Это хороший путь, если вы поддерживаете changelog прямо в spec-файле и собираете только собственное ПО. В ином случае предлагаем вам удобную, на наш взгляд, систему организации репозиториев. 

Создайте отдельную группу в gitlab для пакетов и отдельный репозиторий на каждый spec. Репозитории стоит делать по готовым шаблонам со всем необходимым для сборки, чтобы минимизировать дальнейшие телодвижения в системе CI\CD:

├── BUILD
│   └── .gitignore
├── BUILDROOT
│   └── .gitignore
├── LICENSE
├── README.md
├── RPMS
│   └── .gitignore
├── SOURCES
│   └── .gitignore
├── SPECS
│   └── template.spec
└── SRPMS
    └── .gitignore

Стоит придерживаться следующих правил:

  1. В sources должны сохраняться все патчи, дополнительные файлы (systemd.service и т.д.);

  2. Основной архив с исходником должен загружаться через spec и не должен содержаться в репозитории;

  3. Makefile и прочая приближенная к коду кухня сборки должна оставаться рядом с кодом.

Организация SPEC (RPM)

  1. Spec должен быть максимально простым — это документация к ПО;

  2. Тестирование и подобные механизмы должны выноситься в отдельные инструменты;

  3. Если вы еще не перешли на el8, вам придется страдать и многое писать руками. Rpmbuild развивается и в современных системах spec поддерживает множество новых удобных макросов, например, по работе с systemd;

  4. Пишите spec так же, как пишите скрипты: выносите описание констант в заголовочную часть, по-максимуму описывайте пути переменными и константами, используйте RPM-макросы.

Как откатываться?

Чтобы установить более раннюю версию ПО через процедуру обновления, вы можете использовать в spec параметр Epoch. Это монотонно нарастающее число: пакетная система дистрибутива будет считать более новым пакет с младшей версией, но с большей эпохой.

Совет по организации сборки на Jenkins/Ansible

Сделайте более или менее универсальную управляемую задачу для сборки через Jenkins (мы используем отдельные dsl-задачи под каждый пакет). Приведем пример задачи для клиента librespeed, который мы используем на сервере для измерения пропускной способности канала:

freeStyleJob('build_rpm_librespeedcli.dsl') {


	description '''
		Сборка rpm librespeed Command line
		Зависимости Jenkins: envInjection
		Зависимости Shell: "Development Tools",rpm-build,git,ncftp,ansible
		Зависимости Infrastructure: ftp-server (internal repo)'''.stripIndent().trim()

	parameters {
		listGitBranches {
			name('version')
			type('PT_TAG')
			remoteURL('https://github.com/librespeed/speedtest-cli.git')
			credentialsId('299588f1-9fcb-461d-bdc1-24cccbd62ccb')
			sortMode('DESCENDING_SMART')
			quickFilterEnabled(true)
			defaultValue('')
			description('')
			branchFilter('.*')
			tagFilter('v[0-9]*')
			selectedValue('TOP')
		}
		choiceParam ('el_version', ['7', '8'], 'версия Enterprise Linux')
	}

	scm {
		git {
			branch('*/master')
			remote {
				url('git@gitlab:devops/rpm-packages/librespeedcli-rpm.git')
				credentials('299588f1-9fcb-461d-bdc1-24cccbd62ccb')
			}
		}
	}

	wrappers {
		preBuildCleanup()
		buildNameSetter {
			template('#${BUILD_NUMBER} $version')
			runAtStart(true)
			runAtEnd(false)
			descriptionTemplate('Distro: Centos el$el_version')
		}
		credentialsBinding {
			string('gitlab_token', 'gitlab_token')
		}
		ansiColorBuildWrapper {
			colorMapName('xterm')
		}
	}

	environmentVariables {
		env('ProjectName','librespeedcli')
	}

	steps {

		shell (
		'''
		# Установка переменных для ситуации когда формат тега $env_$version
		if [[ -z $hk_version ]];then
		  hk_version=$(echo $version | sed 's/^v//')
		fi

		if [[ -z hk_version ]];then
		  exit 1
		fi

		if [[ -z $github_token ]];then
		  github_token=123
		fi

		# Сборка пакета
		rpmbuild --define "_topdir ${WORKSPACE}" -D "hk_build ${BUILD_NUMBER}" -D "hk_version ${hk_version}" -D "gitlab_token ${gitlab_token}" -D "github_token ${github_token}" -D "godir %{_builddir}/%{name}/.go" -D "hk_dist el${el_version}" -bb SPECS/${ProjectName}.spec

		# Подготовка к доставке пакета на репозиторий
		curl -sH "PRIVATE-TOKEN:${gitlab_token}" 'https://gitlab/api/v4/projects/xx/repository/archive.tar.gz' -o ansible.tar.gz
		tar xf ansible.tar.gz --strip-components 1

		elver=$el_version

		echo "package_version: ${hk_version}" >> vars/packages.yml
		echo hk_repo_packages: >> vars/packages.yml
		for arch in noarch x86_64;do
		  if [[ -d RPMS/$arch ]];then
		    for package in $(find RPMS/$arch -type f -name "*.rpm" | xargs -I{} basename "{}");do
		      name=$(echo $package | grep -Po '^(\\w+-)+\\d' | sed 's/-[0-9]$//')
		      cat << EOF >> vars/packages.yml
		  - package: $package
		    arch: $arch
		    elver: $elver
		    repo: good
		    name: $name
		EOF
		    done
		  fi
		done

		'''.stripIndent().trim()
		)

		ansiblePlaybookBuilder {
			playbook('main.yml')
			inventory {
				inventoryPath {
					path('hosts')
				}
			}
			colorizedOutput(true)
			credentialsId('ansible-cred')
			disableHostKeyChecking(true)
			unbufferedOutput(true)
		}
	}
}

В этой задаче есть всего несколько мест, которые необходимо изменить, чтобы модифицировать ее под сборку любого другого проекта.

Обратите внимание, что доставка ПО в центральный репозиторий осуществляется через отдельный ansible-playbook. Готовые пакеты, которые отдает Jenkins-задача, он отправляет в нужный репозиторий (для примера выше это репозиторий good).

Особенности скриптов pre и post

Напоследок расскажем немного о продвинутом SPEC. В простейшем случае вам необходимо разложить файлы через rpm, однако со временем может потребоваться выполнение каких-либо команд при установке.

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

%pre -- перед установкой файлов
%post -- после установки файлов
%preun -- перед удалением файлов
%postun -- после удаления файлов

Интересно, что в процессе обработки пакета вы можете различать другие ситуации и выполнять для них разные инструкции:

  • первичная установка пакета;

  • удаление пакета;

  • обновление пакета.

Переменная $1 во время исполнения скриптов pre/post принимает разное значение, в зависимости от того, что происходит в системе: установка/удаление или обновление.

Первичная установка: $1 == 1
Обновление: $1 == 2
Удаление: $1 == 0

Если вы пропишете в скрипте условный переход через эту переменную, то сможете развести выполнение инструкций для различных ситуаций.

Выводы

RPM-deploy — довольно удобный и практичный инструмент управления жизненным циклом ПО. Мы используем его на протяжении длительного времени и, несмотря на наличие более современных альтернатив, не планируем отказываться от созданного специалистами HOSTKEY решения в обозримом будущем.


А еще в HOSTKEY можно пользоваться всеми возможностями технологичного API для быстрого заказа и управления серверами. Выберите сетевые настройки, операционную систему и получите любой сервер в течение 15 минут. Вы также можете собрать сервер индивидуальной конфигурации, в том числе с профессиональными GPU-картами.

Еще у нас можно добавить NVIDIA А5500, а специальный промокод для наших читателей «Я С ХАБРА» дает дополнительную скидку на любую покупку. При размещении заказа назовите промокод консультанту — и скидка ваша. 

Комментарии (1)


  1. 13werwolf13
    14.07.2022 10:13
    +1

    я думаю не лишним будет упомянуть такой инструмент как OBS который перекладывает заботу о структуре реп с головы ДевоПса на свою и позволяет собирать и rpm и deb и рачевый pkgbuild для разных дистров с учётом их особенностей построения как самих реп так и их пакетов.

    ну и думаю не лишним будет упомянуть что структура rpm реп всё же бывает разной:
    1) "классические" rh репы
    2) репы использующиеся opensuse (отличия совсем небольшие и в целом обратная совместимость полная
    3) отдельный особняк реп для apt-rpm используемом в altlinux а ранее в.. господи уже даже не помню как звался этот дистр..