Pull to refresh

Comments 46

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В самом первом примере можно выкрутиться одним 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 <| |>
Не нужно такие примеры писать — кто-то может и повторить.

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

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

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

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

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

А оригинально дискуссия была о том, что виртуальные ресурсы не дают дополнительной пользы: все что они делают можно сделать и без них.
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.
Да, а теперь вопрос: зачем тут виртуальные ресурсы? Почему бы пользователей не создавать в классах с действиями, например вот так:
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 и виртуальными ресурсами.
Да и неправильно использовать его нельзя: при создании пользователя выполняется все, что должно выполнится при создании пользователя.
Опять вы со своими сферическими конями.
Давайте я немного приведу к нормальному виду
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 и использующий одинаковые имена пользователей в разных классах (точнее в том, что при правильном дизайне должно быть разными модулями).

Но, что-то у меня начался синдром «в интернете кто-то не прав», поэтому я завершу. Если будут какие-нибудь вопросы по существу, то с удовольствием отвечу.
Но конкретно для этого примера правильным решением будет такое:
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
}
Про кто-то не прав — спасибо, что обратили внимание. Я тоже немного переборщил.
А по существу
1) define успешно принимает массив, each не нужен

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

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

За «впервые увидевший puppet» отдельное спасибо.
А по существу: вполне бывает так что разным сервисам (в том что 1 модуль у нас настраивает один сервис разночтений же нет? Комбайны делающие все никто не пишет?) требуются одинаковые пользователи (раз уж все с пользователей началось). Значит вполне логично что каждый настраиваемый список имеет список необходимых ему пользователей, в котором пользователи могут пересекаться.
Конечно. 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 вполне имеет право на жизнь.
Я плохо выразился. Я имел в виду пример этого
вполне бывает так что разным сервисам требуются одинаковые пользователи
Чтож, полагаю что в ошибочности первого пункта вашего review на мой код сомневаться больше не приходится. Я бы с удовольствием прошелся по остальным, но это выходит за рамки зявленной автором топика темы.

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

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

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

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

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

Articles