Pull to refresh

Пример программирования в Puppet версии 3.8 с использованием Hiera и R10K

Reading time9 min
Views13K
We Need To Go Deeper.
Inception.


Здесь я хочу описать примеры/приёмы программирования. Причина всё та же: в интернете должно быть больше доков. В моей прошлой статье было рассказано про установку и нaстройку Puppet версии 3.8 на примере Centos 6.5. Там же бы простейший пример, чтобы проверить работоспособность связки клиент-сервер. Теперь посмотрим как это можно усложнить и для чего это надо.


Пример №1: Использование параметров в манифестах модуля


class test1 {
  # Это пример комментария  
  $text1 = “our” 		# Переменная раз
  $text2 = “text” 		# Переменная два
  file { 'puppetenv file': 		# создание файла, где «puppetenv file» – уникальное имя для 
						# вызова ресурса file. Нельзя использовать в манифесте повторно.
    path    => '/tmp/puppetenv',		# имя создаваемого файла
    ensure  => file, 				# тип ресурса
    content => "test: ${text1} - ${text2}"	# содержимое файла с использоваинем параметров
  }
}


Цепочка передачи нашего текста в параметрах:
class ($text1 $text2) -> file (content (${text1}-${text2})

Пример №2: Единое хранение пераметров в манифесте модуля


Итак следующий уровень: у нас много классов и мы хотим сделать аналог библиотеки переменных. Параметры можно хранить в одном рецепте и потом использовать уже по всему модулю. К примеру мы хотим собрать некие ситемные переменные и добавить что-то своё.

Немного повторим основы:

Заходим на Мастер-сервере в /etc/puppet/modules и генерируем скелет:

puppet module generate myname-test1


Жмём 8 раз ввод, ибо это всё потом можно переделать. Получаем каталог myname-test1, который переименуем в test1. Полное имя нужно, если вы хотите сбилдить и загрузить свой модуль на общественный Puppet forge.

Создаём файл /etc/puppet/modules/test1/manifests/init.pp
class test1 (				
$envname = $test1::params::env,	# подгрузка параметров из манифеста params
$text1 = $test1::params::text1	
) inherits test1::params {         	# подгрузка класса через inherits
  file { 'puppetenv file': 
    path    => '/tmp/puppetenv', 		
    ensure  => file, 
    content => "${env} - test: ${text1}" 
  }
}


Создаём файл /etc/puppet/modules/test1/manifests/params.pp
class test1::params {
    $env = $settings::environment 	# внутренняя переменная puppet
    $text1 = 'ptestint' 			# наш дефолтовый текст
}


Пояснения:
test1 – Имя модуля – Корневое имя проекта в системе ценностей puppet.
init.pp, params.pp – манифесты – Один манифест хранит один класс. Имя файла и класса должны совпадать.
class test1 – начальный класс, код которого работает по дефолту при простом вызове потом класса через простой include test1
При желании можно оставлять пустым и создавать отдельные именные классы (см ниже).
class test1::params – именной класс. Имя params выбран для удобства и может быть любым.

Проверить заранее синтаксис кода можно 2 способами:
— Изначально доступный через команду типа:
puppet parser validate /etc/puppet/modules/test1/manifests/*


— Поставить более продвинутый спеллчекер рpuppet-lint через gem install puppet-lint и затем проверять файлы (заодно можно чуть причесать синтаксис ключём --fix):
puppet-lint --fix /etc/puppet/modules/test1/manifests/*


Но не надейтесь особо на них, можно легко пропустить ошибку вроде неверного имени манифеста что выдаст ошибку уже при запуске на кленте вроде:

Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Could not find parent resource type 'test::params' of type hostclass in production at /etc/puppet/modules/test1/manifests/init.pp:1 on node stage.test.net


Снова хочу предупредить, что я заметил, что в ветке 3.8.4 теперь изменив код модуля, приходится перезапустить сервис puppetmaster или httpd, чтобы изменения в модулях применились сразу, раньше он это не игнорировал. Возможно в других версиях это и не проявится. Подозреваю где то залипает кэш модулей.

Добавляем вызов нашего модуля test1 в файл /etc/puppet/manifests/site.pp для тест узла stage.test.net.
node default { }
node 'stage.test.net' {
  include test1
}


Проверка на клиенте:
Можно настроить сервис и ждать ХХ(обычно 30) минут, пока он сработает и потом глянуть логи. А можно запустить всё вручную:
[root@stage ~]# puppet agent --test
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for stage.test.net
Info: Applying configuration version '1459348679'
Notice: /Stage[main]/Test1/File[puppetenv file]/ensure: defined content as '{md5}af1b5424181346c5f4827af742b07abf'
Notice: Finished catalog run in 0.12 seconds
[root@stage ~]# cat /tmp/puppetenv
production - test: ptestint


Как видим файл успешно создался. Если кто хочет посмотреть до чего может дойти использование параметров, то вот пример модуля для apache.

Цепочка передачи нашего текста ptestint:
manifests/site.pp -> modules/test1/init.pp($test1::params::text1) -> file (content (${text1})

Теперь как можно изменить дефолтные переменные в /etc/puppet/manifests/site.pp, т. к. у них выше приоритет.
node 'stage.test.net' {
#  include test1
  class  { 'test1':
     text1 => 'newparam',
  }
}


Проверка на клиенте:

-production — test: ptestint
\ No newline at end of file
+production — test: newparam
\ No newline at end of file



Как видим апдейт прошёл удачно.
Цепочка передачи нашего текста newparam:
manifests/site.pp($text1) -> modules/test1/init.pp(text1) -> file (content (${text1})

Такое хранение параметров также удобно, если мы не создаём все манифесты в одном каталоге, а сделали ещё один уровень в вида /test1/manifests/check/time.pp
И потом используем этот класс где угодно через вызов вида:
class { '::test1::check::time': }


Пример №3: Добавляем шаблон.


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

Добавим в тест:
— Файл /etc/puppet/modules/test1/manifests/usetmpl.pp – новый класс для работы с шаблоном
class test1::usetmpl (
$envname = $test1::params::env,  #1
$text1 = $test1::params::text1   #2
) inherits test1::params {

  file { 'puppetenv file from tmpl':
    path    => '/tmp/puppetenv',
    ensure  => file,
    content => template('test1/puppetenv.erb'),
  }
}


Изменения в замене текста content на вызов шаблона puppetenv.erb плюс мы всё вывели в отдельный класс, хотя могли бы добавить создание второго файла в init.pp.

— Файл /etc/puppet/modules/test1/templates/puppetenv.erb — Наш шаблон-генератор.
# Created by puppet
Our text: <%= @text1 %>
Env: <%= @envname %>
Host: <%= @hostname %>


Переменные ${text1} тут передаются в Ruby-формате как <%= @text1 %>. $envname берётся из params.pp, $text1 я опять переопределил в site.pp и заодно добавил «системную» переменную (в puppet они называются фактами) <%= hostname %>

Проверка на клиенте:
[root@stage ~]# puppet agent --test
...
-production - test: newparam
\ No newline at end of file
+# Created by puppet
+Our text: param_tmpl
+Env: production
+Host: stage
...


Итог:
[root@stage ~]# cat /tmp/puppetenv
# Created by puppet
Our text: param_tmpl
Env: production
Host: stage


Цепочка паредачи нашего текста param_tmpl:
manifests/site.pp($text1) -> modules/test1/init.pp::usetmpl($text1) -> file(content(puppetenv.erb(<%= @text1 %>)))

Пример №4: Hiera или ещё больше централизации


Если надо работать с 1-10 серверами, то вполне хватит и обычных модулей, однако если их стало больше плюс пошло разделение на подкластеры, где каждый модуль настраивается по-своему, то можно запутаться в распухшем от параметров модулей site.pp, либо в почти одноимённых модулях и их версиях. Мы идём глубже и настраиваем Hiera.

Hiera — библиотека Ruby, по умолчанию включена в Puppet и помогает организовать данные для всех модулей в едином каталоге.

Для работы нашего хранилища нужно:

— Создать файл /etc/puppet/hiera.yaml примерно такого вида:

:hierarchy:
    - "%{::clientcert}"
    - "%{::custom_location}"
    - "nodes/%{::fqdn}"
    - "nodes/%{::environment}"
    - "virtual/%{::virtual}"
    - common
    - "%{::environment}"

:backends:
    - yaml

:yaml:
    :datadir: "/etc/puppet/hieradata"


Здесь нас ждёт заподлянка от системы в виде приоритета родного файла /etc/hiera.yaml
Т. ч. надо заменить его на симлинк /etc/hiera.yaml -> /etc/puppet/hiera.yaml

— Создать папку /etc/puppet/hieradata (можно дать своё имя и указать его в :datadir)
Файлы в этой папке обязательно должны иметь расширение .yaml и формат данных YAML.

— Создать файл /etc/puppet/hiera/common.yaml
К примеру здесь мы можем записать доступный всем узлам второй тестовый параметр

test::text2: common-hiera


Т. к. мы задали точкой хранения параметров узлов каталог — «nodes/%{::fqdn}», то для нашего тестового узла создаём файл /etc/puppet/hiera/nodes/stage.test.net.yaml. В нём мы теперь можем задать наш третий тестовый параметр и небольшой массив в котором будет параметр и ещё один массив
testparam::text3: ‘node stage hiera’
arrexmpl::colors:
  bw: "B&W"
  rgb:
     - red
     - blue
     - green


Проверка доступности параметров из командной строки в дебаг и простом режиме:
[root@pmaster /etc]# hiera -d test::text2
DEBUG: Wed Mar 30 13:06:13 -0400 2016: Hiera YAML backend starting
DEBUG: Wed Mar 30 13:06:13 -0400 2016: Looking up test::text2 in YAML backend
DEBUG: Wed Mar 30 13:06:13 -0400 2016: Looking for data source common
DEBUG: Wed Mar 30 13:06:13 -0400 2016: Found test::text2 in common
common-hiera
[root@pmaster /etc]# hiera testparam::text3 ::fqdn=stage.test.net
node stage hiera
hiera arrexmpl::colors ::fqdn=stage.test.net
{"rgb"=>["red", "blue", "green"], "bw"=>"B&W"}


Теперь нам надо сохранить их в параметрах и использовать в шаблоне.
/etc/puppet/modules/test1/manifests/params.pp
class test1::params {
    # sss
    $env = $settings::environment    
    $text1 = 'ptestint'
    $text2 = hiera('test::text2', 'ptestint2') # читаем параметр 'test::text2'. В случае неудачи сохраняем в нём 'ptestint2'
    $text3 = hiera('testparam::text3', 'ptestint3')
    $colors = hiera('arrexmpl::colors', 'nohiera') # читаем весь массив arrexmpl::colors
    if $colors != 'nohiera' {			    # проверка и первый парсинг массива
      $c1 = $colors['bw']
      $c2 = $colors['rgb']
    }
    else
    {
      $c1 = "lost"
    }
}


/etc/puppet/modules/test1/manifests/usetmpl.pp
class test1::usetmpl (
$envname = $test1::params::env,
$text1 = $test1::params::text1,
$text2 = $test1::params::text2, # передали все новые параметры
$text3 = $test1::params::text3,
$c1 = $test1::params::c1,
$c2 = $test1::params::c2
) inherits test1::params {

  file { 'puppetenv file':
    path    => '/tmp/puppetenv',
    ensure  => file,
    content => template('test1/puppetenv.erb'),
  }

  file { 'hiera test':
    path    => '/tmp/phiera',
    ensure  => file,
    content => template('test1/hieratst.erb'),
  }
}


/etc/puppet/modules/test1/templates/hieratst.erb
# Hiera test
# Created by puppet
Our text1: <%= @text1 %>
Our text2: <%= @text2 %>
Our text3: <%= @text3 %>
Colors:
BW = <%= @c1 %>
<% if c1 == "lost" %>  # проверка на фэил
 ! Hiera fail !
<% end -%>
RGB =
<%- c2.each do |colors| -%>  # второй  парсинг массива arrexmpl::colors::rgb:
- <%= colors %>
<%- end -%>


Проверка на клиенте:

[root@stage ~]# puppet agent --test
...
[root@stage /etc/puppet]# cat /tmp/phiera
# Hiera test
# Created by puppet
Our text1: paramtmpl
Our text2: common-hiera
Our text3: node stage hiera
Colors:
BW = B&W
RGB =
- red
- blue
- green


Пример №5: R10K или ещё ещё ещё больше централизации


Собственно если счёт серверов пошёл на десятки сотни то становится безопаснее делить их на environments. Можно делать это ручками, но лучше использовать R10K, который позволяет запускать отдельную конфигурацию использования модулей, которая хранится в его личных настройках. Т. е. По сути он заменяет единый гигантский site.pp на своё дерево конфигов.

Я не буду делать тест, просто приведу условный алгоритм такой настройки на примере моей рабочей конфигурации.

— Сервера делятся на группы настройки которые хранятся в отдельных каталогах в /etc/puppet/environments

К примеру будем тестить наш рецепт на группе серверов test_devops

Дерево хиеры

— хранится на гите/битбакете
— клонится для обновления применении измений на Мастер сервер
— в hiera.yaml добавлено в :hierarchy:
  - %{environment}/%{role}/%{calling_module}
  - %{environment}/%{role}
  - %{role}
  - %{environment}/%{environment}

— Наши параметры будут условно храниться в файлах
/etc/puppet/hieradata/test_devops/ test_devops.yaml — для всех узлов через доп метку для R10K
     classes:
       - roles::base

/etc/puppet/hieradata/test_devops/stage.test.net.yaml для сервера плюс нужна метка для R10K
    classes:
      - roles::stagesrv::test


Настройка запуска модуля для test_devops через R10K

— На узле stage.test.net в файл /etc/puppet/puppet.conf нужно добавить строчку
environment = stage_nbc210

/etc/puppet/environments/test_devops/Puppetfile – тут хранятся все используемые модули.
Примеры записи
mod 'saz/sudo', '3.0.1'
mod 'stdlib',
  :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git',
  :ref => '4.1.0' 
mod 'test1',
        :git => 'git://github.com/fake_link/test1.git',
        :ref => 'master'


Они потом скачаваются/обновляются в консоли через команду типа
sudo r10k deploy module test1 -e test_devops 

/etc/puppet/environments/test_devops/modules/test1 — Место куда свалился наш модуль

/etc/puppet/environments/test_devops/dist/profiles/manifests/ — дерево манифестов запуска модулей. Имена файлов не должны совпадать с именами модулей.

Создаём тут файл типа runtest1.pp

   class profiles::runtest1{
   class  { 'test1':
     text1 => 'newparam',
  }


Как видим узлы теперь не указываются. Если другим узлам нужны другие параметры то можно создать runtest2.pp и т. д. Поддерживаются доп уровни. Например можно создать файл /etc/puppet/environments/test_devops/dist/profiles/manifests/ver2/runtest3.pp
class profiles::ver2::runtest3
class { 'test1': text1 => 'newparam v2', }
}


— Теперь надо привязать манифесты запуска модулей к узлам:
/etc/puppet/environments/test_devops/dist/roles/manifests/init.pp
class roles { }
class roles::base inherits roles {     include profiles::base }
class roles::private inherits roles {     include profiles::private }
class roles::public inherits roles {     include profiles::public }

class roles::stagesrv::test {	# <- Помните мы забивали метку в Hiera ?  
   include profiles::runtest1
}

# Причём при желании мы можем ещё и наследовать запуск модулей, чтобы не повторяться. 
# В данном примере будет небольшой конфликт, когда runtest3 перезапишет совпадающий фйал от наследуемого 
# runtest1, плюс у нас нет узла с меткой roles::ver2::runtest3, т. ч. код просто проигнорируется.

	class roles::ver2::runtest3 inherits roles::stagesrv::test {
	  include profiles::ver2::runtest3
	}


— Собственно остался дамп итогов настройки
sudo r10k deploy environment test_devops


И потом это применится на узле по автозапуску или можно протестить вручную через puppet agent --test

На это собственно всё. Спасибо, что дочитали до сюда.

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

Сравнивать с опытом использования chef client-server / chef+berkshelf / chef + AWS Opswork не буду, т. к. там совсем другой алгоритм организации кукбоков-«модулей» и более чистый Ruby в рецептах-«манифестах».

Доп доки:
1. По микропримерам Puppet кода.
2. По введению в Hiera
3. Немного по R10K
Tags:
Hubs:
-35
Comments11

Articles

Change theme settings