Migrácia z Nagios na Icinga2 v Austrálii

Ahojte všetci.

Ja som linux administrátor, presťahoval som sa z Ruska do Austrálie na nezávislé profesionálne vízum v roku 2015, ale článok nebude o tom, ako prasiatko získa traktor. Takých článkov je už dosť (napriek tomu, ak by bol záujem — napíšem aj o tom), takže by som chcel porozprávať o tom, ako som vo svojej práci v Austrálii na pozícii linux-ops inžiniera bol iniciátorom migrácie z jedného monitorovacieho systému na iný. Konkrétne — Nagios => Icinga2.

Článok je čiastočne technický a čiastočne o komunikácii s ľuďmi a problémoch súvisiacich s kultúrnymi rozdielmi a pracovnými metódami.

Bohužiaľ, tag „code“ nezvýrazňuje kód Puppet a yaml, takže som musel použiť „plaintext“.

Nič nepredpokladalo problémy ráno 21. decembra 2016. Ja, ako obvykle, som čítal Habr ako nezaregistrovaný anonymný používateľ v prvých polhodinách pracovného dňa, popíjajúc kávu a narazil na tento článok.

Keďže v mojej firme práve používali Nagios, neváhal som a vytvoril tiket v Redmine a poslal odkaz do spoločného chatu, pretože som to považoval za dôležité. Iniciatíva je v Austrálii tiež postihnuteľná, takže vedúci inžinier mi dal túto úlohu, keďže som ju objavil.

Snímek z RedmineMigrácia z Nagios na Icinga2 v Austrálii

V našej sekcii je pred prezentovaním svojho názoru zvykom navrhnúť minimálne jednu alternatívu, aj keď je výber zrejmý, takže som začal googliť, aké monitorovacie systémy sú aktuálne, pretože v Rusku som mal vo svojej poslednej práci vlastný napísaný systém, veľmi primitívny, ale aj tak celkom funkčný a plniaci všetky naň uložené úlohy. Python, St. Petersburg Polytechnic a metro sú super. Nie, metro je na nič. To je osobné (11 rokov práce) a zaslúži si samostatný článok, ale nie teraz.

Trochu o pravidlách zmeny konfigurácie infraštruktúry na mojom súčasnom mieste. Používame Puppet, Gitlab a princíp Infrastructure as Code, takže:

  • Žiadne ručné zmeny cez SSH manuálnou zmenou akýchkoľvek súborov na virtuálnych strojoch. Počas troch rokov práce som za to dostal vynadávanie mnohokrát, naposledy pred týždňom a nemyslím si, že to bolo naposledy. No, naozaj — opraviť jeden riadok v konfiguračnom súbore, reštartovať službu a pozrieť sa, či sa problém vyriešil — 10 sekúnd. Vytvoriť novú vetvu v Gitlabe, nahrať zmeny, čakať, kým r10k zafunguje na Puppetmastere, spustiť Puppet —environment=mybranch a ešte pár minút čakať, kým toto všetko zafunguje — minimálne 5 minút.
  • Akékoľvek zmeny sa robia vytvorením Merge Requestu v Gitlabe a je potrebné získať schválenie minimálne od jedného člena tímu. Závažné zmeny podľa rozhodnutia tímového lídra vyžadujú dve alebo tri schválenia.
  • Všetky zmeny sú tak či onak textové (pretože manifesty Puppet, skripty a dáta Hiera sú text), binárne súbory sú veľmi neodporúčané a na schválenie takýchto súborov sú potrebné vážne dôvody.

Takže, možnosti, ktoré som zvážil:

  • Munin — ak je v infraštruktúre viac ako 10 serverov, administrácia sa premení na peklo (z tohto článku. Nemal som zvláštnu chuť to overovať, takže som veril na slovo).
  • Zabbix — už dávno som ho sledoval, ešte v Rusku, ale vtedy bol nadbytočný pre moje úlohy. Tu — musel som ho zavrhnúť kvôli použitiu Puppet ako správcu konfigurácie a Gitlaba ako systému správy verzií. V tom čase, pokiaľ som pochopil — Zabbix uchováva celú konfiguráciu v databáze, a preto nebolo jasné, ako spravovať konfiguráciu v súčasných podmienkach a ako sledovať zmeny.
  • Prometheus — to, k čomu sa nakoniec dostaneme, súdiac podľa nálady v oddelení, ale v tom čase som to nezvládol a nemohol som prezentovať skutočný funkčný vzor (Proof of Concept), takže som musel ustúpiť.
  • Ešte bolo niekoľko ďalších možností, ktoré buď vyžadovali úplnú prestavbu systému, alebo boli v zárodku/zaniknuté a z toho dôvodu boli zamietnuté.

Nakoniec som sa zastavil na Icinga2 z troch dôvodov:

1 — kompatibilita s Nrpe (klientská služba, ktorá vykonáva kontroly na príkazoch od Nagiosu). To bolo veľmi dôležité, pretože sme mali v tom čase 135 (teraz ich je v roku 2019 165) virtuálnych strojov s množstvom vlastných služieb/overení a prerábka toho všetkého by bola nesmierne náročná.
2 — všetky konfiguračné súbory sú textové, čo umožňuje ľahko ich upravovať, vytvárať merge requests s možnosťou vidieť, čo bolo pridané alebo odstránené.
3 — je to živý a rozvíjajúci sa projekt OpenSource. Máme radi OpenSource a prispievame k nemu vytváraním Pull Requests a Issues na riešenie problémov.

Takže, ideme na to, Icinga2.

Prvé, čo som musel riešiť — inertnosť kolegov. Všetci boli zvyknutí na Nagios/Nadjios (aj keď sa ani tu nedokázali dohodnúť, ako to vyslovovať) a rozhranie CheckMK. V prípade Icinga je rozhranie úplne iné (čo bol mínus), ale je možné flexibilne nastaviť, čo chcem vidieť pomocou filtrov doslova podľa akéhokoľvek parametra (čo bol plus, ale za to sme sa poriadne pohádali).

FiltreMigrácia z Nagios na Icinga2 v Austrálii

Ohodnoťte pomer veľkosti posuvnej lišty k veľkosti plochy na posúvanie.

Druhé — všetci boli zvyknutí vidieť celú infraštruktúru na jednom monitore, pretože CheckMk umožňuje pracovať s viacerými hostiteľmi Nagios, ale rozhranie Icinga to takto nevedelo (v skutočnosti to vedelo, ale o tom ďalej). Alternatívou bola vec s názvom Thruk, ale jej dizajn vyvolával zvracacie pocity u všetkých členov tímu s výnimkou jedného — toho, kto to navrhol (nie ja).

Preč s Thruk — jednomyseľné rozhodnutie tímu.Migrácia z Nagios na Icinga2 v Austrálii

Po pár dňoch brainstorming-u som navrhol myšlienku klastrového monitorovania, keď je jeden master-host vo production zóne a dvaja podriadení - jeden v dev/test a jeden externý host umiestnený u iného poskytovateľa s cieľom monitorovať naše služby z pohľadu zákazníka alebo nezávislého pozorovateľa. Takáto konfigurácia umožnila vidieť všetky problémy v jednom webovom rozhraní a úplne fungovala, ale Puppet… Problém s Puppetom spočíval v tom, že master-host teraz musel poznať všetky hosty a služby/overenia v systéme a musel ich rozdeľovať medzi zóny (dev-test, staging-prod, ext), ale odosielanie zmien cez Icinga API trvá niekoľko sekúnd, zatiaľ čo kompilácia adresára Puppet všetkých služieb pre všetky hosty trvá niekoľko minút. To mi dodnes vyčítajú, hoci som už niekoľkokrát vysvetľoval, ako to funguje a prečo to všetko trvá tak dlho.

Tretie - hromada SnowFlakes (snehových vločiek) - veci, ktoré sa vymykajú z bežného systému, pretože majú niečo osobitné, a preto sa na ne nevzťahujú bežné pravidlá. Riešilo sa to priamym útokom - ak sú tu upozornenia, ale v skutočnosti je všetko v poriadku, znamená to, že tu treba hľadať hlbšie a zistiť, prečo ma to varuje, hoci by nemalo. Alebo naopak - prečo Nagios panikári, zatiaľ čo Icinga - nie.

Štvrté - Nagios fungoval tu pred mnou tri roky a mal väčšiu dôveru od samého začiatku ako moja moderná hipsterská systému, takže zakaždým, keď Icinga vyvolávala paniku, nikto nič neurobil, pokiaľ sa Nagios nedostal do rozpakov kvôli tej istej otázke. Ale veľmi zriedka Icinga vydávala reálne varovania skôr ako Nagios a považujem to za vážnu chybu, o ktorej sa budem zmiňovať v časti „Závery.“

Nakoniec sa uvedenie do prevádzky oneskorilo viac ako o 5 mesiacov (plánované na 28. júna 2018, skutočne - 3. decembra 2018), hlavne kvôli „parity check“ - tej veci, keď existuje niekoľko služieb v Nagios, o ktorých nikto nič nepočul posledné dva roky, ale PRESNE TERAZ vyhlásili crit bez akéhokoľvek dôvodu a musel som vysvetliť, prečo ich nemám na svojom paneli a musel som ich pridať do Icinga, aby „parity check bol dokončený“ (Všetky služby/overenia v Nagios zodpovedajú službám/overeniam v Icinga)

Implementácia:
Prvá vec — vojna Code vs Data, v štýle Puppet. Všetky údaje, presne všetky, musia byť v Hiera a inak to nejde. Celý kód je v súboroch .pp. Premenné, abstrakcie, funkcie — všetko ide do pp.
Takže máme veľa virtuálnych strojov (165 v čase písania článku) a 68 webových aplikácií, ktoré treba monitorovať z hľadiska funkčnosti a platnosti SSL certifikátov. Ale kvôli historickému chaosu sa informácie na monitorovanie aplikácií berú z oddeleného gitlab repozitára a formát údajov sa nezmenil od čias Puppet 3, čo spôsobuje ďalšie problémy v konfigurácii.

Puppet kód pre aplikácie, chráňte si oči

definuj profily::služby::monitoring::docker_aplikácie(
  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,
  )
{
#### APLIKÁCIE ####
  $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'], {} )) } # pridáva oznámenia pre predvolenú skupinu (systémy) + akúkoľvek skupinu definovanú v 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')        # Vyber regex pre kontrolu

    $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 | {        # Rozdeľ aplikáciu podľa domén, ak ich je dve alebo viac
      $vhost_name = {'http_vhost' => $vhost}

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

      $web_ipaddress = is_array($vdata['web_ipaddress']) ? {  # Urobte IP adresu poľa, ak nie je, pretože askizzy má 2 ip a je to pole
        true  => $vdata['web_ipaddress'],
        false => [$vdata['web_ipaddress']],
      }

      $access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Zlúčte predvolenú zónu (kde je aplikácia definovaná) a extra zóny, ak existujú
      $web_ipaddress.each | String $ip_address | {            # Pre každú IP (ak máme viacero)
        $suffix = length($web_ipaddress) ? {                  # Ak máme viac ako jednu - pridajte IP ako príponu k tomuto hostnámu, aby sa predišlo duplicite zdrojov
          1       => '',
          default => "_${ip_address}"
        }
        $octets = split($ip_address, '.')
        $ip_tag = "${octets[2]}.${octets[3]}" # Použitie posledného oktetu spôsobuje kolíziu medzi nginx-vip 203.15.70.94 a 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}" # Ak ide o hostiteľa pre ext - prefix sa stáva '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('profily::služby::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
              }
            }
          }
        }
      }
    }
  }
}

Kód konfigurácie hostiteľov a služieb vyzerá tiež hrozne:

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 <> # bude aktivované při úplném přechodu 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('Prosím ujistěte se, že má uzel nastavený fakt $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 <> # Toto je pro invazi do vesmíru, až to bude připravené.
  $hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')

  $hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | {           # Rozdělení seznamů stránek podle hostgroup - 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 |{  # Rozdělení seznamu skupin podle hostů
      # Je tento host v poli $hosts_has_large_disks? Pokud ano, nastavte 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)) # Sloučení proměnných zvlášť

      $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){                                                                # Všechny hosty spravované API
    $hosts_api.each | String $zone, Hash $hosts_api_zone | {                            # Rozdělení api hostů podle zón
      $hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | {   # Rozdělení seznamů stránek podle hostgroup - 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 |{        # Rozdělení seznamu skupin podle hostů
          # Je tento host v poli $hosts_has_large_disks? Pokud ano, nastavte 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'],
          }
        }
      }
    }
  }

#### KONEC HOSTŮ ####

####   SLUŽBY   ####

  $services.each | String $service_group, Hash $s_list |{             # Skupina služeb a seznam služeb v této skupině
    $service_list = $s_list['checks']                                 # Seznam skutečných kontrol, odděleně od nastavení SG
    $service_list.each | String $service_name, Hash $data |{

      $merged_defaults = merge($service_defaults, $s_list['settings']) # globální výchozí hodnoty služby + výchozí hodnoty skupiny služeb
      $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)

      # Pokud přepíšeme výchozí check_timeout, ale ne nrpe_timeout, nastavíme nrpe_timeout na stejnou hodnotu jako check_timeout
      if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
        # NB: Icinga automaticky převede 1m na 60!
        $nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
      } else {
        $nrpe = {}
      }

      # Ve výchozím nastavení používáme nrpe a všechny příkazy se spouštějí přes nrpe. Tak vars.nrpe_command = $service_name je výchozí hodnota
      # Pokud je to příkaz Icinga na straně serveru - nepotřebujeme 'nrpe_command'
      # ale není na škodu mít tuto proměnnou a kód je kratší

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

      # Sestavení $vars z globálních výchozích nastavení pro služby, nastavení skupiny služeb, tohoto konkrétního kontroly a nezapomeňte na nastavení 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 je vyžadován všude, jinak se stává "Hodnota '' nemůže být převedena na číslo"

      $service_notify_group = $service_notify ? {
        []      => $service_defaults['vars']['notify_group'],
        default => $service_notify
      } # Přidělit výchozí skupinu (systémy), pokud nejsou definovány žádné jiné skupiny

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

      # To je potřeba sloučit samostatně, protože sloučení jako součást MERGED_DATA přepisuje pole místo jejich sloučení, takže ztrácíme některé "přidělit" a "ignorovat" hodnoty

      $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'],
      }
    }
  }
#### KONEC SLUŽEB ####

#### DALŠÍ NUDNÉ VĚCI ####

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

  # Sloučení nastavení upozornění pro uživatele s jinými nastaveními
  $users_oncall = deep_merge($users, $oncall)
  # Magie. Nedotýkejte se.
  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',
  }

}

Stále pracujem na tejto 'ceste' a zlepšujem ju, akonáhle to je možné. Avšak práve takýto kód umožnil použiť jednoduchú a zrozumiteľnú syntaxi v Hiera:

Údaje

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'

Všetky kontroly sú rozdelené do skupín, pričom každá skupina má predvolené nastavenia, kde a ako často sa tieto kontroly vykonávajú, aké oznámenia sa posielajú a komu.

V každej kontrole je možné predefinovať akúkoľvek možnosť a to sa nakoniec spočítava aj s predvolenými nastaveniami všetkých kontrol ako celku. Preto je v config.pp napísaná takáto 'cesta' — tam prebieha zlúčenie všetkých predvolených nastavení so nastaveniami skupín a potom ešte s každou individuálnou kontrolou.

Rovnako veľmi dôležitou zmenou bola možnosť používať funkcie v nastaveniach, napríklad funkcia na náhradu portu, adresy a url pre kontrolu 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 znamená — ak v definícii hostiteľa existuje premenná http_port — použiť ju, inak 443. Napríklad, webové rozhranie jabber visí na 9090, a Unifi — na 7443.
http_vhost znamená ignorovať DNS a použiť túto adresu.
Ak je v hostiteľovi uvedený uri — treba ísť po ňom, inak zobrať "\/".

S http_ssl sa stala zábavná situácia — tento problém sa nedal jednoducho vypnúť na požiadanie. Dlho som sa motal okolo tejto riadky, kým som si neuvedomil, že v premenné v definícii hostiteľa:

http_ssl: false

Vkladá sa do výrazu

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

ako nepravda a výsledok je

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

t.j. kontrola ssl je vždy aktívna. Riešilo sa to zmenou syntaktiky:

http_ssl: no

Zhrnutie:

Výhody:

  • Teraz máme jeden monitorovací systém, nie dva, ako to bolo posledných 7-8 mesiacov, alebo jeden, zastaraný a zraniteľný.
  • Štruktúra dát hostiteľov / služieb (kontrol) je teraz (podľa môjho názoru) oveľa prehľadnejšia a zrozumiteľnejšia. Pre iných sa to ukázalo nie tak jasné, takže som musel vytvoriť pár stránok v miestnej wike na vysvetlenie, ako to všetko funguje a čo kde opraviť.
  • Existuje možnosť flexibilného nastavovania kontrol pomocou premenných a funkcií, napríklad pre kontrolu http_regexp sa hľadaný vzor, návratový kód, url a port dajú nastaviť v nastaveniach hostiteľa.
  • Existuje niekoľko panelov (dashboards), pre každý z nich môžeme definovať svoj vlastný zoznam zobrazovaných alarmov a spravovať to všetko cez Puppet a merge requests.

Nevýhody:

  • Inertnosť členov tímu - Nagios pracoval, pracoval a pracoval, zatiaľ čo tvoja Icinga neustále zlyháva a spomaľuje. A ako si tu pozriem históriu? Aha, blázon, ona sa predsa neaktualizuje… (Skutočný problém – história alarmov sa neaktualizuje automaticky, iba po F5)
  • Inertnosť systému - keď kliknem v webovom rozhraní na 'aktualizovať' (check now) – výsledok závisí od počasia na Marse, zvlášť pri zložitých službách, ktoré vyžadujú desiatky sekúnd na vykonanie. Takýto výsledok je normálny. Migrácia z Nagios na Icinga2 v Austrálii
  • Celkovo z polročnej štatistiky práce dvoch systémov vedľa seba Nagios vždy fungoval rýchlejšie ako Icinga a to ma veľmi rúti. Podľa mňa tam niečo pokazili s časovačmi a kontrola raz za päť minút v skutočnosti trvá päť a pol minúty, alebo niečo v tom duchu.
  • Ak reštartujem službu v ktorýkoľvek okamih (systemctl restart icinga2) – všetky kontroly, ktoré boli v procese vykonávania v tom čase, vyhodia alarm critical na obrazovku a z vonku to vyzerá, akoby všetko zlyhalo (potvrdená chyba).

Ale vo všeobecnosti - funguje to.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster