evgen@spb: ~/2026/06/kuklovodim-cherez-openvox-chast-2-nastrojka-klientov/

Кукловодим через OpenVox. Часть 2. Настройка клиентов

В первой части мы подняли сервер управления и научили его раскатывать код из git через r10k. Сервер есть, а управлять ему пока нечем. Пора подключать первых клиентов.

Звучит как формальность — поставил агент, подписал сертификат, готово. На тестовой ноде так и есть. А вот когда начинаешь подключать живые серверы, где уже крутятся базы, почтовики и мониторинг, выясняется, что дьявол, как обычно, в деталях. Про эти детали и будет большая часть статьи — я собрал сюда все грабли, на которые наступил сам.

Адрес сервера в примерах — puppet.example.com. Команды на ноде выполняются от root.

Подключаем ноду: четыре шага

Схема простая: правильное имя → установка агента → запрос сертификата → подпись на сервере. Разберём по порядку, потому что ошибка на первом шаге аукнется на всех остальных.

Шаг 0. Правильный hostname — до первого прогона

Это тот шаг, который хочется пропустить, а потом переделывать всё заново. certname — имя, под которым нода известна серверу, — генерируется из FQDN при первом контакте и сам потом не меняется. Поэтому имя надо задать заранее:

hostnamectl set-hostname ИМЯ.example.com
hostname -f                              # должен вернуть полный FQDN с доменом

Если hostname -f отдаёт короткое или неправильное имя — пропишите его в /etc/hosts:

echo 'IP_НОДЫ  ИМЯ.example.com ИМЯ' >> /etc/hosts

Заодно убедитесь, что нода вообще видит сервер. Если DNS нет:

echo 'IP_СЕРВЕРА  puppet puppet.example.com' >> /etc/hosts

Шаг 1. Агент, и обязательно от той же версии ОС

Здесь прячется самая обидная ошибка во всём процессе.

Критично. Release-пакет OpenVox привязан к версии ОС. Поставите пакет «не от той» версии — получите GLIBC ... not found, и агент просто не запустится. Проверьте версию заранее: cat /etc/os-release на Debian или lsb_release -rs на Ubuntu.

Дальше берёте пакет ровно под свою систему.

Debian 13:

wget -q https://apt.voxpupuli.org/openvox8-release-debian13.deb && dpkg -i openvox8-release-debian13.deb && apt update && apt install -y openvox-agent

Debian 12:

wget -q https://apt.voxpupuli.org/openvox8-release-debian12.deb && dpkg -i openvox8-release-debian12.deb && apt update && apt install -y openvox-agent

Debian 11:

wget -q https://apt.voxpupuli.org/openvox8-release-debian11.deb && dpkg -i openvox8-release-debian11.deb && apt update && apt install -y openvox-agent

Ubuntu (замените ubuntu24.04 на свою версию — lsb_release -rs):

wget -q https://apt.voxpupuli.org/openvox8-release-ubuntu24.04.deb && dpkg -i openvox8-release-ubuntu24.04.deb && apt update && apt install -y openvox-agent

Если wget вернёт 404 — значит для этой версии ОС пакета в репозитории пока нет, смотрите актуальный список на apt.voxpupuli.org.

Шаг 2. Запрос сертификата — на ноде

/opt/puppetlabs/bin/puppet config set server puppet.example.com --section main
/opt/puppetlabs/bin/puppet agent -t --waitforcert 60

Агент сгенерирует запрос на сертификат (CSR) и повиснет в ожидании подписи, переспрашивая сервер каждые 60 секунд. Это штатное поведение — не прерывайте, идёте подписывать на сервер.

Шаг 3. Подпись — на сервере

puppetserver ca list                                  # увидите ожидающий CSR
puppetserver ca sign --certname ИМЯ.example.com

Как только подписали, агент на ноде сам подхватит сертификат и докрутит первый прогон. Применятся все базовые правила: пользователи, пакеты, локаль и часовой пояс, мониторинг — всё, что вы заложили в базовую роль.

Проверяем, что связка сложилась. На ноде — чистый прогон без ошибок:

/opt/puppetlabs/bin/puppet agent -t

На сервере — сертификат в списке подписанных:

puppetserver ca list --all | grep ИМЯ

Bootstrap-скрипт: чтобы не делать это руками каждый раз

Четыре шага — нормально для одной ноды. Для десятой уже хочется автоматизации. У меня для этого есть bootstrap-node.sh: он сам определяет ОС, подключает локальное зеркало пакетов (чтобы не ходить на медленный внешний репозиторий), ставит агент, задаёт certname из FQDN и отправляет CSR.

#!/usr/bin/env bash
#
# Bootstrap ноды OpenVox через локальное зеркало.
#
set -euo pipefail

PUPPET_SERVER="puppet.example.com"
MIRROR_URL="http://puppet.example.com:3333"
KEYRING="/etc/apt/keyrings/openvox-keyring.gpg"
WAITFORCERT=30

die() { echo "ОШИБКА: $*" >&2; exit 1; }

[ "$(id -u)" -eq 0 ] || die "запусти от root"
[ -r /etc/os-release ] || die "нет /etc/os-release"
. /etc/os-release

# suite = имя каталога dists/ на зеркале
case "${ID:-}" in
  debian)
    case "${VERSION_ID:-}" in
      11|12|13|14) SUITE="debian${VERSION_ID}" ;;
      "") case "${VERSION_CODENAME:-}" in
            bullseye) SUITE="debian11" ;;
            bookworm) SUITE="debian12" ;;
            trixie)   SUITE="debian13" ;;
            forky)    SUITE="debian14" ;;
            *) die "неизвестный Debian: ${VERSION_CODENAME:-}" ;;
          esac ;;
      *) die "Debian ${VERSION_ID} не поддерживается" ;;
    esac ;;
  ubuntu)
    [ -n "${VERSION_ID:-}" ] || die "нет версии Ubuntu"
    SUITE="ubuntu${VERSION_ID}" ;;   # напр. ubuntu24.04
  *) die "ОС '${ID:-}' не поддерживается" ;;
esac
echo ">> ${ID} ${VERSION_ID:-$VERSION_CODENAME} → suite: ${SUITE}"

# GPG-ключ зеркала
install -d -m 0755 /etc/apt/keyrings
wget -qO "$KEYRING" "${MIRROR_URL}/openvox-keyring.gpg" || die "не скачался ключ"
file "$KEYRING" | grep -qiE 'PGP|GPG|keybox|public key' || die "ключ невалиден (не HTML?)"
chmod 0644 "$KEYRING"

# источник на локальное зеркало (signed-by указывает на реальный путь ключа)
cat > /etc/apt/sources.list.d/openvox8-release.list <<EOF
# Managed manually (bootstrap) — local OpenVox mirror
deb [signed-by=${KEYRING}] ${MIRROR_URL}/ ${SUITE} openvox8
EOF

apt-get update
apt-get install -y openvox-agent

PUPPET=/opt/puppetlabs/bin/puppet
[ -x "$PUPPET" ] || die "нет $PUPPET"

FQDN="$(hostname -f 2>/dev/null || true)"
case "$FQDN" in *.*) : ;; *) die "hostname -f без домена: '$FQDN'";; esac
echo ">> certname: $FQDN"

"$PUPPET" config set server   "$PUPPET_SERVER" --section main
"$PUPPET" config set certname "$FQDN"          --section main
"$PUPPET" agent -t --waitforcert "$WAITFORCERT" || true

echo ""
echo "Теперь на СЕРВЕРЕ подпиши: puppetserver ca sign --certname $FQDN"

Сохраняете в bootstrap-node.sh, запускаете bash bootstrap-node.sh. Подпись всё равно делается руками на сервере — это операция центра сертификации, автоматизировать её без нужды не стоит.

Здесь важно понимать разделение ролей. Скрипт нужен только для первого касания новой ноды — довести её до состояния «агент стоит и подписан». Дальше поддержанием правильного источника пакетов занимается отдельный профиль при каждом прогоне. Небольшое дублирование логики между скриптом и профилем — это нормально: скрипт для bootstrap, профиль для постоянного enforcement.

Оговорюсь про безопасность честно. В этом варианте GPG-ключ зеркала тянется по HTTP, а проверка ловит лишь то, что скачался не HTML вместо ключа. Отпечаток ключа не сверяется. Для доверенной внутренней сети, где зеркало и ноды под вашим контролем, это осознанный компромисс ради простоты. Но если ноды bootstrap-ятся через недоверенный канал — раздавайте ключ по HTTPS и сверяйте фингерпринт, иначе весь смысл подписи пакетов теряется.

Если зеркало недоступно (нода в другой сети, без доступа к порту 3333) — ставьте release-пакет напрямую с apt.voxpupuli.org, как в ручной установке выше. После первого прогона профиль сам переключит ноду на зеркало.

Живые серверы подключаем через --noop

А вот теперь то, ради чего стоило писать эту статью.

Когда подключаете сервер, на котором уже что-то работает — база, почтовик, мониторинг — никогда не гоните боевой прогон сразу. Сначала сухой:

/opt/puppetlabs/bin/puppet agent -t --noop

--noop (no-operation) показывает, что Puppet собирается сделать, ничего при этом не меняя. Это ваша страховка от конфликтов, и она не абстрактная.

Реальный случай из практики: подключаю сервер мониторинга, а на нём уже стоит zabbix-agent2. Базовый профиль, ничего не подозревая, лезет ставить конфликтующий классический zabbix-agent. Боевой прогон устроил бы весёлые полчаса. --noop показал бы это заранее — и ноду можно было спокойно исключить из профиля через per-node Hiera до того, как что-то сломается.

Мораль простая: на чистой ноде --noop избыточен, на обжитой — обязателен.

Отключаем ноду

Отключение — это не просто «выключить агент». Сервер продолжит считать ноду своей, пока вы не отзовёте сертификат.

На сервере — отозвать сертификат:

puppetserver ca clean --certname ИМЯ.example.com

clean убирает и подписанный сертификат, и висящий CSR. После этого сервер перестаёт доверять ноде и компилировать для неё каталог.

Убрать per-node данные, если заводили. Если для ноды был файл переопределений в Hiera — удаляем его через тот же git-цикл, что и любую другую правку:

cd /root/control-repo
rm -f data/nodes/ИМЯ.example.com.yaml
git add -A && git commit -m 'remove node ИМЯ' && git push origin production
/opt/puppetlabs/puppet/bin/r10k deploy environment production -pv

На ноде — остановить агент, если нода ещё жива:

systemctl disable --now puppet
# при желании снести агент полностью:
apt purge -y openvox-agent
rm -rf /etc/puppetlabs

Переименование подключённой ноды

Раз certname не меняется сам, смена имени — это фактически перевыпуск сертификата в четыре хода.

1. На ноде — сменить hostname:

hostnamectl set-hostname НОВОЕ.example.com
hostname -f                              # проверь FQDN

2. На сервере — отозвать старое имя:

puppetserver ca clean --certname СТАРОЕ.example.com

3. На ноде — удалить локальный SSL и перезапросить:

systemctl stop puppet 2>/dev/null
rm -rf /etc/puppetlabs/puppet/ssl
/opt/puppetlabs/bin/puppet config set certname НОВОЕ.example.com --section main
/opt/puppetlabs/bin/puppet agent -t --waitforcert 60

4. На сервере — подписать новое имя:

puppetserver ca sign --certname НОВОЕ.example.com

И не забудьте переименовать per-node Hiera-файл, если он был: data/nodes/СТАРОЕ....yamldata/nodes/НОВОЕ....yaml.

Типовые проблемы

Всё, что ловил на живых нодах, — в одну таблицу, чтобы было куда возвращаться.

СимптомПричина и решение
GLIBC ... not found при запуске агентаУстановлен release-пакет не от той версии ОС. Удалите openvox-agent и неверный release-пакет, поставьте правильный (см. Шаг 1).
No more routes to ca / Connection refused на 8140Нода не резолвит puppet.example.com или сервер не запущен. Проверьте /etc/hosts / DNS и systemctl status puppetserver на сервере.
Certificate ... does not match its private keyЛокальный SSL рассинхронизирован. rm -rf /etc/puppetlabs/puppet/ssl на ноде + ca clean на сервере, затем перезапросить.
ca sign говорит «No certificate request found»CSR ещё не дошёл (запустите agent -t --waitforcert на ноде) либо имя сертификата не совпадает с FQDN.
Прогон запущен без sudoPuppet использует SSL из ~/.puppetlabs вместо системного. Запускайте агент от root полным путём /opt/puppetlabs/bin/puppet.
Failed to parse keyring ... No such file / «репозиторий не подписан»Путь в signed-by= не совпадает с реальным расположением ключа. Ключ обычно в /etc/apt/keyrings/openvox-keyring.gpg; приведите signed-by= к нему.

Что дальше

Теперь у сервера есть кем управлять: ноды подключены, сертификаты подписаны, а живые серверы вы умеете подключать не ломая. Но пока всё, что они получают, — это абстрактная «базовая роль».

В следующей части перейдём к самому интересному — начнём собственно кукловодить: писать роли и профили, раскладывать настройки по Hiera и делать так, чтобы конкретная нода получала ровно то, что ей положено, и ни строчкой больше.

evgen@spb: ~/contacts
evgen@spbping -c1 evgen.info
64 bytes from evgen.info: отвечу в течение рабочего дня
evgen@spbcat contacts.txt
mail      ya@evgen.info
telegram  @EvgenOne
github    github.com/onegin
blog      evgen.info/blog
evgen@spbecho $ЗАЧЕМ_ПИСАТЬ
интересная инфраструктурная задача · вопрос по Proxmox или Icinga ·
желание обсудить, почему memtest молчал, а память была битая
evgen@spb
© 2026 Евгений Подолинский · Санкт-Петербург
синий здесь – не тот, о котором вы подумали.