Привет всем,
Сегодня хотел бы поделится вводной информацией, относительно того как начать использоваться puppet.
Итак, как многим уже известно Puppet - система управления конфигурацией кучки хостов. С его помощью мы можем приводить и поддерживать хосты в нужном нам состоянии.
Как работает puppet
Puppet, в отличии от ansible, работает по pull модели. Хосты на которых устанавливается puppet-агент, каждые пол часа обмениваются информацией с puppet-мастером для получения новой конфигурации. При этом клиенты puppet отправляют факты о себе мастеру. И в случаи наличия изменений применяют их у себя локально.
Взаимодействие между клиентом/сервером осуществляется за счет создания SSL-соединения, на puppet-master поднимается https порт - 8140/TCP, который ожидает входящий трафик.
Но прежде чем начать обменится данными, puppet-агент установленный на хосте, должен быть правильно идентифицирован на главном сервере puppet (puppet-master). Для это каждый клиент генерит свой клиентcкий сертификат, в который включается имя хоста и отправляет его на puppet-сервер.
Администратор сервера просматривает этот сертификат и видит имя клиентского хоста, затем принимает решение о дальнейшем взаимодействии с ним.
Обычно мы просто подписываем клиентский сертификат, и затем подписаный клиентский сертификат отправляется обратно на хост.
Теперь клиент/сервер готовы к взаимодействию. И обе стороны могут поднять SSL сессию.
Когда клиенту вновь потребуется обратится к puppet-серверу, он запросит его мастер-сертификат. В ответ на запрос, puppet-сервер отправит свой сертификат и затем запросит сертификат клиента, для того что бы проидентифицировать клиента в своей системе.
Если же клиентский сертификат был ранее подписан puppet-сервером, то устанавливается SSL сессия с клиентом.
Иначе полученный клиентский сертификат нужно будет подписать на стороне puppet-сервера.
Сеанс связи между сервером/клиентом установлен. Агент запущенный на клиентских хостах собирает все факты о системе (установленная операционка, ip-адресс, и прочее) и отправляет их на puppet-server.
Полученные факты с хостов сравниваются с манифестами на puppet-сервер. Делается это для того, что бы проверить соблюдается ли текущее состояние клиентского хоста с описанием манифеста.
В случаи же, если то состояние что описано в манифесте не соблюдено на хосте, компилируется новый пакет и этот пакет отправляется на клиенсткий хост. Полученный пакет выполняется на клиенте, для достижения нужного состояния.
И после применений, каждым хостом генерируется некий репорт(отчет), который отправляется мастеру.
Настройка/Работа с puppet
И так, мы ранее разобрались с тем как все работает, и много раз было упомянуто относительно состояния системы и манифестов.
Конфигурация нод (или состояние) в puppet описывается в манифестах на декларативном языке DSL.
-
Манифест (Manifest) это сушность объединяющая все ресурсы и классы, как правило заканчивается с расширением на
.pp
. Проводя параллель с Ansible, манифесты puppet можно сравнить с playbook в Ansible. -
Ресурсы (Resources) это минимальный объект описывающий то состояние, которое мы хотим увидеть в конечном итоге. В экосистеме Ansible, task схож с ресурсом в puppet.
-
Классы (Class) - сушность внутри которой описывается несколько ресурсов, для упорядочивания и объединения в один объект. Классы также позволяют избежать повторного переиспользования написанного кода.
-
Модуль (Module) - это дерево каталогов содержащее манифесты, конфигурационные файлы, шаблоны. Модуль используется для реализации одной задачи, например для установки и настройки одного ПО. В мире Ansible, модуль можно сравнить с ролью.
Перед тем как начать знакомится более подробней, развернем небольшую инфраструктуру из трех серверов на Almalinux 8. Один сервер будет выступать в качестве puppet-сервера, а остальные две ноды будут подчиненными.
Установка puppet-сервера
Для установки пакета puppet, сначала добавляем репозиторий:
[root@puppet-master ~]# yum install -y https://yum.puppetlabs.com/puppet8-release-el-8.noarch.rpm
Как можете заменить используема нами версия - 8.
Ну и теперь просто инсталим пакет:
[root@puppet-master ~]# yum install -y puppetserver
Puppet работает на java, окружение java ставить дополнительно не требуется. В качестве зависимости пакет openjdk c 11 версией java поставится автоматом.
По умолчанию puppetserver резервирует 2gb памяти, в зависимости от общего числа памяти на сервере можем отдать меньшее колличество. Например у меня на виртуальной машине общий объем памяти 4gb, под нужны puppetserver отдам 1gb.
[root@puppet-master ~]# vi /etc/sysconfig/puppetserver
---
# Modify this if you'd like to change the memory allocation, enable JMX, etc
JAVA_ARGS="-Xms1g -Xmx1g -Djruby.logger.class=com.puppetlabs.jruby_utils.jruby.Slf4jLogger"
Меняются значения в параметрах - -Xms1g -Xmx1g
.
В файле глобальной конфигурации puppetserver нужно определить настройки мастер-сервера:
[root@puppet-master ~]# vim /etc/puppetlabs/puppet/puppet.conf
---
[master]
dns_alt_names = puppet-master,puppet-master.nixhub.ru
[main]
certname = puppet-master.nixhub.ru
server = puppet-master.nixhub.ru
runinterval = 30m
Самая базовая настройка конфигурации это указание dns-имен сервера. Без этого не сгенерится рутовый/промежуточный сертификат.
Ну и в файл /etc/hosts
, добавляем запись:
[root@puppet-master ~]# vim /etc/hosts
---
# Pupper server
127.0.0.1 puppet-master.nixhub.ru puppet-master
Так как в моей тестовой лабе нет внешних dns, в качестве альтернативы будем использовать локальные источники.
В 8 версии, рабочий каталог и бинарные файлы располагаются в /opt/puppetlabs/puppet/
. Для удобства взаимодествия с утилитами, можно добавить этот путить к переменной окружения $PATH
. Ну или же прост осоздать симлинк на нужные утилиты:
[root@puppet-master ~]# ln -s /opt/puppetlabs/bin/puppetserver /usr/sbin/puppetserver
Ну и теперь настроим CA puppet-сервера:
[root@puppet-master ~]# puppetserver ca setup
Generation succeeded. Find your files in /etc/puppetlabs/puppetserver/ca
Прежде чем запустить сервис, не забываем открыть порт на фаерволе:
[root@puppet-master ~]# firewall-cmd --add-port=8140/tcp --permanent
[root@puppet-master ~]# firewall-cmd --reload
Ну и запускаем сервис:
[root@puppet-master ~]# systemctl enable --now puppetserver
Настройка puppet-agent
Настройка агентов, такая тривиальная задача. Добавляем репозиторий puppet:
[root@puppet-hode01 ~]# yum install -y https://yum.puppetlabs.com/puppet8-release-el-8.noarch.rpm
Ставим агента:
[root@puppet-hode01 ~]# yum install -y puppet-agent
В конфигурационном файле агента, нужно прописать адрес puppet-сервера и имя сертификата нашего хоста (клиента):
[root@puppet-hode01 ~]# vi /etc/puppetlabs/puppet/puppet.conf
---
[main]
server = puppet-master.nixhub.ru
certname = puppet-node01.nixhub.ru
runinterval = 30
Опять же, не забываем в /etc/hosts
добавить запись с адресом нашего сервера:
[root@puppet-hode01 ~]# vi /etc/hosts
---
# Puppet server
10.8.5.21 puppet-master.nixhub.ru
Затем запускаем агента,
[root@puppet-hode01 ~]# /opt/puppetlabs/bin/puppet resource service puppet ensure=running enable=true
Notice: /Service[puppet]/ensure: ensure changed 'stopped' to 'running'
service { 'puppet':
ensure => 'running',
enable => 'true',
provider => 'systemd',
}
Подпись клиентского сертификата
Как упоминалось ранее, для идентификации удаленных клиентов используется модель подписи сертификатов. При первом запуске агента, клиент отправит свой сертификат на сервер.
Для его просмотра воспользуемся командой:
[root@puppet-master ~]# puppetserver ca list
Requested Certificates:
puppet-node01.nixhub.ru (SHA256) FF:9F:96:FF:F7:6E:22:62:5B:00:CE:26:AF:44:9B:7E:8C:2E:5B:9A:EA:39:69:50:A4:15:B0:F8:B5:6C:4A:7D
puppet-node02.nixhub.ru (SHA256) FA:52:AF:2E:07:46:03:26:8E:48:56:86:A9:8C:35:61:9C:79:0E:7F:48:81:4A:76:3C:B4:4D:F4:35:CF:DF:71
Как видно из вывода команды, у нас есть два запроса на подпись сертификата.
Для того, что бы подписать запрос можем воспользоваться командой:
# Для подписи определенного запроса:
[root@puppet-master ~]# puppetserver ca sign --certname puppet-node01.nixhub.ru
Successfully signed certificate request for puppet-node01.nixhub.ru
# Для подписи всех запросов:
[root@puppet-master ~]# puppetserver ca sign --all
Successfully signed certificate request for puppet-node02.nixhub.ru
Структура каталогов сервера
Прежде чем приступить к созданию своего манифеста, рассмотрим структуру каталогов puppet-сервера.
Ключевые каталоги указываются в файле - /etc/puppetlabs/puppet/puppet.conf
, по желанию их можно переопределить.
Основной каталог, с которым чаще всего придется работать это - /etc/puppetlabs/code
. В его состав включено два подкаталога environments
и modules
.
environments
- содержит наши рабочие окружения, быть может в нашей инфраструктуре есть несколько сред, например - production, dev, test. И можем разделить выполнение задач по тем или иным окружениям.modules
- в данном каталоге как правило располагаются общие модули, доступные для импорта во все среды. Также в этот каталог добавляются модули скачанные из внешней библиотеки.
На данном этапе нам интересен раздел со средами. По умолчанию в puppet, уже имеется каталог с production
средой. Мы можем использовать его как эталонный для создания своих сред (Агенты по умолчанию, стягивают каталоги с этого окружения).
/etc/puppetlabs/code/environments/
└── production
├── data
├── environment.conf
├── hiera.yaml
├── manifests
└── modules
Внутри каталога среды имеется следующие подкаталоги:
data
- содержит данные относящиеся к модулю hiera (переменные и прочее, об этом позже);manifest
- как уже понятно из названия, в данный каталог помещают различные манифесты. Обычно туда кладут файлsite.pp
, который как бы является entry-точкой входа в манифест;modules
- в содержимое данного каталога кладут различные модули, как написанные самостоятельно так и скачанные из сети. Затем эти модули импортирются(инклюдятся) в главный манифестsite.pp
, как например.environment.conf
- в данному файле определяются настройки для нашей среды.hiera.yaml
- а этот файл служит для настройки доп.компонента hiera. Обычно он нужен для выстраивания иерархии хостов, для их обработки.
Пишем первый манифест
После всей лирики, давайте перейдем к практике и выполним так называемый - Hello World
. Пропишем ресурс file
, который создаст файл с содержащий информацию по нашим серверам.
Итак, тут все просто открываем файл site.pp
, в каталоге /etc/puppetlabs/code/environments/manifests/
и указываем:
# Для наглядности мое текущее расположение:
[root@puppet-master production]# pwd
/etc/puppetlabs/code/environments/production
# Пишем site.pp в manifests:
[root@puppet-master production]# vim manifests/site.pp
---
node default {
file { '/tmp/server_info.txt':
ensure => present,
content => "${facts['networking']['fqdn']}, (${facts['networking']['ip']})\n",
owner => "root",
group => "root",
}
}
Манифест начинается с контекста node default { ... }
, который нам говорит на каких нодах мы хотим применить данные изменения. Слово default
в ключевом значении node
, в данном случаи указывает применения инструкций на всех нодах, известных puppet-сервер. Эта конструкция в терминалогии кода puppet называется - Node definitions
. И по мимо значения default
, мы можем указывать имя сертификата отдельного хоста. Также можем использовать регулярные выражения в контексте определения имен узлов.
Внутри Node definitions
, идет контекст ресурса(resource) - file { ... }
. Как уже упоминалось ранее, каждый ресурс описывает желаемое состояние, которое мы хотим видеть. В нашем же случаи берется тип ресурса - file
, в его заголовке(title) идет уникальное значение - /tmp/server_info.txt
. Заголовки, как раз таки и нужны для уникальной идентификации ресурса паппетом. Затем следуют атрибуты ресурса, как раз таки в атрибутах и описывается желаемое нами состояние. В моем примере:
ensure => present
- данный атрибут, говорит нам что файл будет гарантированно создан;content => "..."
- указывает желаемое содержимое в виде одной строки. В нашем же случаи строка собирается из фактов, которые в свою очередь добавляют имя fqdn-хоста и ip-адресс этого же хоста.owner
/group
- аттрибуты определяющие владельца и группу создаваемого файла.
Чтобы не ждать пока изменения будут применены, применим конфигурацию с клиентского хоста. Как я говорил ранее, по дефолту интервал обновления 30 минут.
[root@puppet-hode01 ~]# /opt/puppetlabs/bin/puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet-node01.nixhub.ru
Info: Applying configuration version '1693830717'
Notice: /Stage[main]/Main/Node[default]/File[/tmp/server_info.txt]/ensure: defined content as '{sha256}1d2de8d01c94900ce5176de145719a258b87851ee6fa4d46371d0dcab9d34edc'
Notice: Applied catalog in 0.02 seconds
Загляним во внутрь файла /tmp/server_info.txt
,
[root@puppet-hode01 ~]# cat /tmp/server_info.txt
puppet-hode01.office.localdomain, (10.8.5.22)
Отлично файл с нужным контентом у нас отобразился.
Пишем первый модуль
Представим, что к нам пришла задача, в которой требуется поставить zabbix-агента на десяток серверов. И для решения этой задачи мы напишем соответствующий модуль.
Я проваливаюсь во внутрь каталога с окружением:
[root@puppet-master ~]# cd /etc/puppetlabs/code/environments/production/
Затем внутри окружения создаю новый модуль:
[root@puppet-master production]# mkdir -p modules/setup_zbx_agent/{manifests,templates,files}
Рекурсивно будет создан модуль с названием setup_zbx_agent
, внутри каталога модуля будут созданы каталоги:
manifests
- директория для хранение ресурсов и классов;templates
- каталог где будут лежать шаблоны, в нашем случаи шаблон с конфигурацией zabbix-агента;files
- ну и каталог под файлы, тут как правило хранятся статические файлы. В этот каталог мы закинем файл репозитория zabbix.
В документации puppet, раздел написания модулей, рекомендуется декомпозировать большую задачу на множество мелких подзадач, которые будут выполнять только одно действие.
Начнем процесс с добавления статического файла в каталог - files
.
[root@puppet-master ~]# cd /etc/puppetlabs/code/environments/production/
[root@puppet-master production]# vim modules/setup_zbx_agent/files/zabbix.repo
---
[zabbix]
name=Zabbix Official Repository - $basearch
baseurl=https://repo.zabbix.com/zabbix/6.0/rhel/8/$basearch/
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
Здесь создаем файл репозитория zabbix, данные для репозитория нашел на официальном сайта.
Сервис заббикс-агента требует минимальный конфиг для запуска, в конфиге указывается данные определяющие настройки агента и доступ к серверам zabbix. В этой задаче создадим шаблон, который будет параметаризирован в процессе. Из этого шаблона будет собиратся готовый конфигурационный файл.
В каталоге templates
, создаем:
[root@puppet-master production]# vim modules/setup_zbx_agent/templates/zabbix_agentd.erb
---
# --
# Managment via Puppet
# --
# Pid location
PidFile=/var/run/zabbix/zabbix_agentd.pid
# Log's patametres
LogFile=/var/log/zabbix/zabbix_agentd.log
LogFileSize=0
# Zabbix-server hostname/ip
Server=<%= @zbx_server %>
ServerActive=<%= @zbx_server %>
# Zabbix-agent hostname
Hostname=<%= @zbx_host %>
# Includes extentions:
Include=/etc/zabbix/zabbix_agentd.d/*.conf
В этом шаблоне используется всего две переменной - zbx_server
, zbx_host
. В первой переменной будет подставляться адрес zabbix сервера, во второй будет fqdn/хостнейм хоста на котором будет инсталирован заббикс агент.
Данные подставляются в шаблон за счет открывающего/закрывающего тега - <%=
, %>
. Доступ к переменной в шаблоне осуществляется через символ @
, например - @zbx_server
.
На этом этапе остается только написать манифест, который собирает все воедино и устанавлиет агента. Декомпозировав задачу, я решил написать два класса. Первый класс будет устанавливать zabbix-агента, предварительно добавив репозиторий. Ну и после установки открывать соответсвующий порт. Второй класс будет из шаблона собирать конфигурационный файл, и перезапускать сервер zabbix-агента.
Начнем с описания первого класса:
[root@puppet-master production]# vim modules/setup_zbx_agent/manifests/install.pp
---
class setup_zbx_agent::install (
$zbx_package = 'zabbix-agent',
$zbx_listen_port = 10050,
){
file { '/etc/yum.repos.d/zabbix.repo':
ensure => 'present',
source => "puppet:///modules/setup_zbx_agent/zabbix.repo",
}
package { "$zbx_package":
ensure => 'present',
require => File['/etc/yum.repos.d/zabbix.repo'],
}
exec { 'firewall':
path => '/bin/',
command => "firewall-cmd --add-port=${zbx_listen_port}/tcp --permanent && firewall-cmd --reload",
onlyif => "firewall-cmd --list-all | grep ${zbx_listen_port} >/dev/null && echo False || echo True",
require => Package["$zbx_package"],
}
}
Здесь мы объявляем класс, имя класса состоит из названия модуля (setup_zbx_agent) + именем манифеста (install.pp). Внутри класса (круглые скобки) объявляются перемененные, которые будут использоваться по умолчанию. У нас две переменные:
zbx_package
- с именем пакета zabbix-агента;zbx_listen_port
- порт, на котором будет запускаться заббикс агент.
В следующем блоке, назовем его контекст выполнения, описываются ресурсы. В нашем кейсе используются ресурсы:
file
- копирует файл репозитория на сервер. В имя ресурса указываем путь расположения файла. А в атрибутах указываем желаемое состояние (ensure), и источник (require) откуда файл будет заполучен.package
- через этот ресурс ставить пакет заббикс агента. В имя ресурса передаем значение из переменной$zbx_package
, в атрибутах также указывается состояние. И новый для нас атрибут -require
, который создает зависимость от другого ресурса. То есть в нашем случаи данный ресурс выполнится, после ресурса -file
выше.exec
- в нашем примере через этот ресурс мы добавляем правило в firewalld. На самом деле для firewalld в puppet есть готовый модуль, которой можно заюзать. Но для примера, так скажем на коленке решил использовать exec. Из атрибутов:path
- указываем исполняемый каталог,command
- атрибут содержащий команду для исполнения. У нас команда на перманентное добавление порта. Сам порт подставляется из значения переменнойzbx_listen_port
.onlyif
- данный отрибут выполняет команду перед выполнением основной команды. И если в результате его выполнения возвращается результатTrue
, то будет выполнена основная команда. В нашем примере проверяется добавлен ли нужный порт.require
- также добавляется зависимость, но теперь только от ресурсаpackage
.
Напишим основной манифест, который будет являтся входной точкой в наш модуль. В данном сценарии будет инклюдится предыдущий манифест с установкой агента. Ну и далее будет собираться конфигурационный файл для агента:
[root@puppet-master production]# vim modules/setup_zbx_agent/manifests/init.pp
---
class setup_zbx_agent (
$zbx_server = 'localhost',
$zbx_host = "${facts['networking']['fqdn']}",
){
class {'setup_zbx_agent::install':}
file { '/etc/zabbix/zabbix_agentd.conf':
content => template('setup_zbx_agent/zabbix_agentd.erb'),
owner => 'zabbix',
group => 'zabbix',
}
service { 'zabbix-agent':
ensure => 'running',
subscribe => File['/etc/zabbix/zabbix_agentd.conf']
}
}
Объявляем новый класс, как правило первый класс в манифесте init.pp
, принято называть именем модуля.
В этой классе объявляются переменные по умолчанию - zbx_server
, zbx_host
. В значения переменных подставляется адрес zabbix-сервера, и fqdn-имя клиентского хоста. В контексте выполнения модуля мы добавляем инклюд, подключаем класс который устанавливает агента. Ну а затем следует знакомые нам ресурсы.
file
- из ранее созданного шаблона, создает конфигу zabbix-агента -/etc/zabbix/zabbix_agentd.conf
.service
- данный ресурс, запускает сервис агента. У сервиса есть атрибутsubscribe
, который запускает текущий ресурс при каждом изменении целевого ресурса. В нашем случаи целевым является ресурс -file
. Это означает, что при изменениях в шаблоне, каждый раз будет использоваться ресурсservice
.
Вобщем то на этом все, модуль готов. Остается только включить его в корневой манифест site.pp
:
node default {
class { 'setup_zbx_agent':
zbx_server => "10.8.10.7",
}
}
Здесь мы для всех агентов, инклюдим класс setup_zbx_agent
, в аргументах к классу переопределяем переменную - zbx_server
.
Проверяем через апдейт на агенте:
[root@puppet-hode01 ~]# /opt/puppetlabs/bin/puppet agent -t
Ну и смотрим появился ли агент на сервере:
[root@puppet-hode01 ~]# systemctl status zabbix-agent
● zabbix-agent.service - Zabbix Agent
Loaded: loaded (/usr/lib/systemd/system/zabbix-agent.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2023-09-06 16:40:11 +06; 6s ago
Process: 701651 ExecStart=/usr/sbin/zabbix_agentd -c $CONFFILE (code=exited, status=0/SUCCESS)
Main PID: 701653 (zabbix_agentd)
Tasks: 6 (limit: 11148)
Memory: 3.7M
CGroup: /system.slice/zabbix-agent.service
├─701653 /usr/sbin/zabbix_agentd -c /etc/zabbix/zabbix_agentd.conf
├─701654 /usr/sbin/zabbix_agentd: collector [idle 1 sec]
├─701655 /usr/sbin/zabbix_agentd: listener #1 [waiting for connection]
├─701656 /usr/sbin/zabbix_agentd: listener #2 [waiting for connection]
├─701657 /usr/sbin/zabbix_agentd: listener #3 [waiting for connection]
└─701658 /usr/sbin/zabbix_agentd: active checks #1 [idle 1 sec]
Sep 06 16:40:11 puppet-hode01.office.localdomain systemd[1]: Starting Zabbix Agent...
Sep 06 16:40:11 puppet-hode01.office.localdomain systemd[1]: Started Zabbix Agent.
Для понимания общей картинки взаимосвязей между объектами, придумал такую схемку:
И на этом моменте, думаю информации для первого знакомства будет достаточно, в следующих заметках поработаем с мастхев тулзой для puppet - hiera
. Также попробуем прикрутить дополнения например - PuppetDB.