Home » Управление конфигурациями 101: написание манифестов Puppet
Управление конфигурациями 101: написание манифестов Puppet

Управление конфигурациями 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 поможет вам воплотить эту философию в жизнь.


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

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked