- Home »

Управление конфигурациями 101: написание манифестов Puppet
Если вы хотя бы раз сталкивались с развёртыванием одинаковых конфигураций на десятках серверов, то знаете, что боль — это копипастить конфиги и молиться, чтобы ничего не сломалось. Puppet решает эту проблему элегантно: вы описываете желаемое состояние системы декларативно, а он делает всё остальное. Сегодня разберём, как писать манифесты Puppet правильно — от базовых концепций до продвинутых трюков. Будет много практики, примеров и подводных камней, которые лучше знать заранее.
Как работает Puppet: архитектура и принципы
Puppet работает по принципу “Infrastructure as Code” — вы описываете, что хотите получить, а не как это сделать. Система состоит из двух основных компонентов: Puppet Server (мастер) и Puppet Agent (клиент). Агенты периодически подключаются к серверу, получают каталог ресурсов и приводят систему к желаемому состоянию.
Основные принципы работы:
- Декларативность — вы описываете конечное состояние, а не шаги для его достижения
- Идемпотентность — повторное применение манифеста даёт тот же результат
- Конвергенция — система автоматически стремится к желаемому состоянию
- Каталогизация — манифесты компилируются в каталог ресурсов
Архитектура agent-server позволяет централизованно управлять конфигурациями, но можно использовать и standalone режим для небольших инфраструктур.
Установка и базовая настройка
Начнём с установки Puppet на вашем VPS. Покажу на примере Ubuntu 20.04:
# Добавляем официальный репозиторий Puppet
wget https://apt.puppet.com/puppet7-release-focal.deb
sudo dpkg -i puppet7-release-focal.deb
sudo apt update
# Устанавливаем Puppet Server
sudo apt install puppetserver
# Устанавливаем Puppet Agent
sudo apt install puppet-agent
# Настраиваем память для Puppet Server (по умолчанию 2GB)
sudo vim /etc/default/puppetserver
# Меняем JAVA_ARGS="-Xms2g -Xmx2g" на подходящие значения
# Запускаем сервисы
sudo systemctl enable puppetserver
sudo systemctl start puppetserver
sudo systemctl enable puppet
sudo systemctl start puppet
Базовая настройка puppet.conf:
# /etc/puppetlabs/puppet/puppet.conf
[main]
certname = puppet.example.com
server = puppet.example.com
environment = production
runinterval = 30m
[agent]
report = true
pluginsync = true
Анатомия манифеста: ресурсы, типы и атрибуты
Манифест Puppet — это файл с расширением .pp, содержащий описание ресурсов. Базовый синтаксис выглядит так:
resource_type { 'resource_title':
attribute1 => 'value1',
attribute2 => 'value2',
}
Рассмотрим основные типы ресурсов:
Управление пакетами
# Установка пакета
package { 'nginx':
ensure => present,
}
# Установка конкретной версии
package { 'docker.io':
ensure => '20.10.7-0ubuntu5~20.04.2',
}
# Удаление пакета
package { 'apache2':
ensure => absent,
}
Управление файлами
# Создание файла с содержимым
file { '/etc/nginx/sites-available/mysite':
ensure => file,
content => template('nginx/vhost.erb'),
owner => 'root',
group => 'root',
mode => '0644',
notify => Service['nginx'],
}
# Создание директории
file { '/var/www/mysite':
ensure => directory,
owner => 'www-data',
group => 'www-data',
mode => '0755',
}
# Символическая ссылка
file { '/etc/nginx/sites-enabled/mysite':
ensure => link,
target => '/etc/nginx/sites-available/mysite',
}
Управление сервисами
service { 'nginx':
ensure => running,
enable => true,
require => Package['nginx'],
}
Практические примеры: от простого к сложному
Простой веб-сервер
Создадим манифест для настройки базового веб-сервера:
# /etc/puppetlabs/code/environments/production/manifests/webserver.pp
class webserver {
# Устанавливаем nginx
package { 'nginx':
ensure => present,
}
# Настраиваем конфигурацию
file { '/etc/nginx/sites-available/default':
ensure => file,
content => "
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm;
server_name _;
location / {
try_files \$uri \$uri/ =404;
}
}
",
require => Package['nginx'],
notify => Service['nginx'],
}
# Создаём простую страницу
file { '/var/www/html/index.html':
ensure => file,
content => 'Puppet works!
',
owner => 'www-data',
group => 'www-data',
mode => '0644',
}
# Запускаем сервис
service { 'nginx':
ensure => running,
enable => true,
require => Package['nginx'],
}
}
# Применяем класс
include webserver
Продвинутый пример: LAMP стек
# /etc/puppetlabs/code/environments/production/manifests/lamp.pp
class lamp {
# Устанавливаем пакеты
package { ['apache2', 'mysql-server', 'php', 'php-mysql', 'libapache2-mod-php']:
ensure => present,
}
# Настраиваем Apache
service { 'apache2':
ensure => running,
enable => true,
require => Package['apache2'],
}
# Настраиваем MySQL
service { 'mysql':
ensure => running,
enable => true,
require => Package['mysql-server'],
}
# Включаем mod_rewrite
exec { 'enable_mod_rewrite':
command => '/usr/sbin/a2enmod rewrite',
unless => '/usr/sbin/apache2ctl -M | grep rewrite',
require => Package['apache2'],
notify => Service['apache2'],
}
# Создаём виртуальный хост
file { '/etc/apache2/sites-available/000-default.conf':
ensure => file,
content => template('lamp/vhost.erb'),
require => Package['apache2'],
notify => Service['apache2'],
}
# Создаём PHP info файл
file { '/var/www/html/info.php':
ensure => file,
content => '',
owner => 'www-data',
group => 'www-data',
mode => '0644',
}
}
Продвинутые возможности: классы, модули и шаблоны
Работа с классами
Классы позволяют группировать ресурсы и делать код переиспользуемым:
# Класс с параметрами
class mysql_server (
$root_password = 'changeme',
$port = 3306,
$bind_address = '127.0.0.1',
) {
package { 'mysql-server':
ensure => present,
}
service { 'mysql':
ensure => running,
enable => true,
require => Package['mysql-server'],
}
file { '/etc/mysql/mysql.conf.d/puppet.cnf':
ensure => file,
content => "[mysqld]
bind-address = ${bind_address}
port = ${port}
",
notify => Service['mysql'],
}
# Устанавливаем пароль root
exec { 'set_mysql_root_password':
command => "/usr/bin/mysql -e \"ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '${root_password}';\"",
unless => "/usr/bin/mysql -uroot -p${root_password} -e 'SELECT 1'",
require => Service['mysql'],
}
}
# Использование класса
class { 'mysql_server':
root_password => 'supersecret',
port => 3307,
}
Создание модулей
Модули — это структурированный способ организации кода. Создадим модуль для Nginx:
# Структура модуля
/etc/puppetlabs/code/environments/production/modules/nginx/
├── manifests/
│ ├── init.pp
│ ├── config.pp
│ └── service.pp
├── templates/
│ └── vhost.erb
└── files/
└── nginx.conf
# manifests/init.pp
class nginx (
$package_ensure = 'present',
$service_ensure = 'running',
$service_enable = true,
) {
contain nginx::config
contain nginx::service
Class['nginx::config'] -> Class['nginx::service']
}
# manifests/config.pp
class nginx::config {
package { 'nginx':
ensure => $nginx::package_ensure,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
source => 'puppet:///modules/nginx/nginx.conf',
owner => 'root',
group => 'root',
mode => '0644',
notify => Class['nginx::service'],
}
}
# manifests/service.pp
class nginx::service {
service { 'nginx':
ensure => $nginx::service_ensure,
enable => $nginx::service_enable,
}
}
Шаблоны и условная логика
ERB шаблоны
Шаблоны позволяют генерировать конфигурационные файлы динамически:
# templates/vhost.erb
server {
listen <%= @port %>;
server_name <%= @server_name %>;
root <%= @document_root %>;
index index.html index.htm;
<% if @ssl_enabled %>
ssl on;
ssl_certificate <%= @ssl_cert %>;
ssl_certificate_key <%= @ssl_key %>;
<% end %>
location / {
try_files $uri $uri/ =404;
}
<% @locations.each do |location| %>
location <%= location['path'] %> {
<%= location['content'] %>
}
<% end %>
}
# Использование шаблона
define nginx::vhost (
$port = 80,
$server_name = $name,
$document_root = "/var/www/${name}",
$ssl_enabled = false,
$ssl_cert = undef,
$ssl_key = undef,
$locations = [],
) {
file { "/etc/nginx/sites-available/${name}":
ensure => file,
content => template('nginx/vhost.erb'),
notify => Service['nginx'],
}
file { "/etc/nginx/sites-enabled/${name}":
ensure => link,
target => "/etc/nginx/sites-available/${name}",
notify => Service['nginx'],
}
}
Условная логика и циклы
# Условия
if $facts['os']['family'] == 'RedHat' {
$package_name = 'httpd'
$service_name = 'httpd'
} elsif $facts['os']['family'] == 'Debian' {
$package_name = 'apache2'
$service_name = 'apache2'
} else {
fail("Unsupported OS: ${facts['os']['family']}")
}
# Case конструкция
case $facts['os']['name'] {
'Ubuntu': {
$php_package = 'php'
}
'CentOS', 'RedHat': {
$php_package = 'php-fpm'
}
default: {
fail("Unsupported OS: ${facts['os']['name']}")
}
}
# Циклы
$users = ['alice', 'bob', 'charlie']
$users.each |$user| {
user { $user:
ensure => present,
home => "/home/${user}",
shell => '/bin/bash',
}
}
Факты и внешние данные
Puppet собирает информацию о системе через факты (facts). Можно создавать собственные факты:
# /etc/puppetlabs/code/environments/production/modules/mymodule/lib/facter/custom_fact.rb
Facter.add(:custom_fact) do
setcode do
if File.exist?('/etc/custom_config')
File.read('/etc/custom_config').strip
else
'default_value'
end
end
end
Использование фактов в манифестах:
# Использование встроенных фактов
notify { "This is ${facts['os']['name']} ${facts['os']['release']['full']}": }
# Настройка в зависимости от железа
if $facts['memory']['system']['total_bytes'] > 8589934592 {
$mysql_innodb_buffer_pool_size = '4G'
} else {
$mysql_innodb_buffer_pool_size = '512M'
}
Hiera: управление данными
Hiera позволяет вынести данные из кода в отдельные файлы:
# /etc/puppetlabs/code/environments/production/hiera.yaml
version: 5
defaults:
datadir: data
data_hash: yaml_data
hierarchy:
- name: "Per-node data"
path: "nodes/%{facts.networking.fqdn}.yaml"
- name: "Per-OS data"
path: "os/%{facts.os.name}.yaml"
- name: "Common data"
path: "common.yaml"
# data/common.yaml
---
mysql::root_password: 'changeme'
nginx::worker_processes: 4
users:
- name: 'deploy'
shell: '/bin/bash'
groups: ['sudo', 'docker']
- name: 'monitoring'
shell: '/bin/false'
groups: ['monitoring']
# Использование в манифестах
class myapp {
$mysql_password = lookup('mysql::root_password')
$users = lookup('users')
$users.each |$user| {
user { $user['name']:
ensure => present,
shell => $user['shell'],
groups => $user['groups'],
}
}
}
Обработка ошибок и отладка
Основные подходы к отладке манифестов:
# Проверка синтаксиса
puppet parser validate mymanifest.pp
# Тестовый запуск без применения изменений
puppet apply --noop mymanifest.pp
# Подробный вывод
puppet apply --verbose --debug mymanifest.pp
# Проверка каталога
puppet catalog find mynode.example.com
Обработка ошибок в коде:
# Проверка существования файла
if $facts['path'] {
file { '/usr/local/bin/myscript':
ensure => file,
source => 'puppet:///modules/mymodule/myscript.sh',
mode => '0755',
}
} else {
fail('PATH fact is not available')
}
# Graceful fallback
$service_name = $facts['os']['family'] ? {
'RedHat' => 'httpd',
'Debian' => 'apache2',
default => 'apache2',
}
Сравнение с альтернативами
Критерий | Puppet | Ansible | Chef | SaltStack |
---|---|---|---|---|
Архитектура | Agent-based | Agentless | Agent-based | Agent-based |
Язык конфигурации | Declarative DSL | YAML | Ruby DSL | YAML/Python |
Кривая обучения | Средняя | Простая | Сложная | Средняя |
Производительность | Высокая | Средняя | Высокая | Очень высокая |
Идемпотентность | Встроенная | Встроенная | Встроенная | Встроенная |
Puppet хорош для:
- Больших инфраструктур с тысячами серверов
- Сложных конфигураций с множеством зависимостей
- Строгого соблюдения политик безопасности
- Организаций с выделенными DevOps командами
Интеграция с другими инструментами
Docker и контейнеры
# Установка Docker через Puppet
class docker {
package { ['docker.io', 'docker-compose']:
ensure => present,
}
service { 'docker':
ensure => running,
enable => true,
require => Package['docker.io'],
}
# Создание Docker-compose файла
file { '/opt/myapp/docker-compose.yml':
ensure => file,
content => template('docker/docker-compose.yml.erb'),
notify => Exec['docker-compose-up'],
}
exec { 'docker-compose-up':
command => '/usr/bin/docker-compose up -d',
cwd => '/opt/myapp',
refreshonly => true,
require => [Package['docker-compose'], Service['docker']],
}
}
Мониторинг и логирование
# Интеграция с Prometheus
class monitoring {
# Установка Node Exporter
package { 'prometheus-node-exporter':
ensure => present,
}
service { 'prometheus-node-exporter':
ensure => running,
enable => true,
require => Package['prometheus-node-exporter'],
}
# Конфигурация для отправки логов в ELK
file { '/etc/filebeat/filebeat.yml':
ensure => file,
content => template('monitoring/filebeat.yml.erb'),
notify => Service['filebeat'],
}
}
Автоматизация и CI/CD
Puppet отлично интегрируется с системами CI/CD:
# .gitlab-ci.yml
stages:
- validate
- test
- deploy
validate_puppet:
stage: validate
script:
- puppet parser validate manifests/*.pp
- puppet-lint manifests/
test_puppet:
stage: test
script:
- rspec spec/
deploy_puppet:
stage: deploy
script:
- puppet apply --environment=production manifests/site.pp
only:
- master
Интересные факты и возможности
Несколько неочевидных возможностей Puppet:
- Puppet Bolt — позволяет выполнять ad-hoc команды без агентов
- Puppet DB — хранит факты и отчёты для аналитики
- Автоматическое восстановление — Puppet может автоматически исправлять drift конфигурации
- Exported resources — ресурсы могут экспортироваться с одного узла и импортироваться на другой
- Puppet Enterprise — коммерческая версия с GUI и дополнительными возможностями
# Exported resources пример
# На веб-сервере
@@firewall { "100 allow http from ${ipaddress}":
proto => 'tcp',
port => 80,
action => 'accept',
tag => 'webserver',
}
# На балансировщике
Firewall <<| tag == 'webserver' |>>
Оптимизация производительности
Для больших инфраструктур важно оптимизировать работу Puppet:
# puppet.conf оптимизация
[main]
# Увеличиваем количество worker процессов
max_requests_per_instance = 1000
# Настраиваем кеширование
catalog_cache_terminus = json
facts_cache_terminus = json
# Оптимизируем сеть
http_connect_timeout = 10s
http_read_timeout = 60s
[agent]
# Увеличиваем интервал запуска для production
runinterval = 30m
splay = true
splaylimit = 600
Мониторинг производительности:
# Анализ времени выполнения
puppet apply --evaltrace --summarize mymanifest.pp
# Профилирование каталога
puppet catalog compile --render-as json mynode | \
jq '.resources[] | select(.parameters.ensure == "present") | .title'
Безопасность и лучшие практики
Важные аспекты безопасности при работе с Puppet:
- Управление сертификатами — регулярно обновляйте SSL сертификаты
- Разделение сред — используйте отдельные environments для dev/staging/prod
- Секреты — не храните пароли в открытом виде в манифестах
- Контроль доступа — настройте RBAC для Puppet Enterprise
# Работа с секретами через Hiera-eyaml
# Установка
gem install hiera-eyaml
# Генерация ключей
eyaml createkeys
# Шифрование значения
eyaml encrypt -s 'supersecret'
# В hiera файле
mysql::root_password: >
ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxgg...]
Заключение и рекомендации
Puppet — мощный инструмент для управления конфигурациями, который особенно хорош для больших инфраструктур. Его декларативный подход и строгая типизация делают конфигурации предсказуемыми и надёжными.
Когда использовать Puppet:
- У вас более 50 серверов для управления
- Нужна строгая политика соответствия конфигураций
- Требуется централизованное управление с детальной отчётностью
- Есть команда DevOps, готовая вложиться в изучение DSL
Когда лучше выбрать альтернативы:
- Малая инфраструктура (< 20 серверов) — рассмотрите Ansible
- Нужна простота и скорость внедрения — Ansible
- Работаете в основном с контейнерами — Docker Compose + Kubernetes
- Нужна максимальная производительность — SaltStack
Для начала работы рекомендую развернуть Puppet на тестовом VPS, написать простые манифесты для управления пакетами и файлами, а затем постепенно переходить к более сложным конфигурациям с классами и модулями.
Если планируете серьёзно заниматься управлением конфигурациями, стоит изучить не только Puppet, но и смежные инструменты — Terraform для инфраструктуры, GitLab CI для автоматизации деплоя, и системы мониторинга для контроля состояния серверов. Современная инфраструктура — это экосистема инструментов, и Puppet может стать её важной частью.
Помните: Infrastructure as Code — это не просто инструмент, это философия. Относитесь к инфраструктуре как к коду: используйте версионирование, code review, тестирование и документацию. Puppet поможет вам воплотить эту философию в жизнь.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.