Пользователь
0,0
рейтинг
31 октября 2014 в 18:07

Администрирование → Виртуальные ресурсы в Puppet из песочницы

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

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

Имеется сервер с установленным 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' : }
}



Более подробную информацию о синтаксисе и паттернах использования можно найти по следующим ссылкам:
@Glueon
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (46)

  • 0
    Практика показала, что от паппета лучше держаться подальше. Костыли в костылях на костылях с костылями и статья тому очередное подтверждение. Один только синтаксис повергает в шок.
    • 0
      Где синтаксис гораздо лучше ruby-подобного в puppet?
      • 0
        Посмотрите на анлибл, реально за ему подобными системами будет будущее и надеюсь они вытеснят почти везде паппеты и чефы, сказать честно с проблемами паппета, когда надо мониторить каждую мелочь совсем надоели.
        • 0
          Прочитав соседнюю статью про AWS и Ansible пришел в небольшой шок, так как по-моему такой подход ужасен.
          Правда, когда я пробовал Ansible на простых задачах он мне понравился. В том числе понравилась agentless использование, когда можно перед сдачей сервера клиенту сконфигурировать его по ssh без лишних телодвижений.
          Но есть такое ощущение, что он прост и удобен до тех пор, пока задача не становится более-менее сложной. Пока опыта работы с ним мало, но от других админов слышал примерно такие выводы.
          • 0
            Сложные задачи и на паппете то решаются не очень удобно и просто, пока что можно сказать почти все можно переписать на ансибл и при это стабильность его в в разы больше и понятнее.
            • 0
              Мне сложно спорить, так как я пока не знаю ansible. Я, например, не совсем понял можно ли в ansible писать свои факты и модифицировать плагинами работу существующих модулей.
              • 0
                Насколько мое знание папета (сейчас работаю с ансиблом) позволяет понять ваш вопрос — факты можно задавать какие угодно, для этого нужно положить их описание на локальный хост (адрес папки можно посмотреть в документации) Но по большому счету это не требуется — все данные указываются или в файле с хостами, или в файлах переменных или в фактах с хоста (а их там мнооого)
                Плагинов там нет, можите переписывать модули или писать свои на питоне.
                • 0
                  Просто, к примеру, у одного заказчика парк серверов и начиная с какого-то момента часть из них ушла под определенный проект, в котором есть БД со списком этих серверов и у каждого сервера свой ID. Т.е. на хостнеймы я опираться не могу.
                  В зависимости от ID я должен предпринимать какие-то определенные телодвижение в puppet. Я просто добавил дополнительные факт someConcreteID, который мне присылает сам сервер. Выплевывает его PHP код проекта, мне даже не интересно как.
                  На Ansible как я понимаю я должен писать на Python-е какой-то код с urllib, который через GET запрос стучится в БД проекта, получает ID и начинает какие-то телодвижения. Т.е. решение есть но оно как мне кажется менее удобное.
                  • 0
                    Я бы сделал так на ансибле:
                    В роли делается задание для получение ИД запросом в базу, парсится и регистрируется как переменная для хоста, тот же самый someConcreteID. Потом можно уже что-то делать с сервером в зависимости от этой переменной.
                    В конце концов никто не мешает вам опираться не на хост нэймы, а на ИП адреса :) Получая хост нэйм прямо с сервера через тот же факт
                    Вообще, ансибл настроен на работу с конкретным списком хостов, разделенным по группам. Т.е. если среда постоянно меняется (утром был веб сервер, потом апп, потом бд) — то тут действительно лучше паппет.
                    • 0
                      В моем случае я просто попросил разработчиков создать env переменную FACTER_serverID. Так как у нее префикс «FACTER_» она автоматически появляется у меня в puppet и я могу с нею работать $::serverID.
                      Еще делать описание самих нод я могу с помощью любого другого внешего скрипта. Поэтому я могу у себя в каком-нибудь биллинге прописать, что такой-то VPS надо поставить LAMP, puppet при подключении VPS запустит мой скрипт и я на лету сгенерирую ему описание ноды с включенным в нее mysql, apache и php. В ansible по-моему такого нет.
                      • 0
                        Можно делать списки хостов с помощью этих внешних скриптов, дробить сервера на группы и накатывать туда все, что угодно. По крону :)
                        Я опасаюсь не контролировать установку софта или конфигов на продакшене, поэтому я веду списки хостов для ансибла сам. Появился хост, я знаю что туда нужно поставить, я включаю его в группу, запускаю плейбук и сижу, смотрю как туда ставится мусукль, пых, заббикс агент и прочее-прочее.

                        Я ни в коем случае не агитирую все работающее бросать и переходить на ансибл, просто пытался ответить на некоторые ваши вопросы в меру своих знаний и опыта :)
                        • 0
                          Нет, важно приводить примеры того, как это работает с другими погремушками. По крайней мере у меня теперь есть информация для размышления.
                          Спасибо.
            • 0
              ok, тогда назови парочку крупных игроков, которые отказались от папета и перешли на ансибл?
              • 0
                Да никто не будет переходить если подсел уже, я вот один проект перевожу с шефа по причинам того что шеф не работает нормально в новом руби просто напросто.
                Не нужно смотреть на другие проекты нужно смотреть на паппет и ансибл.
  • 0
    Salt
    • 0
      На вкус и цвет… SaltStack-у всего 3 года, Puppet-у 9 лет. Поэтому он может быть и хорош, но в продакшене еще не так активно обкатан. Из молодых альтернатив пробовал Ansible — он хорош.
      • 0
        Ансибл хороший. использую его. Тоже есть костыли, тоже есть велосипеды в нем. Никто этого не лишен
  • 0
    Проблема Паппета разве что в отсутствии «лучших практик». То есть, они, вроде, сформулированы и кто-то им следует, но по Puppet Forge это не очень заметно.

    И получается, что несмотря на то, что создатели пропагандируют повторное использование кода, почти всегда его приходится исправлять под свои нужды или писать с нуля. Имеются в виду модули.

    Синтаксис — дело вкуса. Мне вот YAML не кажется более читаемым. А уж как знатно выглядят императивные костыли в декларативном Энсибл. В этом он еще Паппету фору даст.

    Спасибо за статью. Мне кажется тут есть решение одной моей актуальной задачи.
    • 0
      Мне кажется это скорее проблема отдельных писателей модулей нежели самого puppet :)
      На сайте puppetlabs есть рекомендации по оформлению модулей, если замечательные книжки bitfieldconsulting.com/cookbook и www.apress.com/9781430260400 с примерами того как писать хорошо. Просто не все этим, к сожалению, пользуются.
      • 0
        Да, я об этом и говорю. А когда ищешь модуль для какого-то не самого популярного ПО, как правило, на образцы не следования практикам и попадаешь :)
      • 0
        Кстати, на последней puppetconf обращали на эту проблему внимания и теперь появилась категория «Puppet Approved», в которой модули проверены и они написаны качественно.
  • 0
    *miss*
  • 0
    Puppet несколько лет не могут научить полноценно работать с UTF-8 (!) В итоге русские комментарии в конфигах и даже манифестах периодически приводят к падению всей инфраструктуры (и не надо мне говорить, что всё надо писать на английском, это бред сивой кобылы, ибо UTF-8. Тем более как вообще может что-то падать из-за комментариев???).

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

    Плюс синтаксис манифестов. Это отдельный разговор. Вермишель из непойми чего. Тут нужны кавычки, там нет. Тут нужна запятая, а там не нужна. Хуже perl. Каждый пишет манифесты как хочет, в итоге читать очень тяжело.

    Итого: puppet — классная штука. Но только если её почистить от тех тонн фекалий, которые зачем-то (я реально не понимаю зачем) в неё привнесли. Архитектурно система отличная, но те же виртуальные ресурсы… Такое впечатление что проектировал хороший архитектор, а виртуальные ресурсы придумал школьник-практикант. Я написал много тысяч строк кода манифестов, но до сих пор не понимаю, почему не избавить Puppet от этих адских костылей и очевидных критических багов (вроде поддержки UTF-8).
    • 0
      Если у вас будут одинаковые имена в пределах одной видимости вы моложете в итоге прийти к коду:
      user { 'bob': ensure => present }
      user { 'bob': ensure => absent }
      

      Сами понимаете, что в случае декларативного языка такое работать в принципе не может. Какую с этих строчек он должен игнорировать?
      Приведите пример где у вас в пределах одного scope два пользователя с одинаковыми именами и абсолютно понятно какой из участков кода можно игнорировать?

      Насчет комментариев — пока не наткнулся. Я не использую комментарии на русском, но специально попробовал и проблемы не обнаружил. Может тоже есть пример?

      А насчет синтаксиса — мне кажется вам просто не нравится ruby :)
      • 0
        В этом случае он должен ругаться и падать. НО! Только в этом. Если один и тот же пользователь с ОДИНАКОВЫМИ параметрами деклариется хоть стопятьсот раз, то это не должно смущать puppet. Т.е. если объект уже есть в каталоге и его параметры в точности совпадают с праметрами объекта, который пытаются добавить — то накой падать-то? В чём вообще смысл такого идиотического поведения? Зачем на каждый чих создавать свои классы или, что в разы хуже, виртуальные ресурсы? Когда можно просто разрешить многократное определение одной и той же сущности, в чём, сосбвенно, проблема такого подхода? Никто её не знает, никто её не видит, но puppet так не умеет. Puppet умеет костыль, называемый виртуальными ресурсами. А Ruby… Дело в том, что как минимум всё, что является строковым литералом, обязано заключаться в кавычки. Иначе становится очень сложно отличать всякие конструкции языка от параметров. Puppet даже этому простому, тривиальному и очень нужному правилу не следует.
        • 0
          В чём вообще смысл такого идиотического поведения

          Главное правило паппета — если есть хоть одна возможность повредить, то нужно падать на этапе компиляции. Это правильное поведение, так как при разрешенных дубликатах легко получить неправильные ресурсы (а тестов, как я понял, у вас нету).
    • 0
      Плюс синтаксис манифестов. Это отдельный разговор. Вермишель из непойми чего. Тут нужны кавычки, там нет. Тут нужна запятая, а там не нужна. Хуже perl. Каждый пишет манифесты как хочет, в итоге читать очень тяжело.

      Прокомментируй про кавычки. Я не понял, в чём проблема — их можно ставить везде (или просто сходу не могу вспомнить, где они не нужны)
      • 0
        Видимо человека смущает то, что Puppet позволяет писать как absent, так и 'absent' и т.д. :)
        Но для этого есть puppet style guide где просто четко сказано КАК писать. Есть puppet-lint, который можно прикрутить к vim через syntastic и получать это все в режиме реального времени.
        Тоже не понимаю где проблема в общем.
  • 0
    Я в вашем примере одного не понял: зачем вводить виртуальные ресурсы?
    if ( ! defined(User['webUser']) ) {  user { 'webUser': ensure   => present } }
    

    Главная идея в том, что пользователи во всех классах должны создаваться (и определяться) одинаково. И именно это главное преимущество виртуальных ресурсов, перед конструкцией приведенной выше. В случае виртуальных ресурсов он определяется один раз и вызывается из неограниченного числа мест.

    Но можно обойтись и без виртуальных ресурсов: один пользователь = 1 класс (ну или подкласс в users). И делать include каждого такого класса-пользователя. Эффект будет точно таким же.
    • 0
      Я в вашем примере одного не понял: зачем вводить виртуальные ресурсы?

      В самом первом примере можно выкрутиться одним if-ом. Но данное решение совсем не scalable и в последующих примерах одним if-ом уже не отделаться.

      Задачу можно решить без виртуальных ресурсов, в том числе и:
      И делать include каждого такого класса-пользователя. Эффект будет точно таким же.

      И эффект будет таким же, но сравните код:
      class myUser {
          user { 'user1' : ensure => present }
      }
      class myUser2 {
          user { 'user2' : ensure => present }
      }
      class myUser3 {
          user { 'user3' : ensure => present }
      }
      class myUser4 {
          user { 'user4' : ensure => present }
      }
      
      node 'supernode.example.com' {
         include myUser4
         include myUser2
      

      И вариант с виртуальными ресурсами:
      class virtual::users {
          @user { 'user1' : ensure => present }
          @user { 'user2' : ensure => present }
          @user { 'user3' : ensure => present }
          @user { 'user4' : ensure => present }
      }
      node 'supernode.example.com' {
         include virtual::users
         #Подключаем только определенных пользователей
         realize User['user2'], User['user4']
         #Вместо 5 include-ов можем сделать так
         User <| |>
      
    • 0
      Не нужно такие примеры писать — кто-то может и повторить.

      Так появляются повторы описания ресурсов и часто приводит к дублированию кода и возникновению логических проблем (например, когда через некоторое время поправили в одном месте, а в другом забыли).

      один пользователь = 1 класс

      Это плохой вариант проектирования. Так тоже не нужно делать.
      • 0
        Именно про это я в своем комментарии и написал.

        А мой пример с «1 пользователь = 1 класс» начинает прекрасно работать в тот момент, когда кроме создания пользователя нужно делать что-то еще, специфичное для пользователя. С виртуальными ресурсами, на сколько я понимаю, такой фокус не сработает.
        • 0
          Если приведете пример кода смогу прикинуть как это работает с виртуальными ресурсами. Пока не совсем понимаю какой фокус и почему не сработает.
          • 0
            Допустим, нужно создать пользователя и положить ему bashrc, ssh ключи или сделать владельцем каталогов. В общем любая операция, связанная с пользователем, которая должна быть выполнена и без которой пользователя создавать не следует.
            • 0
              У вас задача сделать так для одного пользователя? Как-то это выглядит сферическим конём в вакууме. Обычно есть типовые пользователи, которым нужно что-то сделать. Поэтому получается что есть параметризированный класс/дефайн, которому, например, требуется передать имя пользователя.

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

                А оригинально дискуссия была о том, что виртуальные ресурсы не дают дополнительной пользы: все что они делают можно сделать и без них.
            • 0
              class virtual::users {
                @user { 'bob' :  ensure => present }
                @user { 'john' : ensure => present }
              }
              
              class someAction {
                file { '/home/bob/.bashrc' :
                  ensure => present,
                  require => User['bob']
                 }
              }
              
              class anotherAction {
                file { '/home/john/.bashrc' :
                  ensure => present,
                  require => User['john']
                }
              }
              
              node 'testnode.example.com' {
                include virtual::users
                include someAction, anotherAction
                realize User['john'], User['bob']
              }
              

              Если ресурс не определить через realize/spaceship получите ошибку
              Invalid relationship… because User['john'] doesn't seem to be in the catalog.
              • 0
                Да, а теперь вопрос: зачем тут виртуальные ресурсы? Почему бы пользователей не создавать в классах с действиями, например вот так:
                define create_users ($users) {
                  $users.each |$user| {
                    include "users::$user"
                  }
                }
                
                class users {
                  User { ensure => present }
                }
                class users::user1 inherits users {
                  user{'user1':}
                }
                class users::user2 inherits users {
                  user{'user2':} ->
                  file{'/mnt/user2':
                    ensure => 'directory',
                    owner => 'user2',
                  }
                
                }
                
                class apache {
                  create_users{"$name":
                    users => [ 'user1', 'user2' ],
                  }
                }
                class other_service {
                  create_users{"$name":
                    users => [ 'user1' ],
                  }
                }
                node 'testnode.example.com' {
                  include apache
                  include other_service
                }
                
                

                И это же ответ на ваш первый вопрос про действия при создании пользователей. И, за одно, про сравнение кода с include и виртуальными ресурсами.
                Да и неправильно использовать его нельзя: при создании пользователя выполняется все, что должно выполнится при создании пользователя.
                • 0
                  Опять вы со своими сферическими конями.
                  Давайте я немного приведу к нормальному виду
                  class apache {
                   @user { ['user1', 'user2']: }
                  }
                  
                  class other_service {
                   @user { 'user2': }
                  }
                  
                  define custom_file {
                    file{"/mnt/${title}":
                      ensure  => 'directory',
                      owner   => $title,
                      require => User[$title],
                    }
                  }
                  
                  class role {
                  }
                  
                  class role::web_server {
                    include apache
                    include other_service
                    custom_file { 'user2': }
                  
                    realize User['user1'], User['user2']
                  }
                  
                  node 'testnode.example.com' {
                    include role::web_server
                  }
                  

                  Примечание, реалайз должен быть в профиле, а не в роли (должны быть только инклуды профилей). Но так как классы изначально кривые, то я позволил себе немного подсократить код.

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

                  Вообще, вот код ревью, если будет полезно
                  1) define успешно принимает массив, each не нужен
                  2) Старайтесь не использовать наследование — это считается плохим тоном, усложняющим понимание написанное. Сейчас наследование используется только для доступа к полям других классов (обычное применение — хранение дефолтных переменных в модулях)
                  3) User по-умолчанию present.
                  4) На ноде в идеале должна быть только одна роль, явно описывающий, что это за нода
                  5) Такое ощущение, что классы писал кто-то, впервые увидевший puppet и использующий одинаковые имена пользователей в разных классах (точнее в том, что при правильном дизайне должно быть разными модулями).

                  Но, что-то у меня начался синдром «в интернете кто-то не прав», поэтому я завершу. Если будут какие-нибудь вопросы по существу, то с удовольствием отвечу.
                  • 0
                    Но конкретно для этого примера правильным решением будет такое:
                    define custom_file {
                      file{"/mnt/${title}":
                        ensure  => 'directory',
                        owner   => $title,
                        require => User[$title],
                      }
                    }
                    
                    class profile {
                    }
                    
                    class profile::web_server {
                      user { ['user1', 'user2']: }
                      custom_file { 'user2': }
                    }
                    
                    node 'testnode.example.com' {
                      include profile::web_server
                    }
                    
                  • 0
                    Про кто-то не прав — спасибо, что обратили внимание. Я тоже немного переборщил.
                    А по существу
                    1) define успешно принимает массив, each не нужен

                    Тут возникнет проблема если два класса решат создать одинаковых пользователей.
                    Именно по этому у меня в title поставлено $name, а массив передается через параметр.

                    5) Такое ощущение, что классы писал кто-то, впервые увидевший puppet и использующий одинаковые имена пользователей в разных классах (точнее в том, что при правильном дизайне должно быть разными модулями).

                    За «впервые увидевший puppet» отдельное спасибо.
                    А по существу: вполне бывает так что разным сервисам (в том что 1 модуль у нас настраивает один сервис разночтений же нет? Комбайны делающие все никто не пишет?) требуются одинаковые пользователи (раз уж все с пользователей началось). Значит вполне логично что каждый настраиваемый список имеет список необходимых ему пользователей, в котором пользователи могут пересекаться.
                    • 0
                      Можно пример?
                      • 0
                        Конечно. Puppet, при передаче в define (или любой тип, вроде user) массива в виде title, разворачивает его на отдельные элементы и делает вызов для каждого элемента. По этому имеем:
                        define create_users ($users=$title) {
                          $users.each |$user| {
                            include "users::$user"
                          }
                        }
                        
                        class users {
                          User { ensure => present }
                        }
                        class users::user1 inherits users {
                          user{'user1':}
                        }
                        class users::user2 inherits users {
                          user{'user2':} ->
                          file{'/mnt/user2':
                            ensure => 'directory',
                            owner => 'user2',
                          }
                        
                        }
                        
                        class apache {
                          create_users{[ 'user1', 'user2' ]: }
                        }
                        class other_service {
                          create_users{[ 'user1' ]: }
                        }
                        
                        include apache
                        include other_service
                        

                        И в результате:
                        puppet apply test.pp
                        
                        Error: Evaluation Error: Error while evaluating a Resource Statement, Duplicate declaration: Create_users[user1] is already declared in file /root/test.pp:23; cannot redeclare at /root/test.pp:26 at /root/test.pp:26:3 on node mynode
                        Error: Evaluation Error: Error while evaluating a Resource Statement, Duplicate declaration: Create_users[user1] is already declared in file /root/test.pp:23; cannot redeclare at /root/test.pp:26 at /root/test.pp:26:3 on node mynode
                        

                        Более того, если мы захотим передать хэш в виде title у нас тоже ничего не получится (но, правда по другим причинам). По этому с массивами нужно быть крайне аккуратным и each внутри define вполне имеет право на жизнь.
                        • 0
                          Я плохо выразился. Я имел в виду пример этого
                          вполне бывает так что разным сервисам требуются одинаковые пользователи
                          • 0
                            Чтож, полагаю что в ошибочности первого пункта вашего review на мой код сомневаться больше не приходится. Я бы с удовольствием прошелся по остальным, но это выходит за рамки зявленной автором топика темы.

                            Теперь вернемся к оригинальной теме этого топика — виртуальным ресурсам в puppet.
                            Мой пример иллюстрирует лишь то, что использование виртуальных ресурсов избыточно (Ваша просьба примера использования одинаковых пользоватеелй в разных классах/сервисах, впрочем, тоже, поскольку вирутальные ресурсы решают имено проблему вызова одинаковых ресурсов из разных классов). Иллюстрирует он это в тех же терминах, что и автор этого поста: применительно к ресурсу user.

                            Но и Вашу просьбу о примере без ответа я оставить не могу (хотя он тут же будет назван «специфическим», но мы же сейчас не решаем какую-то реальную задачу, а обсуждаем общие принципы, связанные с виртуальными ресурсами).
                            Итак, пример.
                            Распределенное дисковое хранилище, dCache, для организации доступа по протоколу srm, требует установленные на зулах x509 сертификаты, которые должны принадлежать тому же пользователю, от которого dCache запусакется. Сертификаты обновляются каждый год. Тому же пользователю должны принадлежать все конфигурационыне файлы.
                            Итого: классу, настраивающему dCache требуется тот же пользователь, что и классу, который кладет сертификаты на сервер.
                            • 0
                              То ли я плохо рассказываю, то ли вы плохо понимаете.

                              Я указывал, что в любом случае создание отдельного класса для пользователя — плохой дизайн. Без каких-то оправданий. И все проблемы — например, из-за дубликации дефайна в вашем случае, которая появилось именно из-за плохого дизайна. Стоит отметить, что все примеры виртуальных ресурсов, которые тут указаны, неудачны и являются лишним усложнением кода.

                              И конкретно для примера я написал лучшее решение: habrahabr.ru/post/242123/#comment_8106233

                              Касательно пользователей в dCache. Почему вы пытаетесь описывать пользователя по 100500 раз? Зачем вы дублируете код?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.