Migracija s Nagiosa na Icinga2 u Australiji

Pozdrav svima.

Ja sam Linux sistem administrator koji se 2015. godine preselio iz Rusije u Australiju putem nezavisne profesionalne vize, ali ovaj članak neće biti o tome kako svinja nabavlja traktor. Takvih članaka već ima dovoljno (ipak, ako bude interesa — mogu i o tome napisati), pa bih želeo pričati o tome kako sam na svom poslu u Australiji kao inženjer linux-ops inicirao migraciju s jednog sistema za nadgledanje na drugi. Konkretno — Nagios => Icinga2.

Članak je delimično tehnički i delimično o komunikaciji s ljudima i problemima vezanim uz razlike u kulturi i načinu rada.

Nažalost, oznaka «code» ne označava kod Puppet i yaml, pa sam morao koristiti «plaintext».

Niče nije naslućivalo nevolje ujutro 21. decembra 2016. godine. Kao i obično, čitao sam Habr kao neregistrirani anonimac u prvih pola sata radnog dana, upijajući kafu i naišao na ovaj članak.

Budući da je u mojoj kompaniji korišćen Nagios, bez razmišljanja sam otvorio tiket u Redmineu i poslao link u zajednički chat, smatrajući to važnim. Inicijative su kažnjive čak i u Australiji, pa je glavni inženjer preuzeo ovu problematiku na mene, budući da sam je otkrio.

Screenshot iz RedmineaMigracija s Nagiosa na Icinga2 u Australiji

U našem odeljenju je običaj pre nego što izložimo svoje mišljenje, predložiti najmanje jednu alternativu, čak i ako je izbor očigledan, tako da sam počeo s pretragom koje su uopšte trenutno aktuelne sisteme za nadgledanje, pošto sam u Rusiji na svom poslednjem radnom mestu imao svoj vlastiti ručno rađeni sistem, veoma primitivan, ali prilično funkcionalan i sposoban da ispuni sve zadatke koji su mu povereni. Python, Politehnički univerzitet u Sankt Peterburgu i metro su odlični. Ne, metro — to je loše. Ovo je lično iskustvo (11 godina rada) i zaslužuje poseban članak, ali ne sada.

Malo o pravilima unošenja promena u konfiguraciju infrastrukture na mom trenutnom radnom mestu. Koristimo Puppet, Gitlab i princip Infrastrukture kao Koda, tako da:

  • Nikakve ručne izmene putem SSH menjajući bilo koju datoteku na virtualnim mašinama. Tokom tri godine rada zbog toga sam bio ukorenjen mnoge puta, poslednji put pre nedelju dana i ne mislim da je to bilo poslednji put. Pa, zaista — ispraviti jedan red u konfiguraciji, restartovati servis i videti da li je problem rešen — 10 sekundi. Kreirati novu granu u Gitlabu, push-ati izmene, sačekati da r10k odradi na Puppetmaster-u, pokrenuti Puppet —environment=mybranch i još par minuta čekati da sve to prođe — minimum 5 minuta.
  • Sve promene se vrše putem kreiranja Merge Request u Gitlabu i neophodno je dobiti odobrenje najmanje od jednog člana tima. Ozbiljnije izmene po rešenju tim-leada zahtevaju dva ili tri odobrenja.
  • Sve promene su više-manje tekstualne (budući da su Puppet manifesti, skripte i Hiera podaci — to je tekst), binarne datoteke su ekstremno nepreporučive i za odobrenje takvih datoteka potrebni su ozbiljni razlozi.

Dakle, opcije koje sam razmotrio:

  • Munin — ako u infrastrukturi ima više od 10 servera, administracija postaje pakao (iz ovog članka. Nisam imao posebnu želju da to proveravam, pa sam verovao na reč).
  • Zabbix — dugo sam ga posmatrao, još u Rusiji, ali tada je bio prekompleksan za moje zadatke. Ovde — morao sam da ga odbacim zbog korišćenja Puppeta kao menadžera konfiguracije i Gitlaba kao sistema za kontrolu verzija. Tada sam razumeo — Zabbix čuva svu konfiguraciju u bazi podataka, zbog čega nije bilo jasno kako upravljati konfiguracijom u trenutnim uslovima i kako pratiti promene.
  • Prometheus — to ka čemu ćemo doći na kraju, sudeći po raspoloženju u odeljenju, ali tada nisam uspeo da to savladam i nisam mogao da demonstriram stvaran radni uzorak (Proof of Concept), tako da sam morao da se odričem.
  • Bilo je još nekoliko drugih opcija koje su ili zahtevale potpunu rekonstrukciju sistema, ili su bile u začetku / zapostavljene i iz istog razloga su odbijene.

Na kraju sam se odlučio za Icinga2 iz tri razloga:

1 — kompatibilnost s Nrpe (klijentskom uslugom koja pokreće provjere na temelju komandi iz Nagios-a). To je bilo jako važno jer smo imali 135 virtualnih mašina s brojnim vlastitim servisima/provjerama, a prepraviti sve to bi bio veliki problem.
2 — svi konfiguracijski datoteke su tekstualne, što omogućava lako izmjenjivanje istih, stvaranje merge zahtjeva s mogućnošću pregleda šta je dodano ili uklonjeno.
3 — to je aktivan i razvijajući se OpenSource projekt. Jako cijenimo OpenSource i dajemo svoj doprinos stvaranjem Pull Requests i Issues za rješavanje problema.

Dakle, krenimo, Icinga2.

Prvo s čime smo se susreli — inertnost kolega. Svi su navikli na Nagios/Nagios, čak ni ovdje nismo mogli doći do kompromisa kako izgovarati to ime i sučelje CheckMK. Kod Icinge sučelje izgleda potpuno drugačije (što je bio minus), ali pruža mogućnost fleksibilnog podešavanja onoga što treba vidjeti putem filtara doslovno po bilo kojem parametru (što je bio plus, ali sam se za to dosta borio).

FiltriMigracija s Nagiosa na Icinga2 u Australiji

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

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

U smeće s Thruk-om — jednoglasna odluka timaMigracija s Nagiosa na Icinga2 u Australiji

Nakon nekoliko dana brainstorming-a, predložio sam ideju klasterskog praćenja, gdje postoji jedan master-host u proizvodnoj zoni i dva podređena - jedan u dev/test i jedan vanjski host, smješten kod drugog pružatelja s ciljem praćenja naših usluga s gledišta klijenta ili vanjskog promatrača. Ova konfiguracija omogućila je uvid u sve probleme u jednom web sučelju i prilično je dobro funkcionirala, ali Puppet… Problem s Puppetom bio je u tome što je master-host sada trebao znati o svim hostovima i uslugama/provjerama u sustavu i morao ih je rasporediti između zona (dev-test, staging-prod, ext), ali slanje promjena putem Icinga API-a traje nekoliko sekundi, dok kompilacija Puppet direktorija svih usluga za sve hostove traje nekoliko minuta. To mi se često zamjera, iako sam već nekoliko puta objašnjavao kako sve to funkcionira i zašto to traje tako dugo.

Treće - hrpa SnowFlakes (snješka) - stvari koje se izdvajaju iz općeg sustava jer u njima ima nešto posebno, pa se opća pravila na njih ne primjenjuju. Rješavalo se frontalnim napadom - ako postoje upozorenja, ali su u stvarnosti sve u redu, onda treba dublje istražiti i utvrditi zašto me alarmira, iako ne bi trebao. Ili obrnuto - zašto Nagios panika, a Icinga ne.

Četvrto - Nagios je ovdje radio tri godine prije mene i prvotno je imao više povjerenja nego moj moderni hipsterski sustav, tako da svaki put kada je Icinga podizala paniku - nitko nije ništa radio, dok se Nagios ne uzbudi zbog istog pitanja. Međutim, vrlo rijetko je Icinga davala stvarna upozorenja prije Nagiosa i smatram to ozbiljnim nedostatkom koji ću opisati u odjeljku „Zaključci“.

Na kraju, puštanje u rad odužilo se više od 5 mjeseci (planirano 28. lipnja 2018, a zapravo 3. prosinca 2018), uglavnom zbog „parity check“ - tog nereda kada postoji nekoliko usluga u Nagiosu o kojima nitko ništa nije znao posljednjih nekoliko godina, ali UPRAVO SADA su, dovraga, izdale kritično upozorenje bez ikakvog razloga i morao sam objašnjavati zašto ih nema na mojoj ploči i morao ih dodati u Icinga, kako bi „parity check bio dovršen“ (Sve usluge/provere u Nagiosu odgovaraju uslugama/proverama u Icinga).

Implementacija:
Prvo — rat između Koda i Podataka, nešto kao Puppet stil. Svi podaci, doslovno svi, moraju biti u Hiera, i nikako drugačije. Sav kod mora biti u .pp datotekama. Varijable, apstrakcije, funkcije — sve ide u pp.
Na kraju — imamo hrpu virtualnih mašina (165 u trenutku pisanja ovog članka) i 68 web-aplikacija koje treba pratiti u pogledu funkcionalnosti i valjanosti SSL certifikata. Međutim, zbog povijesnih problema, informacije za praćenje aplikacija dolaze iz zasebnog gitlab-repozitorija, a format podataka nije se mijenjao od vremena Puppet 3, što dodatno otežava konfiguraciju.

Puppet-kod za aplikacije, čuvajte oči

definir profili::usluge::nadzor::docker_aplikacije(
  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,
  )
{
#### APLIKACIJE ####
  $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'], {} )) } # dodaje obavijesti za zadanu grupu (sustavi) + bilo koju grupu definiranu u 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')        # Odaberi regex za provjeru

    $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 | {        # Razdvoji aplikaciju po domenama ako ih ima dvije ili više
      $vhost_name = {'http_vhost' => $vhost}

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

      $web_ipaddress = is_array($vdata['web_ipaddress']) ? {  # Napravi IP-adresu nizom ako nije, jer askizzy ima 2 ips i to je niz
        true  => $vdata['web_ipaddress'],
        false => [$vdata['web_ipaddress']],
      }

      $access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Spoji zadanu zonu (gdje je aplikacija definirana) i dodatne zone ako postoje
      $web_ipaddress.each | String $ip_address | {            # Za svaku IP (ako imamo više)
        $suffix = length($web_ipaddress) ? {                  # Ako imamo više od jedne - dodaj IP kao sufiks ovom imenu hosta da izbjegne dupliciranje resursa
          1       => '',
          default => "_${ip_address}"
        }
        $octets = split($ip_address, '.')
        $ip_tag = "${octets[2]}.${octets[3]}" # Koristeći samo zadnji oktet dolazi do sukoba između nginx-vip 203.15.70.94 i 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}" # Ako je to domaćin za ext - prefiks postaje '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
              }
            }
          }
        }
      }
    }
  }
}

Kod konfiguracije hostova i usluga također izgleda strašno:

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 <> # bit će omogućeno kada se potpuno prebacimo na 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('Molimo provjerite da li je čvoru postavljen $location fakt (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 <> # Ovo je za invaziju svemirskog broda kada bude spremno.
  $hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')

  $hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | {           # Razdvajanje lista stranica prema hostgroupovima - docker_host/gluster_host/etc
    $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 |{  # Razdvajanje lista grupa prema hostovima
      # Da li je ovaj host u nizu $hosts_has_large_disks? Ako jest, postavi 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)) # Merging vars separately

      $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){                                                                # Svi hostovi upravljani API-jem
    $hosts_api.each | String $zone, Hash $hosts_api_zone | {                            # Razdvajanju api hostova prema zonama
      $hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | {   # Razdvajanje lista stranica prema hostgroupovima - docker_host/gluster_host/etc
        $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 |{        # Razdvajanje lista grupa prema hostovima
          # Da li je ovaj host u nizu $hosts_has_large_disks? Ako jest, postavi 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'],
          }
        }
      }
    }
  }

#### KRAJ HOSTOVA ####

####   SERVISI   ####

  $services.each | String $service_group, Hash $s_list |{             # Service_group i lista usluga u toj grupi
    $service_list = $s_list['checks']                                 # Lista stvarnih provjera, posebno od SG postavki
    $service_list.each | String $service_name, Hash $data |{

      $merged_defaults = merge($service_defaults, $s_list['settings']) # globalni zadani servisi + postavke grupnog servisa
      $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)

      # Ako prebacimo zadani check_timeout, ali ne nrpe_timeout, postavi nrpe_timeout isto kao check_timeout
      if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
        # NB: Icinga će automatski pretvoriti 1m na 60!
        $nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
      } else {
        $nrpe = {}
      }

      # Po defaultu koristimo nrpe i sve naredbe se pokreću putem nrpe. Dakle vars.nrpe_command = $service_name je default vrijednost
      # Ako je to naredba za Icinga na poslužitelju - ne trebamo 'nrpe_command'
      # ali nema štete imati tu varijablu i kod je kraći

      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 = {}
      }

      # Sakupljanje $vars iz globalnih zadatih usluga, postavki grupnog servisa, ovih konkretnih provjera i ne zaboravimo nrpe postavke.
      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 je potreban svugdje, inače postaje "Vrijednost '' ne može se pretvoriti u Numeričku"

      $service_notify_group = $service_notify ? {
        []      => $service_defaults['vars']['notify_group'],
        default => $service_notify
      } # Dodijeli zadanu grupu (sustavi) ako nijedna druga grupa nije definirana

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

      # Ovo treba spojiti odvojeno, jer njegovo spajanje kao dio MERGED_DATA prepisuje nizove umjesto da ih spoji, tako gubimo neke "dodijeli" i "ignoriši" vrijednosti

      $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'],
      }
    }
  }
#### KRAJ SERVISA ####

#### DRUGE DOSADNE STVARI ####

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

  # Spajanje postavki obavijesti za korisnike s drugim postavkama
  $users_oncall = deep_merge($users, $oncall)
  # Magija. Nemoj dirati.
  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',
  }

}

Još uvijek radim na ovom kodu i poboljšavam ga koliko god mogu. Međutim, upravo je ovakav kod omogućio korištenje jednostavne i razumljive sintakse u Hieri:

Podaci

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'

Sve provjere su podijeljene u grupe, a svaka grupa ima zadane postavke koje određuju gdje i koliko često se provjere izvode, koje obavijesti se šalju i kome.

U svakoj provjeri moguće je prepisati bilo koju opciju, a sve to na kraju se kombinira s zadanim postavkama svih provjera u cjelini. Zbog toga je u config.pp napisana ovakva struktura — tu se spajaju sve zadane postavke s postavkama grupa i zatim s pojedinačnim provjerama.

Također, vrlo važna promjena bila je mogućnost korištenja funkcija u postavkama, na primjer, funkcija za zamjenu porta, adrese i URL-a za provjeru http_regex.

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 znači — ako u definiciji hosta postoji varijabla http_port — koristiti je, inače 443. Na primjer, web sučelje jabber je na 9090, a Unifi — na 7443.
http_vhost znači ignorirati DNS i uzeti ovu adresu.
Ako je u hostu naveden uri — koristiti ga, inače uzeti „\/”.

S http_ssl se dogodila zabavna situacija — ta stvar nikako nije htjela biti isključena na zahtjev. Dugo sam se mučio s ovom linijom dok nisam shvatio da je varijabla u definiciji hosta:

http_ssl: false

Zamjenjuje se u izrazu

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

kao false i na kraju dolazimo do

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

dakle, provjera ssl uvijek je aktivna. Ovo je riješeno promjenom sintakse:

http_ssl: no

Zaključci:

Prednosti:

  • Sada imamo jedan sustav nadzora, a ne dva, kao što je to bio slučaj u posljednjih 7-8 mjeseci, ili jedan, zastario i ranjiv.
  • Struktura podataka hostova / usluga (provjera) sada (po mom mišljenju) izgleda mnogo čitljivije i razumljivije. Drugima to nije bilo tako očito, pa sam morao izraditi nekoliko stranica u lokalnoj wiki koje objašnjavaju kako sve to funkcionira i što gdje treba uređivati.
  • Postoji mogućnost fleksibilnog podešavanja provjera putem varijabli i funkcija, na primjer za provjeru http_regexp, traženi obrazac, kod povrata, url i port mogu se postaviti u postavkama hosta.
  • Postoji nekoliko panela (dashboarda), za svaki od kojih se može odrediti vlastiti popis prikazanih upozorenja i upravljati svime ovim putem Puppeta i merge zahtjeva.

Mane:

  • Inercija članova tima — Nagios je radio, radio i radio, dok tvoj Icinga neprestano se smrzava i usporava. A kako ovdje vidjeti povijest? A, uh, ona se ne ažurira... (Pravi problem — povijest upozorenja se automatski ne ažurira, samo s F5)
  • Inercija sustava — kada kliknem u web sučelju na "ažurirati" (check now) — rezultat izvođenja ovisi o vremenskim uvjetima na Marsu, posebno za složene usluge koje zahtijevaju desetke sekundi za izvršenje. Takav rezultat — normalna pojava. Migracija s Nagiosa na Icinga2 u Australiji
  • Općenito, prema polugodišnjoj statistici rada dva sustava jedan pored drugog, Nagios je uvijek brže obavljao zadatke nego Icinga, i to me jako iritira. Čini mi se da su nešto zeznuli s vremenskim mjeračima i provjera svake pet minuta zapravo se odvija svaka 5:30 ili nešto slično.
  • Ako u bilo kojem trenutku ponovo pokrenem servis (systemctl restart icinga2) — sve provjere koje su tada bile u procesu izvršenja, ispostavit će kritično upozorenje <terminated by signal 15> na ekranu i s te strane izgleda kao da je sve potpuno stalo (potvrđeni bug).

Ali u cjelini — to radi.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster