Виртуальные ресурсы в Puppet

в 15:07, , рубрики: puppet, puppetdb, системное администрирование

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

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

Имеется сервер с установленным 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 бэкендов.

Лучше использовать существующие модули, но для демонстрации принципа данный пример подойдет.
В очень упрощенном виде добавлять бэкенды можно примерно следующим образом, определяя бэкенд:

define nginx::backend($connectStr) {
  @@concat::fragment { $title:
    content                                     => "server $connectStr;",
    target                                      => '/etc/nginx/conf.d/backend.conf'
  }
}

И фронтенд:

class nginx::frontend {
  concat { '/etc/nginx/conf.d/backend.conf' :
    ensure => present,
    ensure_newline                => true
  }
  Concat::Fragment <|| target == '/etc/nginx/conf.d/backend.conf' ||>
}

Теперь каждый бэкенд будет добавлять nginx::backend:

  nginx::backend { ${::fqdn}_backend" :
    connectStr => '127.0.0.1:8080'
  }

Экспортируя таким образом данные о себе в PuppetDB, а фронтэнд подключая класс nginx::frontend собирает их, складывает и складывает в файл backend.conf.

Более подробную информацию о синтаксисе и паттернах использования можно найти по следующим ссылкам:

Автор: Glueon

Источник

Поделиться

* - обязательные к заполнению поля