Привет! Меня зовут Андрей Колесников, я тимлид одной из DevOps-команд Авито. Уже 10 лет я работаю с высоконагруженными и бизнес-критичными системами. В этой статье рассказываю, как мы управляем нашей инфраструктурой с помощью Puppet, и объясняю, почему мы продолжаем его использовать.
Это не рассказ о «фичах ради фич» и не реклама инструмента. Я не буду уговаривать всех срочно перейти на Puppet. Вместо этого я поделюсь нашим практическим опытом: как мы используем Puppet в Avito, какие подходы и практики применяем, как масштабируем систему и какие грабли собрали по пути.
Всё, о чём я рассказываю, — это результат работы сразу нескольких инфраструктурных команд, и я благодарен коллегам за помощь в подготовке статьи. А ещё спасибо моей команде — пока они держат инфраструктуру в порядке, я могу рассказывать об этом вам.

Содержание:
Puppet в контексте: почему не Ansible и что у нас в цифрах
Прежде чем углубляться в практики, немного статистики и контекста — чтобы понять, где вообще сегодня стоит Puppet.
По данным JetBrains, среди систем управления конфигурациями на первом месте уверенно держится Ansible. Это неудивительно: он написан на Python — языке, который знают даже те, кто не пишет код каждый день. Ansible прост, популярен и активно развивается.
А вот дальше идут Puppet, Chef и Salt. Причём Puppet продолжает медленно, но уверенно укреплять свои позиции — по свежей статистике, его используют примерно 10% респондентов, и эта доля растёт.

Примерно 30% компаний вообще используют кастомные решения — зачастую скрипты или самописные фреймворки. А кто-то работает с Custom Resource Definition (CRD) в Kubernetes — но это уже совсем другая история.
Как это выглядит в Avito: нагрузка, рост и роли Puppet
Теперь рассказываю на примере Avito. Мы кратно растём каждый год, и вместе с продуктом увеличивается и наша инфраструктура. Особенно это стало заметно после 2022 года, когда нагрузка на внутренний рынок резко возросла.
Чтобы вы понимали масштаб:
у нас сейчас более 15 000 серверов, из которых ~10 000 управляются Puppet;
в моменте наши HTTP-балансировщики обрабатывают около 10 миллионов запросов в минуту;
для управления написано более 50 модулей Puppet;
и что важно: в Puppet-код контрибьютят не только DevOps-инженеры, но и обычные разработчики. Например, если бэкендер хочет ускорить тестирование и выкатку своего сервиса — он может сам внести изменения в манифест.
Как живёт Puppet под капотом
С точки зрения нагрузки на Puppet-сервера:
в среднем — около 65 запросов в минуту для одного метода;
в пике — до 150 запросов, но это редкость;
время компиляции каталога на Puppet Server в среднем 10 секунд, а в пиках может вырасти до минуты — это исключения из правил;
PuppetDB справляется ещё быстрее — в среднем 1 секунда на ответ.
Мы регулярно мониторим эти метрики — это помогает понимать, когда нужно масштабировать инфраструктуру, а когда просто оптимизировать конфигурацию.
Как мы используем Puppet: архитектура и подходы в Avito
Чтобы эффективно управлять инфраструктурой с помощью Puppet, важно правильно расставить границы его применения. В Avito мы чётко определили, за что отвечает этот инструмент: Puppet — это наш основной способ поддерживать конфигурацию серверов в актуальном состоянии в рамках подхода Infrastructure as Code (IaC). Он не закрывает все задачи. Например, для одноразовых операций или быстрой раскатки некоторых приложений мы используем Ansible — он проще для таких целей. Но для постоянного управления конфигурацией инфраструктуры Puppet незаменим.
Как работает Puppet: от агента до сервера
Puppet использует pull-модель. Это значит, что на каждом управляемом сервере (или «ноде») работает агент. Раз в 30 минут он обращается к Puppet-серверу за актуальной конфигурацией. Сам сервер не «толкает» изменения — он просто ждет запросов от нод.
Конфигурация описывается в манифестах на языке Puppet DSL — синтаксически он напоминает Ruby. В этих манифестах описываются ресурсы, с помощью которых вносятся изменения на сервере. Например, вот простой манифест, который копирует индексный HTML-файл на сервер server1.avito:
node 'server1.avito' {
file {'/www/index.html':
ensure => 'file',
content => 'Hello, DevOps!',
owner => 'nginxuser',
mode => '0644'
}
}
Ресурсы, модули, профили и роли
Чтобы конфигурации было удобно поддерживать и переиспользовать, мы строим её по многоуровневой архитектуре:
ресурсы — минимальные сущности: с их помощью вносятся изменения в файлы, пакеты, сервисы и так далее;
модули — независимые проекты, объединяющие ресурсы и другие Puppet-сущности по смыслу. Например, модуль nginx устанавливает пакет, копирует файл конфигурации и запускает веб-сервер:
class nginx {
package {'nginx':
ensure => present,
}
file {'/www/index.html':
ensure => 'file',
content => 'Hello, DevOps!',
owner => 'nginxuser',
mode => '0644',
}
service {'nginx':
ensure => running,
enable => true,
}
}
профили объединяют несколько модулей и других Puppet-сущностей для настройки подсистемы. Например, профиль для frontend может включать nginx, мониторинг и алерты;
роли описывают назначение сервера в бизнес-логике. По best practices Puppet, каждой ноде назначается одна роль — это упрощает управление и исключает конфликты.
Модули и control-repo: как всё устроено
В Avito мы воспринимаем модули как самостоятельные единицы, которые разрабатываются, версионируются и распространяются независимо. Каждая команда пишет модули и выкладывает их во внутренний Forge — аналог Ansible Galaxy. Это позволяет использовать готовые решения без дублирования.
Манифесты ролей и профилей собираются в control-repo — главный репозиторий, из которого Puppet-сервер получает всю конфигурацию. Внутри него указываются зависимости от модулей и данные Hiera.
Мы придерживаемся официального Language Guide от Puppet и дополняем его собственными правилами для оформления модулей и control-repo. Это помогает нам соблюдать единый стиль инфраструктурного кода.
Управление данными: Hiera и ENC
Когда инфраструктура растёт, манифесты усложняются. Чтобы отделить логику от данных, мы используем такой компонент Puppet, как Hiera. В манифестах остаётся логика, а данные, то есть параметры классов (модулей, профилей), выносятся в YAML-файлы Hiera. Это делает код чище и облегчает поддержку.
Кроме того, мы используем ENC (External Node Classifier) — компонент, который позволяет динамически определять, какие роль и окружение назначить ноде. Сервер получает hostname, отправляет его ENC, и тот возвращает, какие роль и окружение применить (например, окружение production и роль Kubernetes_Dev). Благодаря этому мы можем отдать управление ролями во внешний сервис, что позволяет автоматизировать управление ролями и окружениями, а также делает проще ручное управление ими. Для сравнения: раньше мы прямо в манифестах приписывали роли хостам, любое изменение роли или добавление нового сервера вынуждало делать коммит в контрол-репу.

Как мы доставляем и проверяем инфраструктурный код
Чтобы конфигурации попадали на серверы быстро, прозрачно и безопасно, мы выстроили чёткий CI/CD-процесс на основе Puppet и собственного инструмента iack. Начнём с доставки изменений на Puppet-сервер.
Как код попадает на Puppet-сервер
В Avito мы используем Bitbucket как Git-репозиторий и стандартный Git workflow — изменения вносятся в ветку, создаётся PR в основную ветку, при успешном прохождении тестов и получении аппрувов PR можно мёржить. Модули и control repo хранятся в отдельных репозиториях. Когда мы пушим в репозиторий, в работу сразу включается TeamCity: при наличии новых коммитов в ветках запускаются проверки, статус проверок возвращается в Bitbucket. В случае с модулем, если всё проходит успешно и PR мёржится в основную ветку — запускается релизная сборка модуля, модуль публикуется во внутренний Forge.
С control repo история другая: при пуше в ветку точно так же запускаются тесты. Кроме того, при пуше в любую ветку отправляется webhook на соответствующий Puppet-сервер. На Puppet-сервере запущен сервис, который слушает наши webhook'и, и при получении запускает выкатку окружения, соответствующего ветке. Последним шагом сервис собирает список выкаченных на текущий момент окружений и отправляет его в сервис ENC.

Как мы тестируем Puppet-код
Писать манифесты — только половина дела. Чтобы не уронить продакшн и не потерять время на отладку, мы выстроили систему проверок, встроенных в собственный инструмент — iack.
Что делает iack
iack — это наша обвязка над существующими утилитами (puppet-lint, yamllint, puppet-strings, puppet-rspec, test-kitchen, rubocop, r10k, librarian-puppet), расширенная собственной функциональностью, которая упрощает создание модулей, их разработку и тестирование, генерацию документации.
Примеры команд:
iack init <module|control> — создаёт новый модуль или control repo из шаблона;
iack validate — запускает различные линтеры, проверяет разрешение зависимостей, в модулях контролирует корректность версионирования изменений по SemVer и проверяет, что в Changelog добавлена запись про текущую версию;
iack dep show — сравнивает версии модулей в проекте с последними из Forge;
iack test unit — проверяет соответствие версий модулей для юнит-тестов версиям для выкатки на production (они хранятся в независимых местах), после чего запускает сами юнит-тесты.
Пример: как мы пишем и деплоим модуль
Вот как выглядит полный цикл работы:
Создаём модуль: iack init module.
Пишем манифесты, генерируем документацию (iack doc).
Опционально (но желательно) запускаем проверки локально.
Пушим в ветку, создаём PR в мастер-ветку, в CI/CD запускаются все проверки (линтеры, unit- и acceptance-тесты).
Мёржим PR, в Teamcity запускается релизная сборка, собирается архив модуля, пушится в форж.
Теперь модуль готов к использованию в контрол-репах и других модулях.
В Avito действует строгое правило: если не проходит хотя бы один тест — pull request отклоняется. Повторное тестирование идёт уже локально, до полного успешного прогона.
Такой процесс, хотя и требует дисциплины, позволяет избежать сбоев в продакшне и делает инфраструктурные изменения прозрачными для всей команды.
Безопасность и работа с секретами
Один из важных аспектов нашей работы с Puppet — это безопасность и управление секретами. Мы стараемся уделять этому максимум внимания.
Для безопасного хранения и получения чувствительных данных используем HashiCorp Vault. Доступ к нему осуществляется через бэкэнд для Hiera, а чтобы упростить работу разработчиков, мы написали собственную обёртку для работы с секретами в манифестах. Она позволяет получать секреты из Vault напрямую в переменные манифеста. Это делает процесс безопасным, предсказуемым и удобным.
Кроме доступа к секретам, мы также заботимся о том, чтобы секреты не просочились куда-то в логи: для этого в Puppet есть тип данных Sensitive, который скрывает секретные данные при записи в лог (в том числе на экран при прогоне агента вручную и в PuppetDB).
Кейс: неявные зависимости и массовый сбой
Теперь о практическом опыте. Один из заметных инцидентов связан с неочевидными зависимостями при обновлении конфигурации DNS.
Мы решили перейти на кеширующий DNS-сервер Unbound и удалить уже неиспользуемый модуль dnsmasq. На первый взгляд — простая задача. Однако в Debian dnsmasq состоит из двух пакетов: dnsmasq и dnsmasq-base, и второй из них является зависимостью пакета LXC. Мы описали в манифестах удаление обоих пакетов.
Проблема в том, что в LXC у нас работают ключевые инфраструктурные сервисы. В результате после удаления пакета dnsmasq-base, вместе с ним был автоматически удалён пакет LXC, а контейнеры остановились. Это вызвало массовую деградацию инфраструктуры на несколько часов.
Ситуацию осложнила pull-модель Puppet: изменения распространяются по нодам асинхронно, каждая нода обращается к Puppet-серверу по своему расписанию (у нас это раз в 30 минут). Из-за этого проблема стремительно распространялась, а точную причину было трудно отследить.
Что мы сделали, чтобы это не повторилось
После этого кейса мы усилили процесс доставки изменений:
убрали автоматическую раскатку модулей на Puppet-сервера (раньше некоторые модули автоматически обновлялись на Puppet-серверах;
расширили базу acceptance-тестов.Теперь acceptance-тесты написаны для всех наших 100+ ролей;
реализовали автоматическую канареечную выкатку — изменения сначала применяются на ограниченном пуле хостов, и только после успешной проверки этих хостов можно мёржить PR и раскатывать изменения на все хосты.
Этот инцидент стал для нас серьёзным уроком: даже одно, на первый взгляд, безопасное действие может привести к масштабным последствиям, если не учитывать все зависимости.
Кейс: предел производительности и горизонтальное масштабирование
Следующий важный эпизод связан с производительностью Puppet-сервера. В какой-то момент мы столкнулись с тем, что он попросту перестал справляться с нагрузкой — на графиках чётко был виден резкий скачок, за которым последовало замедление процессов и ухудшение работы инфраструктуры.
Чтобы стабилизировать ситуацию, мы горизонтально масштабировали кластер Puppet — добавили второй сервер. Ситуация улучшилась моментально, и теперь это наш стандартный подход при росте нагрузки: масштабироваться вширь, добавляя новые compile-серверы.
Как мы это реализовали
Без лишних изысков: просто обратились к официальной документации Puppet, которая дала все необходимые ответы. Мы задеплоили дополнительную ноду с ролью compile master, а роль CA (сертификатный центр) оставили только на одной основной машине — как и рекомендует документация.
Балансировку нагрузки настроили через DNS SRV-записи, что оказалось простым и надёжным решением.
Нагрузочное тестирование: проверяем в боевых условиях
Чтобы избежать подобных сбоев в будущем — особенно в пиковые часы — мы начали регулярно нагружать наш кластер Puppet-серверов в продакшне, тестируя его на прочность.
Вот пример одного из таких тестов:
Мы проверяли кластер, который обслуживает конфигурации для HTTP-балансировщиков, и поэтапно запускали агентов на нодах.
тест шёл в проде и ранним утром, чтобы минимизировать потенциальные риски;
сначала запустили агентов на 30% хостов, затем — на 100%;
метрики показали, что серверы выдержали: тайминги ответа увеличились максимум до 10 секунд, а затем стабилизировались.
Что это дало
Результаты подтвердили: текущая архитектура Puppet-кластера справляется с пиковыми нагрузками.
Мы продолжаем использовать этот подход, добавляя compile-серверы по мере роста инфраструктуры. Пока всё работает стабильно — и это лучший индикатор.
Плюсы и минусы Puppet
После всего сказанного самое время подвести промежуточный итог и кратко обозначить, за что мы ценим Puppet, а с какими особенностями приходится мириться.
Плюсы:
pull-модель. Нет необходимости открывать 22-й порт, как в случае с Ansible — это упрощает жизнь с точки зрения информационной безопасности;
зрелость и стабильность. Puppet на рынке уже более 20 лет, вокруг него сформировалась зрелая экосистема, которую мы расширяем своими инструментами;
гибкость. Puppet DSL позволяет выразить любые инфраструктурные задачи, а при необходимости можно использовать Ruby — это даёт широкие возможности и тонкую настройку;
хорошая документация. Почти вся на английском, но весьма подробная. Мы тоже стараемся вносить вклад в её популяризацию в русскоязычном сообществе.
Минусы:
высокий порог входа. Puppet сложнее для новичков, чем, например, Ansible. В частности из-за специфического Puppet DSL и декларативной парадигмы программирования, которая многим незнакома;
избыточная гибкость. Одну и ту же задачу можно реализовать множеством способов — это требует договорённостей и дополнительных обсуждений внутри команды;
асинхронность. Pull-модель с периодическим опросом серверов означает, что результат изменений не виден мгновенно. Это может усложнять отладку и замедлять troubleshooting.
Итоги и подход Avito
В этой статье я постарался не просто рассказать о Puppet, а поделиться нашим системным подходом к Infrastructure as Code в Avito — о том, как мы выстраиваем процессы и масштабируем инфраструктуру.
Наши главные принципы:
ясные границы инструментов. У каждого средства — своя зона ответственности. Puppet у нас отвечает за поддержание серверов в актуальном состоянии;
открытая экспертиза. В разработке Puppet участвуют не только DevOps, но и разработчики, которым это помогает быстрее выкатывать и тестировать свой код;
стандартизация. Мы договорились о код-стайле и сделали модуль основной единицей переиспользуемого кода. Все модули доступны инженерам через внутренний Forge;
инструменты упрощения. Мы создаём свои обёртки и тулзы, вроде iack, чтобы автоматизировать шаблонные действия и снизить барьер входа;
фокус на безопасность. Работу с секретами мы выстроили через Hiera и Vault, а также реализовали собственные безопасные модули;
горизонтальное масштабирование. Мы убедились, что текущая архитектура Puppet-серверов легко масштабируется под кратный рост инфраструктуры.
Puppet — это не волшебная палочка и не единственно верное решение. Как и любой инструмент, он требует дисциплины, опыта и системного подхода. Но если всё это есть — он отлично масштабируется и работает на благо команды.
Если вы находитесь на технологическом перекрёстке, надеюсь, наш опыт станет для вас дорожным указателем — вне зависимости от того, выберете ли вы Puppet, Ansible или свой путь.
Что думаете о Puppet? Делитесь опытом внедрения инструмента в комментариях!
А если хотите вместе с нами помогать людям и бизнесу через технологии — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.
Комментарии (4)
utoplenick
25.08.2025 14:18Используете ли какой-нибудь оркестратор, например choria? Это могло бы решить задачу принудительного применения конфигурации в моменте, да и помимо прочего ансибловую часть на неё можно перевесить но в тесной интеграции с puppet.
Dhwtj
все ваши пользователи нажмут кнопку (запрос) 100 млн раз в день.
дайте подумаем, зачем же 15.000 серверов...
то есть, теперь 2 вопроса
на каждое целевое нажатие кнопки 150 HTTP запросов - не много ли?
10 M RPM / 15 К nodes = 800 RPM / node - не мало ли?
Kotofeus
Реклама сама себя не загрузит. И всратые уведомления-рекомендации не сгенерить без этого.