Кукловодим через OpenVox. Часть 4. Локальное зеркало репозитория
Четвёртая, дополнительная часть цикла — про то, как перестать зависеть от внешнего репозитория. В предыдущих частях ноды качали агент с apt.voxpupuli.org, и это работало — но медленно, а иногда и с обрывами. Решение простое и знакомое любому, кто держал локальный apt-mirror: поднять своё зеркало и раздавать пакеты из собственной сети.
Схема получается такая: rsync тянет пакеты с Vox Pupuli, nginx раздаёт их по HTTP на нестандартном порту, а профиль Puppet прописывает ноды на локальный источник. Всё разворачивается на puppet-сервере (puppet.example.com) под root.
Синхронизация зеркала
Репозиторий Vox Pupuli устроен так: apt-часть лежит под apt/ (есть ещё yum/ и downloads/). Зеркалить весь репозиторий незачем — берём только apt/ и только компонент openvox8, а метаданные тянем целиком.
apt install -y rsync
mkdir -p /srv/openvox-mirror
rsync -avz --delete \
--include='apt/' \
--include='apt/GPG-KEY-openvox.pub' \
--include='apt/openvox-keyring.gpg' \
--include='apt/dists/***' \
--include='apt/pool/' \
--include='apt/pool/openvox8/***' \
--exclude='*' \
rsync://rsync.voxpupuli.org/packages/ /srv/openvox-mirror/
Проверяем, что зеркало приехало (apt-репозиторий физически лежит в /srv/openvox-mirror/apt/):
du -sh /srv/openvox-mirror # ~20 ГБ (все ОС + компоненты)
find /srv/openvox-mirror/apt/pool -name '*.deb' | head
ls /srv/openvox-mirror/apt/dists # debian11..13, ubuntu...
Про 20 гигабайт. В полную копию входят все поддерживаемые ОС и компоненты (server, db, bolt) плюс отладочные
*-dbgsym*. Хотите сузить — добавьте--exclude='*-dbgsym*'и include только нужных ОС. Но обновления потом докачиваются инкрементально, так что полная копия особо не мешает — место есть, зато ничего не придётся доливать при подключении новой ОС.
Раздача через nginx
Ключевая тонкость — root должен указывать прямо на apt/. Тогда URL для нод получается чистым (/dists/..., а не /apt/dists/...).
apt install -y nginx
cat > /etc/nginx/sites-available/openvox-mirror <<'EOF'
server {
listen 3333;
listen [::]:3333;
server_name puppet.example.com;
root /srv/openvox-mirror/apt;
autoindex on;
location / {
try_files $uri $uri/ =404;
}
}
EOF
ln -sf /etc/nginx/sites-available/openvox-mirror /etc/nginx/sites-enabled/openvox-mirror
nginx -t
systemctl reload nginx
Проверяем, что зеркало отдаёт метаданные:
curl -sI http://puppet.example.com:3333/dists/debian13/InRelease # HTTP/1.1 200 OK
curl -s http://puppet.example.com:3333/dists/ | head
Порт 3333 нестандартный и по умолчанию закрыт — если есть файрвол, откройте его: ufw allow 3333/tcp или соответствующее правило nftables.
Регулярная синхронизация по cron
Зеркало должно обновляться само. Выносим rsync в скрипт и вешаем на cron:
cat > /usr/local/bin/openvox-mirror-sync.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
rsync -az --delete \
--include='apt/' \
--include='apt/GPG-KEY-openvox.pub' \
--include='apt/openvox-keyring.gpg' \
--include='apt/dists/***' \
--include='apt/pool/' \
--include='apt/pool/openvox8/***' \
--exclude='*' \
rsync://rsync.voxpupuli.org/packages/ /srv/openvox-mirror/
EOF
chmod +x /usr/local/bin/openvox-mirror-sync.sh
cat > /etc/cron.d/openvox-mirror <<'EOF'
17 4 * * * root /usr/local/bin/openvox-mirror-sync.sh >/var/log/openvox-mirror-sync.log 2>&1
EOF
Первичная синхронизация тянет все 20 ГБ, а дальше cron докачивает только новое — так что ежедневный прогон быстрый.
Переключение нод на зеркало
Ноды нужно перенаправить на локальный источник вместе с GPG-ключом — иначе apt справедливо откажется доверять неподписанному репозиторию. И здесь пряталась засада, на которую я потратил некоторое время:
Коварный нюанс с путём к ключу. Release-пакет OpenVox кладёт ключ в
/etc/apt/keyrings/openvox-keyring.gpg, но в строкеsigned-by=некоторых версий прописан/usr/share/keyrings/...— путь, которого на ноде нет. Симптом:Failed to parse keyring ... No such file or directoryи жалоба, что «репозиторий не подписан». Профиль ниже кладёт и ключ, и источник в согласованные пути, разом устраняя эту проблему.
Кладём ключ в control-repo
Ключ будем раздавать нодам через Puppet, поэтому кладём его в файлы модуля:
cd /root/control-repo
mkdir -p site-modules/profile/files
cp /etc/apt/keyrings/openvox-keyring.gpg site-modules/profile/files/openvox-keyring.gpg
Профиль profile::openvox_mirror
site-modules/profile/manifests/openvox_mirror.pp:
class profile::openvox_mirror (
String $mirror_url = 'http://puppet.example.com:3333',
) {
# suite = имя каталога dists/ под конкретную ОС
$suite = $facts['os']['name'] ? {
'Debian' => "debian${facts['os']['release']['major']}",
'Ubuntu' => "ubuntu${facts['os']['release']['full']}", # напр. ubuntu24.04
default => undef,
}
if $suite == undef {
warning("profile::openvox_mirror: неподдерживаемая ОС ${facts['os']['name']}")
} else {
file { '/etc/apt/keyrings':
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}
file { '/etc/apt/keyrings/openvox-keyring.gpg':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
source => 'puppet:///modules/profile/openvox-keyring.gpg',
require => File['/etc/apt/keyrings'],
}
file { '/etc/apt/sources.list.d/openvox8-release.list':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => "# Managed by OpenVox\ndeb [signed-by=/etc/apt/keyrings/openvox-keyring.gpg] ${mirror_url}/ ${suite} openvox8\n",
require => File['/etc/apt/keyrings/openvox-keyring.gpg'],
}
}
}
Обратите внимание на $suite: для Debian имя каталога собирается из мажорной версии (debian13), а для Ubuntu — из полной (ubuntu24.04). Это соответствует тому, как каталоги названы в dists/ на зеркале, и разведено селектором по факту ОС.
Подключаем профиль в profile::base:
class profile::base {
include profile::openvox_mirror
include profile::users
include profile::packages
include profile::rsyslog
include profile::zabbix_agent
}
И раскатываем привычным циклом:
git add -A && git commit -m 'Add profile::openvox_mirror'
git push origin production
/opt/puppetlabs/puppet/bin/r10k deploy environment production -pv
Проверка
На любой ноде после прогона:
puppet agent -t
apt update # без ошибок про подпись
apt policy openvox-agent # источник должен быть puppet.example.com:3333
Ожидаемый вывод apt policy — версии агента отдаются с локального зеркала:
*** 8.28.1-1+debian13 1001
500 http://puppet.example.com:3333 debian13/openvox8 amd64 Packages
Быстрое переключение одной ноды вручную
Иногда нужно перевести ноду на зеркало здесь и сейчас, не дожидаясь раскатки профиля. Тогда правим источник напрямую — и не забываем про тот самый путь к ключу:
# заменить хост
sed -i 's|https://apt.voxpupuli.org/|http://puppet.example.com:3333/|' \
/etc/apt/sources.list.d/openvox*.list
# проверить путь к ключу в signed-by и привести к реальному расположению
grep signed-by /etc/apt/sources.list.d/openvox*.list
ls -la /etc/apt/keyrings/openvox-keyring.gpg # где ключ реально лежит
# если пути разошлись — поправить signed-by на реальный путь
sed -i 's|/usr/share/keyrings/openvox-keyring.gpg|/etc/apt/keyrings/openvox-keyring.gpg|' \
/etc/apt/sources.list.d/openvox*.list
apt update
apt policy openvox-agent
Что в итоге
Приятная деталь: профиль не конфликтует с чужими репозиториями — он трогает только источник OpenVox, а репозитории Zabbix, Debian и прочие на нодах не затрагивает.
На этом цикл про OpenVox можно считать законченным. Мы прошли путь от установки сервера до полностью автономной инфраструктуры: свой сервер управления, ноды под контролем, настройки в виде кода и даже пакеты, раздаваемые из собственной сети. Внешний мир теперь нужен этой конструкции по минимуму — ровно так, как и должно быть у инфраструктуры, за которую отвечаешь сам.