Виртуальные ресурсы в Puppet
от Glueon
Как мне кажется, основной смысл виртуальных ресурсов становится более понятен уже на конкретных примерах с экспортируемыми ресурсами — когда виртуальные ресурсы помещаются в базу и используется для обмена информацией между агентами, но чтобы понять рекурсию, нужно понять рекурсию, поэтому начнем с локального применения. На примере.
Пример будет немного синтетическим. Мне было сложно придумать достаточно короткий пример, при этом демонстрирующий смысл виртуальных ресурсов. На практике такие примеры с вшитыми именами пользователей встречаются редко. По крайней мере должны.
Имеется сервер с установленным Apache. Установка и настройка производится удобно и модно puppet-классом apache. Для простоты все будем хранить в основном манифесте site.pp. Все появляющиеся проблемы в ходе развития примера актуальны и в случае разнесения кусков логики по модулям.
Допустим, классу необходим unix-пользователь, в данном примере webUser, домашний каталог которого будет являться document root'ом для веба. Тогда получим следующий скелет site.pp:
Все просто. Теперь мы решили добавить в нашу инфраструктуру nginx неважно для каких целей. Главное, что ему тоже нужен пользователь webUser для отдачи контента. Добавляем класс:
Запускаем:
По понятным причинам оно не работает. Получается, что в одной области видимости у нас два ресурса с одинаковым значением namevar. Решить проблему можно, например, вынеся ресурс пользователя в отдельный класс:
Запускаем — работает:
Предположим, что нам понадобилось добавить нового пользователя cacheUser, в папке которого мы будем хранить какой-либо кэш. Этим кэшем пользуется как Apache, так и nginx, поэтому мы добавляем соответствующего пользователя в класс users:
Далее мы решили добавить php5-fpm и uwsgi, которым нужен webUser, но не нужен cacheUser. В такой ситуации придется выделять cacheUser в отдельный класс, чтобы подключать его отдельно только в классах apache и nginx. Это неудобно. К тому же нет гарантий, что чуть позже не придется выделить еще одного пользователя в отдельный класс. Тут-то на помощь и приходят виртуальные ресурсы.
Если к определению ресурса добавить знак @:
Ресурс будет считаться виртуальным. Такой ресурс не будет добавляться в каталог агента до тех пор, пока мы явно не определим. Из документации:
A virtual resource declaration specifies a desired state for a resource without adding it to the catalog
Поэтому если исполнить код ниже даже при отсутствии в системе пользователей webUser и cacheUser они добавлены не будут:
Проверяем:
Пользователи, как и ожидалось, не добавились.
Но следует быть внимательным. Несмотря на то, что виртуальный ресурс не добавляется в каталог, это не значит, что следующий код будет работать:
Он по-прежнему будет выдавать ошибку компиляции. Это происходит потому, что сначала парсер puppet итерируется по всем ресурсам, добавляя в каталог даже витруальные. На этом этапе этапе и возникает ошибка из-за дублирования имен. Следующий этап — обработка реализации виртуальных типов: коллектор ищет в каталоге места, в которых виртуальные ресурсы определяются и найденные помечает как не виртуальные. И лишь в самом конце происходит очиска каталога от виртуальных ресурсов, которые не были бы реализованы.
Для определения ресурса используется либо spaceship оператор < | | > либо с помощью функция realize. Перепишем наш манифест с использованием как одного так и другого синтаксиса:
В функцию realize можно передавать сразу несколько ресурсов, а в операторе <| |> можно указывать несколько условий, по которым делается поиск ресурсов для определения.
Помимо синтаксической разница в realize и <| |> имеются отличия и в поведение. Если ресурс с указанным названием не существует realize выдаст ошибку:
Оператор <| |> в таком случае ошибку не выдает, потому что он является своего рода надстройкой над функцией realize. Ко всем найденым ресурсам по заданному в его теле поисковому запросу применяется функция realize. Соотвественно, если не нашлось ресурса по заданным критериям ошибки не возникает, так как не вызывается функция realize.
Кстати, у оператора <| |> есть еще два достаточно хороших применения. Его можно использовать для переопределения состояния ресурса в классе. Например:
Исключит файл /etc/apache2.conf для ноды s2.example.com.
Также его можно использовать с операторами ~> и ->. Таким образом, мы можем уведомить все сервисы о каких-либо изменениях, либо потребовать перед установкой любого пакета добавить все yum репозитории:
Как мне кажется, основным преимуществом виртуальных ресурсов является то, что их можно экспортировать и делать доступными для других агентов. Чтобы экспортировать виртуальный ресурс необходим добавить еще один знак @ перед его описанием.
Классический пример из документации Puppet:
В данном примере мы определили виртуальный ресурс sshkey. Оператор-коллектор <<| |>> содержит пустое тело, поэтому выгружает все экспортированные объекты класса Sshkey. Таким образом, любой агент, в манифесте которого подключается класс ssh, экспортирует свой публичный ключ (@@sshkey), а затем импортирует к себе все ключи, добавленные другими агентами (Sshkey <<| |>>).
Экспортируемые ресурсы хранятся в PuppetDB — БД от PuppetLabs. После подключение PuppetDB каждый скопилированный pupet master'ом каталог кладется в базу PuppetDB, которая в свою очередь предоставляет поисковый интерфейс для поиска по каталогам.
Указывая @@, мы помечаем ресурс как экспортируемый и информируем puppet, что ресурс необходимо добавить в каталог и поставить ему метку exported. Когда puppet master видит оператор <<| |>>, он делает поисковый запрос к PuppetDB и добавляет все найденные экспортированные ресурсы, подходящие под критерий поиска.
Важно, что экспортированные ресурсы находятся в глобальной области видимости, поэтому их названия должны быть уникальными.
У этого функционала огромный потенциал и мне достаточно часто приходится им пользоваться. Автоматизация добавления серверов в мониторинг или nginx бэкендов.
Лучше использовать существующие модули, но для демонстрации принципа данный пример подойдет:
Более подробную информацию о синтаксисе и паттернах использования можно найти по следующим ссылкам:
docs.puppetlabs.com/puppet/latest/reference/lang_virtual.html
docs.puppetlabs.com/puppet/latest/reference/lang_exported.html
docs.puppetlabs.com/puppet/2.7/reference/lang_collectors.html
Пример будет немного синтетическим. Мне было сложно придумать достаточно короткий пример, при этом демонстрирующий смысл виртуальных ресурсов. На практике такие примеры с вшитыми именами пользователей встречаются редко. По крайней мере должны.
Имеется сервер с установленным Apache. Установка и настройка производится удобно и модно puppet-классом apache. Для простоты все будем хранить в основном манифесте site.pp. Все появляющиеся проблемы в ходе развития примера актуальны и в случае разнесения кусков логики по модулям.
Допустим, классу необходим unix-пользователь, в данном примере webUser, домашний каталог которого будет являться document root'ом для веба. Тогда получим следующий скелет site.pp:
- class apache {
- user { 'webUser' : ensure => present }
- ...
- }
- node default {
- include apache
- }
Все просто. Теперь мы решили добавить в нашу инфраструктуру nginx неважно для каких целей. Главное, что ему тоже нужен пользователь webUser для отдачи контента. Добавляем класс:
- class apache {
- user { 'webUser' : ensure => present }
- }
- class nginx {
- user { 'webUser' : ensure => present }
- }
- node default {
- include apache
- include nginx
- }
Запускаем:
- root@puppet:/vagrant# puppet apply ./site.pp --noop
- Error: Duplicate declaration: User[webUser] is already declared in file /vagrant/site.pp:17; cannot redeclare at /vagrant/site.pp:11 on node puppet.example.com
- Error: Duplicate declaration: User[webUser] is already declared in file /vagrant/site.pp:17; cannot redeclare at /vagrant/site.pp:11 on node puppet.example.com
По понятным причинам оно не работает. Получается, что в одной области видимости у нас два ресурса с одинаковым значением namevar. Решить проблему можно, например, вынеся ресурс пользователя в отдельный класс:
- class users {
- user { 'webUser' : ensure => present }
- }
- class nginx { include users }
- class apache { include users }
- node default {
- include apache
- include nginx
- }
Запускаем — работает:
- root@puppet:/vagrant# puppet apply ./site.pp --noop
- Notice: Compiled catalog for puppet.example.com in environment production in 0.07 seconds
- Notice: /Stage[main]/users/User[webUser]/ensure: current_value absent, should be present (noop)
- Notice: Class[users]: Would have triggered 'refresh' from 1 events
- Notice: Stage[main]: Would have triggered 'refresh' from 1 events
- Notice: Finished catalog run in 0.02 seconds
Предположим, что нам понадобилось добавить нового пользователя cacheUser, в папке которого мы будем хранить какой-либо кэш. Этим кэшем пользуется как Apache, так и nginx, поэтому мы добавляем соответствующего пользователя в класс users:
- class users {
- user { 'webUser': ensure => present }
- user { 'cacheUser': ensure => present }
- }
Далее мы решили добавить php5-fpm и uwsgi, которым нужен webUser, но не нужен cacheUser. В такой ситуации придется выделять cacheUser в отдельный класс, чтобы подключать его отдельно только в классах apache и nginx. Это неудобно. К тому же нет гарантий, что чуть позже не придется выделить еще одного пользователя в отдельный класс. Тут-то на помощь и приходят виртуальные ресурсы.
Если к определению ресурса добавить знак @:
- @user { 'webUser': ensure => present }
Ресурс будет считаться виртуальным. Такой ресурс не будет добавляться в каталог агента до тех пор, пока мы явно не определим. Из документации:
A virtual resource declaration specifies a desired state for a resource without adding it to the catalog
Поэтому если исполнить код ниже даже при отсутствии в системе пользователей webUser и cacheUser они добавлены не будут:
- class users {
- @user { 'webUser': ensure => present }
- @user { 'cacheUser': ensure => present }
- }
- class nginx { include users }
- class apache { include users }
- node default {
- include apache
- include nginx
- }
Проверяем:
- root@puppet:/vagrant# puppet apply ./site.pp
- Notice: Compiled catalog for puppet.example.com in environment production in 0.07 seconds
- Notice: Finished catalog run in 0.02 seconds
Пользователи, как и ожидалось, не добавились.
Но следует быть внимательным. Несмотря на то, что виртуальный ресурс не добавляется в каталог, это не значит, что следующий код будет работать:
- class apache {
- @user { 'webUser' : ensure => present }
- }
- class nginx {
- @user { 'webUser' : ensure => present }
- }
- node default {
- include apache
- include nginx
- }
Он по-прежнему будет выдавать ошибку компиляции. Это происходит потому, что сначала парсер puppet итерируется по всем ресурсам, добавляя в каталог даже витруальные. На этом этапе этапе и возникает ошибка из-за дублирования имен. Следующий этап — обработка реализации виртуальных типов: коллектор ищет в каталоге места, в которых виртуальные ресурсы определяются и найденные помечает как не виртуальные. И лишь в самом конце происходит очиска каталога от виртуальных ресурсов, которые не были бы реализованы.
Для определения ресурса используется либо spaceship оператор < | | > либо с помощью функция realize. Перепишем наш манифест с использованием как одного так и другого синтаксиса:
- class users {
- @user { 'webUser': ensure => present }
- @user { 'cacheUser': ensure => present }
- }
- class nginx {
- include users
- realize User['webUser'], User['cacheUser']
- }
- class apache {
- include users
- User <| title == 'webUser' or title == 'cacheUser' |>
- }
- node default {
- include apache
- include nginx
- }
В функцию realize можно передавать сразу несколько ресурсов, а в операторе <| |> можно указывать несколько условий, по которым делается поиск ресурсов для определения.
Помимо синтаксической разница в realize и <| |> имеются отличия и в поведение. Если ресурс с указанным названием не существует realize выдаст ошибку:
- Error: Failed to realize virtual resources User[nonExistingUser] on node puppet.example.com
Оператор <| |> в таком случае ошибку не выдает, потому что он является своего рода надстройкой над функцией realize. Ко всем найденым ресурсам по заданному в его теле поисковому запросу применяется функция realize. Соотвественно, если не нашлось ресурса по заданным критериям ошибки не возникает, так как не вызывается функция realize.
Кстати, у оператора <| |> есть еще два достаточно хороших применения. Его можно использовать для переопределения состояния ресурса в классе. Например:
- class configurations
- {
- file { '/etc/nginx.conf' : ensure => present }
- file { '/etc/apache2.conf' : ensure => present }
- }
- node s1.example.com {
- include configurations
- }
- node s2.example.com {
- include configurations
- File <| title == '/etc/apache2.conf' |> { ensure => absent }
- }
Исключит файл /etc/apache2.conf для ноды s2.example.com.
Также его можно использовать с операторами ~> и ->. Таким образом, мы можем уведомить все сервисы о каких-либо изменениях, либо потребовать перед установкой любого пакета добавить все yum репозитории:
- Yumrepo <| |> -> Package <| |>
Как мне кажется, основным преимуществом виртуальных ресурсов является то, что их можно экспортировать и делать доступными для других агентов. Чтобы экспортировать виртуальный ресурс необходим добавить еще один знак @ перед его описанием.
Классический пример из документации Puppet:
- class ssh {
- # Declare:
- @@sshkey { $hostname:
- type => dsa,
- key => $sshdsakey,
- }
- # Collect:
- Sshkey <<| |>>
- }
В данном примере мы определили виртуальный ресурс sshkey. Оператор-коллектор <<| |>> содержит пустое тело, поэтому выгружает все экспортированные объекты класса Sshkey. Таким образом, любой агент, в манифесте которого подключается класс ssh, экспортирует свой публичный ключ (@@sshkey), а затем импортирует к себе все ключи, добавленные другими агентами (Sshkey <<| |>>).
Экспортируемые ресурсы хранятся в PuppetDB — БД от PuppetLabs. После подключение PuppetDB каждый скопилированный pupet master'ом каталог кладется в базу PuppetDB, которая в свою очередь предоставляет поисковый интерфейс для поиска по каталогам.
Указывая @@, мы помечаем ресурс как экспортируемый и информируем puppet, что ресурс необходимо добавить в каталог и поставить ему метку exported. Когда puppet master видит оператор <<| |>>, он делает поисковый запрос к PuppetDB и добавляет все найденные экспортированные ресурсы, подходящие под критерий поиска.
Важно, что экспортированные ресурсы находятся в глобальной области видимости, поэтому их названия должны быть уникальными.
У этого функционала огромный потенциал и мне достаточно часто приходится им пользоваться. Автоматизация добавления серверов в мониторинг или nginx бэкендов.
Лучше использовать существующие модули, но для демонстрации принципа данный пример подойдет:
- #Класс описывающий бэкенд и экспортирующий в базу строку вида "server IP:PORT;" которая будет затем добавлен в блок upstream в nginx
- class nginx::backend($ip = $::ipaddress, $port = 8080) {
- @@concat::fragment { "$::fqdn" :
- content => "server $ip:$port;",
- tag => 'nginx-backend',
- target => '/etc/nginx/conf.d/backend.conf'
- }
- }
- #Класс описывающий фронтенд, в котором объявлется ресурс concat, который далее склеивает все фрагменты экспортированные в nginx::backend
- class nginx::frontend {
- concat { '/etc/nginx/backend.conf' :
- ensure => present,
- force => true,
- ensure_newline => true
- } ~> Class['::nginx::service']
- concat::fragment { 'upstream_header':
- content => 'upstream backend { ',
- order => '01',
- target => '/etc/nginx/backend.conf',
- }
- concat::fragment { 'upstream_footer' :
- content => '}',
- order => '03',
- target => '/etc/nginx/backend.conf'
- }
- #Импортируем все фрагменты
- Concat::Fragment <<| tag == 'nginx-backend' |>> { target => '/etc/nginx/backend.conf', order => '02' }
- }
- class nginx::install {
- package { 'nginx' : ensure => present }
- }
- class nginx::service {
- service { 'nginx' : ensure => running, require => Class['nginx::install'] }
- }
- class nginx {
- class { 'nginx::install' : } -> class { 'nginx::service': }
- }
- node 'back1.example.com' {
- class { 'nginx' : }
- class { 'nginx::backend' : port => 8083 }
- }
- node 'back2.example.com' {
- class { 'nginx' : }
- class { 'nginx::backend' : port => 8084 }
- }
- node 'front1.example.com' {
- class { 'nginx' : }
- class { 'nginx:::frontend' : }
- }
Более подробную информацию о синтаксисе и паттернах использования можно найти по следующим ссылкам:
docs.puppetlabs.com/puppet/latest/reference/lang_virtual.html
docs.puppetlabs.com/puppet/latest/reference/lang_exported.html
docs.puppetlabs.com/puppet/2.7/reference/lang_collectors.html