Migracija z Nagiosa na Icinga2 v Avstraliji

Hello everyone.

Sem jazdil linux sistemski administrator, ki se je leta 2015 preselil iz Rusije v Avstralijo po neodvisni poklicni vizumi, vendar članek ne bo o tem, kako je svinja dobila traktor. Takih člankov je že dovolj (vendar, če bo zanimanje — bom napisal tudi o tem), zato bi rad govoril o tem, kako sem na svojem delu v Avstraliji kot linux-ops inženir sprožil migracijo z enega sistema za spremljanje na drugega. Konkretno — Nagios => Icinga2.

Članek je deloma tehnični in deloma o komunikaciji z ljudmi ter težavah, povezanih z razlikami v kulturi in delovnih metodah.

Žal oznaka „code“ ne osvetli kode Puppet in yaml, zato sem moral uporabiti „plaintext“.

Nič ni napovedovalo težav zjutraj 21. decembra 2016. Kot običajno sem bral Habr kot neregistriran anonimnež v prvih pol urah delovnega dne, ob tem sem pil kavo in naletel na ta članek..

Ker je v moji podjetju že uporabljal Nagios, sem brez oklevanja odprl tiket v Redmine in poslal povezavo v splošni klepet, ker sem to štel za pomembno. Iniciativa je kaznovana tudi v Avstraliji, zato mi je glavni inženir naložil to težavo, ker sem jo odkril.

Posnetek iz Redmine.Migracija z Nagiosa na Icinga2 v Avstraliji

V našem oddelku je pred predstavo svojega mnenja običajno predlagati vsaj eno alternativo, tudi če je izbira očitna, zato sem začel z iskanjem, katere sistem za spremljanje so trenutno relevantne, ker sem v Rusiji na zadnjem delovnem mestu imel svojo lastno napisano sistem, zelo primitivno, a vendar povsem funkcionalno in izpolnjujočo vse naloge, ki so bile pred njo. Python, Peterburški Politehnik in metro so zakon. Ne, metro je slab. To je osebno (11 let dela) in zasluži ločen članek, vendar ne zdaj.

Malce o pravilih za uvajanje sprememb v infrastrukturo na mojem trenutnem delovnem mestu. Imamo Puppet, Gitlab in načelo Infrastructure as Code, zato:

  • Nobenih ročnih sprememb preko SSH z ročnim spreminjanjem datotek na virtualnih strojih. V treh letih dela sem za to večkrat prejel opozorila, zadnje pred enim tednom, in ne mislim, da je bilo to zadnjič. No, res je — popraviti eno vrstico v konfiguraciji, ponovno zagnati storitev in preveriti, ali je težava rešljena — 10 sekund. Ustvariti novo vejo v Gitlabu, potisniti spremembe, počakati, da r10k deluje na Puppetmasterju, zagnati Puppet —environment=mybranch in še nekaj minut počakati, da vse to deluje — vsaj 5 minut.
  • Vsake spremembe se izvajajo z ustvarjanjem Merge Requesta v Gitlabu in potrebno je pridobiti odobritev vsaj od enega člana ekipe. Resne spremembe zahtevajo odobritev dveh ali treh članov, v skladu z odločitvijo vodje tima.
  • Vse spremembe so na nek način besedilne (saj so Puppet manifesti, skripti in Hiera podatki besedilo), binarne datoteke so zelo nezaželene in za odobritev takih datotek so potrebni močni razlogi.

Torej, možnosti, ki sem jih preučil:

  • Munin — če je v infrastrukturi več kot 10 strežnikov, upravljanje postane prava nočna mora (iz ovog članka. Nisem imel posebne želje, da to preverjam, zato sem verjel na besedo).
  • Zabbix — že dolgo sem ga opazoval, še v Rusiji, a takrat je bil pretežak za moje potrebe. Tukaj sem moral zavrniti zaradi uporabe Puppet kot sistema za upravljanje konfiguracij in Gitlab kot sistema za nadzor različic. Po mojem razumevanju je Zabbix shranjeval celotno konfiguracijo v bazo podatkov, zaradi česar ni bilo jasno, kako upravljati konfiguracijo v trenutnih pogojih in kako spremljati spremembe.
  • Prometheus — tisto, čemur bomo na koncu sledili, sodeč po razpoloženju v oddelku, vendar takrat ga nisem obvladal in nisem mogel pokazati dejanske delujoče različice (dokaz koncepta), zato sem se moral odpovedati.
  • Bilo je še nekaj drugih možnosti, ki so bodisi zahtevale popolno predelavo sistema bodisi so bile v začetni fazi / zapuščene in iz tega razloga zavrnjene.

Na koncu sem se odločil za Icinga2 iz treh razlogov:

1 — kompatibilnost s Nrpe (klijentskom uslugom koja pokreće provjere prema naredbama iz Nagiosa). Ovo je bilo jako važno jer smo tada imali 135 (sada u 2019. godini 165) virtualnih mašina s gomilom vlastitih servisa/provjera, a prepraviti sve to bi bilo pravo mučenje.
2 — svi konfiguracijski datoteke su tekstualne, što omogućava lako uređivanje, stvaranje merge zahtjeva uz mogućnost da se vidi što je dodano ili uklonjeno.
3 — to je živopisan i razvijajući se OpenSource projekt. Jako volimo OpenSource i doprinosimo mu stvaranjem Pull zahtjeva i Issues za rješavanje problema.

Dakle, krenimo, Icinga2.

Prvo s čime smo se suočili — inertnost kolega. Svi su navikli na Nagios/Nadjius (i ovdje nismo mogli doći do kompromisa kako to izgovarati) i sučelje CheckMK. Sa icinga sučelje izgleda potpuno drugačije (to je bio minus), ali postoji mogućnost fleksibilnog podešavanja onoga što treba vidjeti pomoću filtara doslovno prema bilo kojem parametru (to je bio plus, ali za to sam se jako borio).

FilteriMigracija z Nagiosa na Icinga2 v Avstraliji

Procijenite odnos veličine scrollbara prema veličini područja za pomicanje.

Drugo — svi su navikli vidjeti cijelu infrastrukturu na jednom ekranu, jer CheckMk omogućava rad s više hostova Nagios, ali Icinga sučelje to nije moglo (zapravo je moglo, ali o tome dolje). Alternativa je bila stvar pod nazivom Thruk, ali njen dizajn je izazivao povraćanje kod svih članova tima, osim jednog — onog tko je to predložio (nisam ja).

U smeće s Thrukom — jednoglasna odluka tima.Migracija z Nagiosa na Icinga2 v Avstraliji

Po nekaj dneh možganske nevihte sem predlagal idejo o klaster monitoring, kjer je en glavni strežnik v proizvodni coni in dva podrejena — eden v dev/test in en zunanji strežnik, ki je lociran pri drugem ponudniku, z namenom nadziranja naših storitev z vidika stranke ali zunanjega opazovalca. Takšna konfiguracija je omogočila, da smo vse težave videli v enem spletnem vmesniku in je popolnoma delovala, vendar Puppet... Problem s Puppetom je bil, da je moral zdaj glavni strežnik vedeti vse o strežnikih in storitvah/preverjanjih v sistemu ter jih razdeliti med cone (dev-test, staging-prod, ext), toda pošiljanje sprememb preko Icinga API traja nekaj sekund, medtem ko je kompilacija imenika Puppet vseh storitev za vse strežnike trajala nekaj minut. To mi še vedno očitajo, čeprav sem že večkrat razložil, kako vse deluje in zakaj to traja toliko časa.

Tretje — kupa SnowFlakes (snežink) — stvari, ki izstopajo iz splošnega sistema, ker imajo nekaj posebnega, zato se splošna pravila na njih ne morejo uporabiti. To se je rešilo s frontalnim napadom — če so bile alarmi, ampak dejansko je bilo vse v redu, pomeni, da je bilo treba kopati globlje in ugotoviti, zakaj me opozarja, čeprav ne bi smelo. Ali obratno — zakaj Nagios panikuje, medtem ko Icinga ne.

Četrto — Nagios je delal tukaj tri leta pred mano in mu je bilo sprva zaupano več kot moji modernejši hipsterski rešitvi, zato, vsakokrat ko je Icinga povzročila paniko — nihče ni naredil nič, dokler se Nagios ni vzburjal zaradi istega vprašanja. Vendar zelo redko je Icinga izdala prave opozorila prej kot Nagios in menim, da je to resna napaka, o kateri bom govoril v razdelku 'Zaključki.'

Na koncu se je uvajanje zavleklo več kot 5 mesecev (načrtovano za 28. junij 2018, dejansko pa 3. december 2018), predvsem zaradi 'parity check' — tista zadeva, ko obstajajo več storitev v Nagiosu, o katerih nihče ni slišal zadnjih nekaj let, vendar SO TEČAJ ODGOVORNEN! in me je moral razložiti, zakaj jih ni na moji plošči, in jih dodati v Icinga, da bi 'parity check is complete' (Vse storitve/preverjanja v Nagiosu ustrezajo storitvam/preverjanju v Icingi).

Uvajanje:
Prvo — rat između Koda i Podataka, tipa Puppet stil. Svi podaci, baš svi, moraju biti u Hiera i nikako drugačije. Sav kod — u .pp datotekama. Varijable, apstrakcije, funkcije — sve ide u pp.
Na kraju — imamo hrpu virtualnih mašina (165 u trenutku pisanja članka) i 68 web-aplikacija koje je potrebno pratiti u vezi s funkcionalnošću i valjanosti SSL certifikata. No zbog povijesne komplikacije, informacije za praćenje aplikacija dolaze iz zasebnog gitlab repozitorija i format podataka se nije mijenjao od vremena Puppet 3, što stvara dodatne poteškoće u konfiguraciji.

Puppet-kod za aplikacije, čuvajte oči

define profiles::services::monitoring::docker_apps(
  Hash $app_list,
  Hash $apps_accessible_from,
  Hash $apps_access_list,
  Hash $webhost_defaults,
  Hash $webcheck_defaults,
  Hash $service_overrides,
  Hash $targets,
  Hash $app_checks,
  )
{
#### APPS ####
  $zone = $name
  $app_list.each | String $app_name, Hash $app_data |
  {

    $notify_group = { 'notify_group' => ($webcheck_defaults[$zone]['notify_group'] + pick($app_data['notify_group'], {} )) } # добавляет уведомления для группы по умолчанию (системы) + любую группу, определенную в int/pm_docker_apps.eyaml

    $data = merge($webhost_defaults, $apps_accessible_from, $app_data)

    $site_domain = $app_data['site_domain']

    $regexp = pick($app_data['check_regex'], 'html')        # Выбор регулярного выражения для проверки

    $check_url = $app_data['check_url'] ? {
      undef   => { 'http_uri' => '\/ },
      default => { 'http_uri' => $app_data['check_url'] }
    }

    $check_regex = $regexp ?{
      'absent' => {},
      default  => {'http_expect_body_regex' => $regexp}
    }

    $site_domain.each | String $vhost, Hash $vdata | {        # Разделение приложения по доменам, если их два или более
      $vhost_name = {'http_vhost' => $vhost}

      $vars = $data['vars'] + $vhost_name + $check_regex + $check_url

      $web_ipaddress = is_array($vdata['web_ipaddress']) ? {  # Создание массива IP-адресов, если он не является массивом, так как askizzy имеет 2 IP и это массив
        true  => $vdata['web_ipaddress'],
        false => [$vdata['web_ipaddress']],
      }

      $access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Объединение зоны по умолчанию (где определяется приложение) и дополнительных зон, если они существуют
      $web_ipaddress.each | String $ip_address | {            # Для каждого IP (если их несколько)
        $suffix = length($web_ipaddress) ? {                  # Если их больше одного - добавляем IP в качестве суффикса к этому имени хоста, чтобы избежать дублирования ресурсов
          1       => '',
          default => "_${ip_address}"
        }
        $octets = split($ip_address, '.')
        $ip_tag = "${octets[2]}.${octets[3]}" # Использование последнего октета только вызывает коллизию между nginx-vip 203.15.70.94 и ext. ip 49.255.194.94

        $access_from_zones.each | $zone_prefix |{
          $zone_target = $targets[$zone_prefix]

          $nginx_vip_name = "${zone_prefix}_nginx-vip-${ip_tag}" # Если это хост для ext - префикс становится 'ext_' (ext_nginx-vip...)
          $nginx_host_vip = {
            $nginx_vip_name => {
              ensure        => present,
              target        => $zone_target,
              address       => $ip_address,
              check_command => 'hostalive',
              groups        => ['nginx_vip',],
            }
          }

          $ssl_vars = $app_checks['ssl']
          $regex_vars = $app_checks['http'] + $vars + $webcheck_defaults[$zone] + $notify_group

          if !defined( Profiles::Services::Monitoring::Host[$nginx_vip_name] ) {
          ensure_resources('profiles::services::monitoring::host', $nginx_host_vip)
          }

          if !defined( Icinga2::Object::Service["${nginx_vip_name}_ssl"] ) {
            icinga2::object::service {"${nginx_vip_name}_ssl":
              ensure         => $data['ensure'],
              assign         => ["host.name == $nginx_vip_name",],
              groups         => ['webchecks',],
              check_command  => 'ssl',
              check_interval => $service_overrides['ssl']['check_interval'],
              target         => $targets['services'],
              apply          => true,
              vars           => $ssl_vars
            }
          }
          if $regexp != 'absent'{
            if !defined(Icinga2::Object::Service["${vhost}${$suffix} regex"]){
              icinga2::object::service {"${vhost}${$suffix} regex":
                ensure          => $data['ensure'],
                assign          => ["match(*_nginx-vip-${ip_tag}, host.name)",],
                groups          => ['webchecks',],
                check_command   => 'http',
                check_interval  => $service_overrides['regex']['check_interval'],
                target          => $targets['services'],
                enable_flapping => true,
                apply           => true,
                vars            => $regex_vars
              }
            }
          }
        }
      }
    }
  }
}

Koda za konfiguracijo gostiteljev in storitev izgleda grozno:

monitoring/config.pp


class profiles::services::monitoring::config(
  Array $default_config,
  Array $hostgroups,
  Hash $hosts = {},
  Hash $host_defaults,
  Hash $services,
  Hash $service_defaults,
  Hash $service_overrides,
  Hash $webcheck_defaults,
  Hash $servicegroups,
  String $servicegroup_target,
  Hash $user_defaults,
  Hash $users,
  Hash $oncall,
  Hash $usergroup_defaults,
  Hash $usergroups,
  Hash $notifications,
  Hash $notification_defaults,
  Hash $notification_commands,
  Hash $timeperiods,
  Hash $webhost_defaults,
  Hash $apps_access_list,
  Hash $check_commands,
  Hash $hosts_api = {},
  Hash $targets = {},
  Hash $host_api_defaults = {},
)
{

  # Profiles::Services::Monitoring::Hostgroup <<| |>> # будет включен, когда мы полностью перейдем на icinga
#### APPS ####
  case $location {
    'int', 'ext': {
      $apps_by_zone = {}
    }
    'pm': {
      $int_apps         = hiera('int_docker_apps')
      $int_app_defaults = hiera('int_docker_app_common')

      $st_apps          = hiera('staging_docker_apps')
      $srs_apps         = hiera('pm_docker_apps_srs')
      $pm_apps          = hiera('pm_docker_apps') + $st_apps + $srs_apps
      $pm_app_defaults  = hiera('pm_docker_app_common')

      $apps_by_zone = {
        'int' => $int_apps,
        'pm'  => $pm_apps,
      }

      $app_access_by_zone = {
        'int' => {'accessible_from' => $int_app_defaults['accessible_from']},
        'pm'  => {'accessible_from' => $pm_app_defaults['accessible_from']},
      }
    }

    default: {
      fail('Пожалуйста, убедитесь, что у узла установлен факт $location (int, pm, ext)')
    }
  }

  file { '"/etc/icinga2/conf.d/":
    ensure  => directory,
    recurse => true,
    purge   => true,
    owner   => 'icinga',
    group   => 'icinga',
    mode    => '0750',
    notify  => Service['icinga2'],
  }

  $default_config.each | String $file_name |{
    file {"/etc/icinga2/conf.d/${file_name}":
      ensure => present,
      source => "puppet://modules/profiles/services/monitoring/default_config/${file_name}",
      owner  => 'icinga',
      group  => 'icinga',
      mode    => '0640',
    }
  }

  $app_checks = {
    'ssl' => $services['webchecks']['checks']['ssl']['vars'],
    'http' => $services['webchecks']['checks']['http_regexp']['vars']
  }

  $apps_by_zone.each | String $zone, Hash $app_list | {
    profiles::services::monitoring::docker_apps{$zone:
      app_list             => $app_list,
      apps_accessible_from => $app_access_by_zone[$zone],
      apps_access_list     => $apps_access_list,
      webhost_defaults     => $webhost_defaults,
      webcheck_defaults    => $webcheck_defaults,
      service_overrides    => $service_overrides,
      targets              => $targets,
      app_checks           => $app_checks,
    }
  }

####    HOSTS    ####

  # Profiles::Services::Monitoring::Host <<| |>> # Это для захвата космического корабля, когда он будет готов.
  $hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')

  $hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | {           # Делим списки сайтов по группам хостов - docker_host/gluster_host/и т.д.
    $list_of_hosts_in_group = $list_of_hosts_with_settings['hosts']
    $hostgroup_settings     = $list_of_hosts_with_settings['settings']
    $merged_hostgroup_settings = deep_merge($host_defaults, $list_of_hosts_with_settings['settings'])
    $list_of_hosts_in_group.each | String $host_name, Hash $host_settings |{  # Делим списки группы по хостам
      # Является ли этот хост в массиве $hosts_has_large_disks? Если да, установите host.vars.has_large_disks
      if ( $hosts_has_large_disks.reduce(false) | $found, $value| { ( $value =~ "^${host_name}" ) or $found } ) {
        $vars_has_large_disks = { 'has_large_disks' => true }
      } else {
        $vars_has_large_disks = {}
      }
      $host_data = deep_merge($merged_hostgroup_settings, $host_settings)
      $hostgroup_settings_vars = pick($hostgroup_settings['vars'], {})
      $host_settings_vars = pick($host_settings['vars'], {})
      $host_notify_group = delete_undef_values($host_defaults['vars']['notify_group'] + $hostgroup_settings_vars['notify_group'] + $host_settings_vars['notify_group'])
      $host_data_vars = delete_undef_values(deep_merge($host_data['vars'] , {'notify_group' => $host_notify_group}, $vars_has_large_disks)) # Объединение переменных по отдельности

      $hostgroups = delete_undef_values([$hostgroup] + $host_data['groups'])

      profiles::services::monitoring::host{$host_name:
        ensure             => $host_data['ensure'],
        display_name       => $host_data['display_name'],
        address            => $host_data['address'],
        groups             => $hostgroups,
        target             => $host_data['target'],
        check_command      => $host_data['check_command'],
        check_interval     => $host_data['check_interval'],
        max_check_attempts => $host_data['max_check_attempts'],
        vars               => $host_data_vars,
        template           => $host_data['template'],
      }
    }
  }
  if !empty($hosts_api){                                                                # Все хосты управляются API
    $hosts_api.each | String $zone, Hash $hosts_api_zone | {                            # Делим api хосты по зонам
      $hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | {   # Делим списки сайтов по группам хостов - docker_host/gluster_host/и т.д.
        $list_of_hosts_in_group = $list_of_hosts_with_settings['hosts']
        $hostgroup_settings     = $list_of_hosts_with_settings['settings']
        $merged_hostgroup_settings = deep_merge($host_api_defaults, $list_of_hosts_with_settings['settings'])
        $list_of_hosts_in_group.each | String $host_name, Hash $host_settings |{        # Делим списки группы по хостам
          # Является ли этот хост в массиве $hosts_has_large_disks? Если да, установите host.vars.has_large_disks
          if ( $hosts_has_large_disks.reduce(false) | $found, $value| { ( $value =~ "^${host_name}" ) or $found } ) {
            $vars_has_large_disks = { 'has_large_disks' => true }
          } else {
            $vars_has_large_disks = {}
          }
          $host_data = deep_merge($merged_hostgroup_settings, $host_settings)
          $hostgroup_settings_vars = pick($hostgroup_settings['vars'], {})

          $host_settings_vars = pick($host_settings['vars'], {})
          $host_api_notify_group = delete_undef_values($host_defaults['vars']['notify_group'] + $hostgroup_settings_vars['notify_group'] + $host_settings_vars['notify_group'])
          $host_data_vars = delete_undef_values(deep_merge($host_data['vars'] , {'notify_group' => $host_api_notify_group}, $vars_has_large_disks))
          $hostgroups = delete_undef_values([$hostgroup] + $host_data['groups'])

          if defined(Profiles::Services::Monitoring::Host[$host_name]){
            $hostname = "${host_name}_from_${zone}"
          }
          else
          {
            $hostname = $host_name
          }
          profiles::services::monitoring::host{$hostname:
            ensure             => $host_data['ensure'],
            display_name       => $host_data['display_name'],
            address            => $host_data['address'],
            groups             => $hostgroups,
            target             => "${host_data['target_base']}/${zone}/hosts.conf",
            check_command      => $host_data['check_command'],
            check_interval     => $host_data['check_interval'],
            max_check_attempts => $host_data['max_check_attempts'],
            vars               => $host_data_vars,
            template           => $host_data['template'],
          }
        }
      }
    }
  }

#### КОНЕФ ХОСТОВ ####

####   УСЛУГИ   ####

  $services.each | String $service_group, Hash $s_list |{             # Группа услуг и список услуг в этой группе
    $service_list = $s_list['checks']                                 # Список фактических проверок, отдельно от настроек SG
    $service_list.each | String $service_name, Hash $data |{

      $merged_defaults = merge($service_defaults, $s_list['settings']) # глобальные настройки по умолчанию для услуг + настройки группы услуг
      $merged_data = merge($merged_defaults, $data)

      $settings_vars = pick($s_list['settings']['vars'], {})
      $this_service_vars = pick($data['vars'], {})
      $all_service_vars = delete_undef_values($service_defaults['vars'] + $settings_vars + $this_service_vars)

      # Если мы переопределяем значение check_timeout по умолчанию, но не nrpe_timeout, сделайте nrpe_timeout таким же, как check_timeout
      if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
        # NB: Icinga автоматически преобразует 1m в 60!
        $nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
      } else {
        $nrpe = {}
      }

      # По умолчанию мы используем nrpe, и все команды выполняются через nrpe. Таким образом, vars.nrpe_command = $service_name - это значение по умолчанию
      # Если это команда Icinga на стороне сервера - нам не нужно 'nrpe_command'
      # но нет никакого вреда в том, чтобы иметь эту переменную, а код короче

      if $merged_data['check_command'] == 'nrpe'{
        $check_command = $merged_data['vars']['nrpe_command'] ? {
          undef   => { 'nrpe_command' => $service_name },
          default => { 'nrpe_command' => $merged_data['vars']['nrpe_command'] }
        }
      }else{
        $check_command = {}
      }

      # Формирование $vars от глобальных настроек по умолчанию для услуг, настроек группы услуг, настроек этой конкретной проверки и не забудем о настройках nrpe.
      if $all_service_vars['graphite_template'] {
        $graphite_template = {'check_command' => $all_service_vars['graphite_template']}
      }else{
        $graphite_template = {'check_command' => $service_name}
      }
      $service_notify = [] + pick($settings_vars['notify_group'], []) + pick($this_service_vars['notify_group'], []) # pick требуется повсюду, иначе становится "Значение '' не может быть преобразовано в Numeric"

      $service_notify_group = $service_notify ? {
        []      => $service_defaults['vars']['notify_group'],
        default => $service_notify
      } # Назначить группу по умолчанию (системы), если не определены другие группы

      $vars = $all_service_vars + $nrpe + $check_command + $graphite_template + {'notify_group' => $service_notify_group}

      # Это нужно объединять отдельно, потому что объединение его как части MERGED_DATA переписывает массивы, и мы теряем некоторые значения "assign" и "ignore"

      $assign = delete_undef_values($service_defaults['assign'] + $s_list['settings']['assign'] + $data['assign'])
      $ignore = delete_undef_values($service_defaults['ignore'] + $s_list['settings']['ignore'] + $data['ignore'])

      icinga2::object::service {$service_name:
        ensure             => $merged_data['ensure'],
        apply              => $merged_data['apply'],
        enable_flapping    => $merged_data['enable_flapping'],
        assign             => $assign,
        ignore             => $ignore,
        groups             => [$service_group],
        check_command      => $merged_data['check_command'],
        check_interval     => $merged_data['check_interval'],
        check_timeout      => $merged_data['check_timeout'],
        check_period       => $merged_data['check_period'],
        display_name       => $merged_data['display_name'],
        event_command      => $merged_data['event_command'],
        retry_interval     => $merged_data['retry_interval'],
        max_check_attempts => $merged_data['max_check_attempts'],
        target             => $merged_data['target'],
        vars               => $vars,
        template           => $merged_data['template'],
      }
    }
  }
#### КОНЕЦ УСЛУГ ####

#### ДРУГИЕ СКУЧНЫЕ ВЕЩИ ####

  $servicegroups.each | $servicegroup, $description |{
    icinga2::object::servicegroup{ $servicegroup:
      target       => $servicegroup_target,
      display_name => $description
    }
  }

  $hostgroups.each| String $hostgroup |{
    profiles::services::monitoring::hostgroup { $hostgroup:}
  }

  $notifications.each | String $name, Hash $settings |{

    $assign = pick($notification_defaults['assign'], []) + $settings['assign']
    $ignore = pick($notification_defaults['ignore'], []) + $settings['ignore']

    $merged_settings = $settings + $notification_defaults

    icinga2::object::notification{$name:
      target       => $merged_settings['target'],
      apply        => $merged_settings['apply'],
      apply_target => $merged_settings['apply_target'],
      command      => $merged_settings['command'],
      interval     => $merged_settings['interval'],
      states       => $merged_settings['states'],
      types        => $merged_settings['types'],
      assign       => delete_undef_values($assign),
      ignore       => delete_undef_values($ignore),
      user_groups  => $merged_settings['user_groups'],
      period       => $merged_settings['period'],
      vars         => $merged_settings['vars'],
    }
  }

  # Объединение настроек уведомлений для пользователей с другими настройками
  $users_oncall = deep_merge($users, $oncall)
  # Магия. Не трогать.
  create_resources('icinga2::object::user', $users_oncall, $user_defaults)
  create_resources('icinga2::object::usergroup', $usergroups, $usergroup_defaults)
  create_resources('icinga2::object::timeperiod',$timeperiods)
  create_resources('icinga2::object::checkcommand', $check_commands)
  create_resources('icinga2::object::notificationcommand', $notification_commands)

  profiles::services::sudoers { 'icinga_runs_ping_l2':
    ensure            => present,
    sudoersd_template => 'profiles/os/redhat/centos7/sudoers/icinga.erb',
  }

}

Še vedno delam na tej zadevi in jo izboljšujem po svojih najboljših močeh. Vendar pa je prav takšna koda omogočila uporabo preprostega in razumljivega sintaktičnega slogana v Hieri:

Podatki

profiles::services::monitoring::config::services:
  perf_checks:
    settings:
      check_interval: '2m'
      assign:
        - 'host.vars.type == linux'
    checks:
      procs: {}
      load: {}
      memory: {}
      disk:
        check_interval: '5m'
        vars:
          notification_period: '24x7'
      disk_iops:
        vars:
          notifications:
            - 'silent'
      cpu:
        vars:
          notifications:
            - 'silent'
      dns_fqdn:
        check_interval: '15m'
        ignore:
          - 'xenserver in host.groups'
        vars:
          notifications:
            - 'silent'
      iftraffic_nrpe:
        vars:
          notifications:
            - 'silent'
  logging:
    settings:
      assign:
        - 'logserver in host.groups'
    checks:
       rsyslog: {}
      nginx_limit_req_other: {}
      nginx_limit_req_s2s: {}
      nginx_limit_req_s2x: {}
      nginx_limit_req_srs: {}
     logstash: {}
      logstash_api:
        vars:
          notifications:
            - 'silent'

Vsi pregledi so razdeljeni v skupine, pri čemer ima vsaka skupina privzete nastavitve o tem, kje in kako pogosto izvajati te preglede, katere obvestila pošiljati in komu.

Pri vsakem pregledu je mogoče prepisati katero koli opcijo, vse skupaj pa se sešteje še s privzetimi nastavitvami vseh pregledov kot celote. Zato je v config.pp napisana taka zadeva — tam poteka združevanje vseh privzetih nastavitev s skupinskimi nastavitvami in nato še z vsakim posameznim pregledom.

Prav tako je zelo pomembna sprememba možnost uporabe funkcij v nastavitvah, na primer, funkcija za nadomestitev vrat, naslova in url za http_regex pregled.

http_regexp:
  assign:
    - 'host.vars.http_regex'
    - 'static_sites in host.groups'
  check_command: 'http'
  check_interval: '1m'
  retry_interval: '20s'
  max_check_attempts: 6
  http_port: '{{ if(host.vars.http_port) { return host.vars.http_port } else { return 443 } }}'
  vars:
    notification_period: 'host.vars.notification_period'
    http_vhost: '{{ if(host.vars.http_vhost) { return host.vars.http_vhost } else { return host.name } }}'
    http_ssl: '{{ if(host.vars.http_ssl) { return false } else { return true } }}'
    http_expect_body_regex: 'host.vars.http_regex'
    http_uri: '{{ if(host.vars.http_uri) { return host.vars.http_uri } else { return "\/" } }}'
    http_onredirect: 'follow'
    http_warn_time: 8
    http_critical_time: 15
    http_timeout: 30
    http_sni: true

To pomeni — če je v definiciji gostitelja spremenljivka http_port — jo uporabiti, sicer 443. Na primer, spletni vmesnik jabber deluje na 9090, Unifi pa na 7443.
http_vhost pomeni ignorirati DNS in vzeti točno ta naslov.
Če je v gostitelju določen uri — ga vzeti, sicer vzeti "\/".

Z http_ssl se je zgodila zabavna zgodba — ta nepridiprav se nikakor ni hotel izklopiti na zahtevo. Dolgo sem se trudil z to vrstico, dokler mi ni postalo jasno, da je v spremenljivki v definiciji gostitelja:

http_ssl: false

Zamenjava v izraz

if(host.vars.http_ssl) { return false } else { return true }

kako false in na koncu dobimo

if(false) { return false } else { return true }

to pomeni, da je ssl preverjanje vedno aktivno. Rešilo se je z zamenjavo sintakse:

http_ssl: ne

Zaključki:

Prednosti:

  • Zdaj imamo en sistem za spremljanje, ne dva, kot je bilo v zadnjih 7-8 mesecih, ali en zastarel in ranljiv.
  • Struktura podatkov o gostiteljih / storitvah (preverjanj) je zdaj (po mojem mnenju) mnogo bolj berljiva in razumljiva. Za druge to ni bilo tako očitno, zato sem moral pripraviti nekaj strani v lokalni wiki za razlago, kako vse deluje in kaj je treba spremeniti.
  • Obstaja možnost prilagodljive nastavitve preverjanj s pomočjo spremenljivk in funkcij, na primer za preverjanje http_regexp bo iskan vzorec, koda vrnitve, URL in vrata mogoče nastaviti v nastavitvah gostitelja.
  • Obstaja več nadzornih plošč (dashboards), za vsako od katerih je mogoče določiti svoj seznam prikazanih alarmov in upravljati preko Puppeta in merge requestov.

Mane:

  • Inercija članov ekipe — Nagios je delal, delal in delal, ta tvoja Icinga pa ves čas zmrzuje in zastaja. Kako lahko vidim zgodovino? A, hčerka, ne posodablja se ... (Prava težava je, da se zgodovina alarmov ne posodablja samodejno, le s F5)
  • Inercija sistema — ko v spletnem vmesniku kliknem na 'osveži' (check now) — rezultat izvajanja je odvisen od vremenskih razmer na Marsu, še posebej pri zahtevnih storitvah, ki potrebujejo desetine sekund za izvedbo. Takšen rezultat je običajna stvar. Migracija z Nagiosa na Icinga2 v Avstraliji
  • Na splošno, glede na polletne statistike delovanja dveh sistemov ob strani, je Nagios vedno deloval hitreje kot Icinga in to me zelo jezi. Zdi se mi, da so tam nekaj pokvarili s časovniki, in preverjanje na pet minut v resnici traja pet minut in pol ali nekaj takega.
  • Če kadar koli ponovno zaženem storitev (systemctl restart icinga2) — vse preverjanja, ki so takrat potekala, bodo prikazala alarm critical na zaslonu in s strani izgleda, kot da je vse padlo (potrjen bug).

Ampak na splošno - deluje.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster