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


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

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 	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

ссылка на оригинал статьи https://habrahabr.ru/post/280632/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *