Привет всем, возможно вы читали прошлый пост об использовании puppet. И там в качестве первого примера мы писали модуль, который ставит zabbix-агентов на хосты.

Там есть нюанс, этот модуль будет работать только с rhel-based операционными системами, а что если у нас огород из Debian/Ubuntu и Rhel? - Для решения этой задачи модифицируем ранее написанный модуль, добавим так сказать кроссплатформености.

Готовим новый хост

Для тестов, к текущей инфраструктуре добавил новую ноду на ubuntu22.04 (puppet-node03). Подготовим новую ноду, и заинставим на нее puppet-агента.

Сначала подключаем репозиторий puppet:

sadmin@puppet-node03:~$ wget https://apt.puppetlabs.com/puppet8-release-jammy.deb
sadmin@puppet-node03:~$ sudo dpkg -i puppet8-release-jammy.deb

Ну с ставим puppet-агента:

sadmin@puppet-node03:~$ sudo apt-get update && sudo apt-get install puppet-agent

В конфигурационном файле puppet-агента, добавляем настройки для подключения к серверу:

sadmin@puppet-node03:~$ sudo vim /etc/puppetlabs/puppet/puppet.conf
---
[main]
server = puppet-master.nixhub.ru
certname = puppet-node03.nixhub.ru
runinterval = 30

Ну и запускаем агента:

sadmin@puppet-node03:~$ sudo /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',
}

Подключаемся на puppet-server, и подписываем сертификат нового puppet-клиента:

[root@puppet-master ~]# puppetserver ca sign --certname puppet-node03.nixhub.ru
---
Successfully signed certificate request for puppet-node03.nixhub.ru

Модифицируем модуль (Первый способ)

Итак, теперь в подчинении у puppet два сервера на AlmaLinux и один сервер на Ubuntu. Модифицируем модуль установки zabbix-agent. Состав модуля такой:

[root@puppet-master production]# tree
.
|-- data
|-- environment.conf
|-- hiera.yaml
|-- manifests
|   `-- site.pp
`-- modules
    `-- setup_zbx_agent
        |-- files
        |   `-- zabbix.repo
        |-- manifests
        |   |-- init.pp
        |   `-- install.pp
        `-- templates
            `-- zabbix_agentd.erb

В манифестах у нас имеется две инструкции, первое описание устанавливаем zabbix-агента, второй манифест применяет конфигурацию агента. Разобьем задачу по установке на два манифеста:

|-- manifests
    |-- install_debian.pp
    |-- install_redhat.pp

В соответсвии с названием, каждый из манифестов будет ставить агента на соответствующую OS.

Манифест для Debian-совместимых

Добавим новый шаблон, из которого будет собиратся файл репозитория для ubuntu. Создаем новый шаблон:

[root@puppet-master production]# vim modules/setup_zbx_agent/templates/zabbix_ubuntu_repo.erb
---
# Zabbix main repository
deb https://repo.zabbix.com/zabbix/6.0/ubuntu <%= @release_name %> main
deb-src https://repo.zabbix.com/zabbix/6.0/ubuntu <%= @release_name %>  main

В значение переменной @release_name, будет подставляется имя релиза.

Затем собираем все вместе в файле - install_debian.pp:

[root@puppet-master production]# vim modules/setup_zbx_agent/manifests/install_debian.pp
--
class setup_zbx_agent::install_debian (
  $zbx_package          = 'zabbix-agent',
  $zbx_listen_port      = 10050,
  $release_name = "${facts['os']['distro']['codename']}"
) {
  file { '/etc/apt/sources.list.d/zabbix.list':
    content     => template('setup_zbx_agent/zabbix_ubuntu_repo.erb'),
  }

  package { "$zbx_package":
    ensure      => 'present',
    require     => File['/etc/apt/sources.list.d/zabbix.list'],
  }

  exec { 'ufw':
    path        => '/bin/',
    command     => "ufw allow ${zbx_listen_port}",
    require     => Package["$zbx_package"],
  }
}

В этом классе, ресурс file из шаблона zabbix_ubuntu_repo.erb собирает репозиторий zabbix. В шаблон подставляется значение из переменной, кодовое слово релиза ubuntu. Затем через ресурс package ставится пакет zabbix-agent. Ну и в конце через exec, добавляется порт на фаерволе. Обратите внимание, что каждый ресурс зависим от предыдущего.

Манифест для rhel-совместимых

Пишем второй манифест для rhel совместимых:

[root@puppet-master production]# vim modules/setup_zbx_agent/manifests/install_redhat.pp
---
class setup_zbx_agent::install_redhat (
  $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 10050 >/dev/null && echo False || echo True",
    require     => Package["$zbx_package"],
  }
}

Этот класс аналогично делает все тоже самое. За исключением файла репозитория, репозиторий просто копируется из файлов.

Обновляем init.pp

Модифицируем файл манифеста init.pp:

[root@puppet-master production]# cat modules/setup_zbx_agent/manifests/init.pp
---
class setup_zbx_agent (
  $zbx_server   = 'localhost',
  $zbx_host     = "${facts['networking']['fqdn']}",
){
  case $facts['os']['family'] {
    'Debian': {
      class {'setup_zbx_agent::install_debian':}
    }
    'RedHat': {
      class {'setup_zbx_agent::install_redhat':}
    }
  }

  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']
  }
}

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

После установки, собирается из шаблона файл конфигурации агента и перезапускается служба.

Модифицируем модуль (Второй способ, через параметры)

На мой взгляд второй способ мне показался более локаничный, и меньше повторяющего кода. Файл репозитория для rhel-совместимых операционок я перенес в шаблоны, но какие либо переменные не включал в его состав. B теперь каталог с шаблонами выглядит так:

`-- templates
    |-- zabbix_agentd.erb
    |-- zabbix_rhel_repo.erb
    `-- zabbix_ubuntu_repo.erb
  • zabbix_agentd.erb - шаблон для заббикс-агента;
  • zabbix_rhel_repo.erb - шаблон файла репозитория для rhel-дистрибутивов;
  • zabbix_ubuntu_repo.erb - шаблон файла репозитория для ubuntu совместимых.

Меняем init.pp

Менять модуль начнем в обратно последовательности, и сначала поменять init.pp:

[root@puppet-master manifests]# vim init.pp
---
class setup_zbx_agent (
  # Zabbix agent package:
  $zbx_package          = $setup_zbx_agent::params::zbx_params,

  # Zabbix config parameters:
  $zbx_server           = $setup_zbx_agent::params::zbx_server,
  $zbx_port             = $setup_zbx_agent::params::zbx_port,
  $zbx_host             = $setup_zbx_agent::params::zbx_host,

  # Zabbix repo path:
  $zbx_repo_path        = $setup_zbx_agent::params::zbx_repo_path,
  $zbx_repo_temp        = $setup_zbx_agent::params::zbx_repo_temp,

  # Host firewall settings:
  $fwd_type             = $setup_zbx_agent::params::fwd_type,
  $fwd_command          = $setup_zbx_agent::params::fwd_command,
  $fwd_command_path     = $setup_zbx_agent::params::fwd_command_path,

) inherits setup_zbx_agent::params {
  class {'setup_zbx_agent::install':} ->
  class {'setup_zbx_agent::configure':}
} 

Описание класса setup_zbx_agent, мы начинаем с объявления переменных. Значения этих переменных будут взяты из другого манифеста (params), который мы напишем позже. Для указания ссылки на переменную в другом манифесте используется такой синтаксис:

  • <имя_модуля>::<имя_манифеста>::<имя_переменной>

В данном классе используются переменные:

  • $zbx_package - имя пакета zabbix-агента;
  • $zbx_server, $zbx_port, $zbx_host - в этих переменных будут содержаться данные ip zabbix-сервера, затем имя хоста и порт на котором будет запускаться агент.
  • $zbx_repo_path - в значение этой переменной мы помещаем путь, по которому будет создаваться файл репозитория на хостах.
  • $zbx_repo_temp - здесь в значение помещаем имя шаблона, который будет использоваться.
  • $fwd_type, $fwd_command, $fwd_command_path - а значениях этих переменных кладем тип используемого фаервола (Например: ufw или firewalld). Так как правило в фаере добавляем через ресурс exec, соответственно в переменной $fwd_command - shell команда на добавление правила. В последней переменной путь к утилите.

Так скажем контекст исполнения, начинается с указания наследования класса -> inherits setup_zbx_agent::params. И в самом контексте включены два других манифеста.

  • setup_zbx_agent::install - в этот манифест вынесены все ресурсы по установке zabbix-агента,
  • setup_zbx_agent::configure - тут перечеслены ресурсы по конфигурации агента.

Оператор -> между классами, говорит нам о том, что перед выполнением класса setup_zbx_agent::configure, сначала будет выполнен класс setup_zbx_agent::install.

Пишем манифест с параметрами

Далее логичнее будет описать класс с параметрами:

[root@puppet-master manifests]# vun params.pp
---
class setup_zbx_agent::params {
  $zbx_package	= 'zabbix-agent'
  $zbx_server   = 'localhost'
  $zbx_port     = '10050'
  $zbx_host     = "${facts['networking']['fqdn']}"
  $release_name	= "${facts['os']['distro']['codename']}"

  case "${facts['os']['family']}" {
    'Debian': {
      $zbx_repo_temp	= 'zabbix_ubuntu_repo'
      $zbx_repo_path	= '/etc/apt/sources.list.d/zabbix.list'
      $fwd_type		= "ufw"
      $fwd_command	= "ufw allow ${zbx_port}"
      $fwd_command_pre	= ""
      $fwd_command_path	= "/usr/sbin/"
    }
    'RedHat': {
      $zbx_repo_temp	= 'zabbix_rhel_repo'
      $zbx_repo_path	= '/etc/yum.repos.d/zabbix.repo'
      $fwd_type		= "firewalld"
      $fwd_command	= "firewall-cmd --add-port=${zbx_port}/tcp --permanent && firewall-cmd --reload"
      $fwd_command_pre	= ''
      $fwd_command_path	= "/bin/"
    }
  }
}

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

Отдельные классы install/configure

Пишем манифест на установку агента:

[root@puppet-master manifests]# vim install.pp
---
class setup_zbx_agent::install {
  file { $setup_zbx_agent::zbx_repo_path:
    content     => template("setup_zbx_agent/${setup_zbx_agent::zbx_repo_temp}.erb")
  }

  package { "$setup_zbx_agent::zbx_package":
    ensure      => 'present',
    require     => File["${setup_zbx_agent::zbx_repo_path}"],
  }

  exec { "$setup_zbx_agent::fwd_type":
    path        => "${setup_zbx_agent::fwd_command_path}",
    command     => "${setup_zbx_agent::fwd_command}",
    require     => Package["${setup_zbx_agent::zbx_package}"],
  }
}

В этом классе содержатся знакомые нам ресурсы:

  • file - на создание файла репозитория из шаблона,
  • package - для установки пакета,
  • exec - для выполнения команды на добавления правила в firewall. Атрибуты к ресурсам строятся из импортированных переменных главного класса.

И наконец напишем манифест, на конфигурирование агента:

class setup_zbx_agent::configure {
  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']
  }
}

Тут нечего не поменялось, также собирается конфиг агента, и перезапускается служба.

Обновляем шаблоны

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

[root@puppet-master manifests]# cat ../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=<%= scope.lookupvar("setup_zbx_agent::zbx_server")  %>
ServerActive=<%= scope.lookupvar("setup_zbx_agent::zbx_server") %>

# Zabbix-agent hostname
Hostname=<%= scope.lookupvar("setup_zbx_agent::zbx_host") %>

# Includes extentions:
Include=/etc/zabbix/zabbix_agentd.d/*.conf

В ответах на stack overflow нашел метод указания переменных в шаблоне через вызов метода - scope.lookupvar(), который предопределяет область видимости. И это сработало.

Итак мы попробовали оба метода, вариант со вторым методом выложу в Gitlab.