Migrácia z Nagios do Icinga2 v Austrálii

Ahoj všetci

Som správca systému Linux, 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 naštartovať traktor pre prasa. Takýchto článkov je už dosť (ale ak bude záujem, napíšem aj o tomto), tak by som rád porozprával o tom, ako som pri mojej práci v Austrálii ako linux-ops inžinier inicioval migráciu z jedného monitorovania systému inému. Konkrétne - Nagios => Icinga2.

Článok je sčasti odborný a sčasti o komunikácii s ľuďmi a problémoch spojených s rozdielmi v kultúre a pracovných metódach.

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

Ráno 21. decembra 2016 neboli žiadne známky problémov. Ako obvykle som čítal Habr od neregistrovaného anonymného užívateľa v prvej polhodine pracovného dňa pri káve a narazil som na tento článok.

Keďže moja spoločnosť používala Nagios, bez rozmýšľania som vytvoril tiket v Redmine a poslal odkaz na všeobecný chat, pretože som to považoval za dôležité. Iniciatíva je trestná dokonca aj v Austrálii, takže hlavný inžinier mi pripísal problém, odkedy som ho objavil.

Snímka obrazovky z RedmineMigrácia z Nagios do Icinga2 v Austrálii

Na našom oddelení je pred vyjadrením názoru zvykom ponúknuť aspoň jednu alternatívu, aj keď je výber zrejmý, preto som začal googliť, aké monitorovacie systémy sú aktuálne aktuálne, keďže v Rusku na poslednom pôsobisku som mal svoj vlastný samopísaný systém, veľmi primitívny, no napriek tomu celkom fungujúci a vykonávajúci všetky úlohy, ktoré mu boli pridelené. Vládne Python, Petrohradská polytechnika a metro. Nie, metro je na hovno. Toto je osobné (11 rokov práce) a hodné samostatného článku, ale nie teraz.

Trochu o pravidlách vykonávania zmien v konfigurácii infraštruktúry na mojom súčasnom mieste. Ako princíp kódu používame Puppet, Gitlab a infraštruktúru, takže:

  • Žiadne manuálne zmeny cez SSH manuálnou zmenou akýchkoľvek súborov na virtuálnych počítačoch. Počas troch rokov práce som za to dostal veľakrát klobúk, naposledy pred týždňom a myslím, že to nebolo naposledy. Naozaj - opravte jeden riadok v konfigurácii, reštartujte službu a zistite, či je problém vyriešený - 10 sekúnd. Vytvorte novú pobočku v Gitlabe, zatlačte zmeny, počkajte, kým r10k začne pracovať na Puppetmaster, spustite Puppet -environment=mybranch a počkajte ešte pár minút, kým to všetko funguje – minimálne 5 minút.
  • Akékoľvek zmeny sa vykonávajú vytvorením žiadosti o zlúčenie v Gitlabe a musia byť schválené aspoň jedným členom tímu. Veľké zmeny, o ktorých rozhoduje vedúci tímu, si vyžadujú dva alebo tri schválenia.
  • Všetky zmeny sú tak či onak textové (keďže bábkové manifesty, skripty a údaje Hiera sú text), binárne súbory sa dôrazne neodporúčajú a na schválenie takýchto súborov musí existovať pádny dôvod.

Takže možnosti, ktoré som zvažoval:

  • Munin - ak je v infraštruktúre viac ako 10 serverov, správa sa zmení na peklo (od tento článok. Nemal som žiadnu zvláštnu túžbu overiť si to, tak som ho vzal za slovo).
  • Zabbix - Pozeral som sa na to už dlho, v Rusku, ale potom to bolo pre moje úlohy zbytočné. Tu - musel byť vyradený kvôli použitiu Puppet ako konfiguračného manažéra a Gitlabu ako systému na správu verzií. V tom čase, pokiaľ som pochopil, Zabbix ukladá celú konfiguráciu do databázy, a preto nebolo jasné, ako spravovať konfiguráciu za aktuálnych podmienok a ako sledovať zmeny.
  • Prometheus je to, k čomu sa nakoniec dostaneme, súdiac podľa nálady na oddelení, ale vtedy som ho neovládal a nebol som schopný predviesť skutočne fungujúcu vzorku (Proof of Concept), takže som musel odmietnuť.
  • Existovalo aj niekoľko ďalších možností, ktoré si buď vyžadovali kompletné prepracovanie systému, alebo boli v plienkach/opustené a boli zamietnuté z rovnakého dôvodu.

Nakoniec som sa rozhodol pre Icinga2 z troch dôvodov:

1 - kompatibilita s Nrpe (klientska služba, ktorá spúšťa kontroly príkazov z Nagios). Bolo to veľmi dôležité, pretože v tom čase sme mali 135 (teraz ich je v roku 2019 165) virtuálnych počítačov s množstvom služieb/kontrol napísaných na mieru a prerobiť to všetko by bola skutočná bolesť.
2 - všetky konfiguračné súbory sú textové, čo uľahčuje úpravu tejto záležitosti, vytváranie žiadostí o zlúčenie s možnosťou vidieť, čo bolo pridané alebo odstránené.
3 je živý a rozvíjajúci sa projekt OpenSource. OpenSource máme veľmi radi a všetkým možným spôsobom k nemu prispievame vytváraním Pull Requests a Issues na riešenie problémov.

Takže poďme, Icinga2.

Prvá vec, ktorej som musel čeliť, bola zotrvačnosť mojich kolegov. Každý je zvyknutý na Nagios/Naggios (aj keď ani tu nedokázali dospieť ku kompromisu, ako to vysloviť) a rozhranie CheckMK. Rozhranie icinga vyzerá úplne inak (to bolo mínus), ale je možné flexibilne prispôsobiť to, čo potrebujete vidieť, pomocou filtrov pre doslova akýkoľvek parameter (to bolo plus, ale veľa som za to bojoval).

FiltreMigrácia z Nagios do Icinga2 v Austrálii

Odhadnite pomer veľkosti rolovacej lišty k veľkosti rolovacieho poľa.

Po druhé, všetci sú 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 nedokázalo (v skutočnosti by mohlo, ale o tom nižšie). Alternatívou bolo niečo, čo sa nazývalo Thruk, ale jeho dizajn prinútil každého v tíme gýč okrem jedného – toho, kto to navrhol (nie mňa).

Do pece Thruk - jednomyseľné rozhodnutie tímuMigrácia z Nagios do Icinga2 v Austrálii

Po niekoľkých dňoch brainstormingu som navrhol myšlienku monitorovania klastra, keď je jeden hlavný hostiteľ v produkčnej zóne a dvaja podriadení – jeden vo vývoji/testovaní a jeden externý hostiteľ u iného poskytovateľa, aby sme mohli monitorovať naše služby z pohľadu klienta alebo vonkajšieho pozorovateľa. Táto konfigurácia umožnila vidieť všetky problémy v jednom webovom rozhraní a fungovala celkom dobre, ale Puppet... Problém s Puppet bol v tom, že hlavný hostiteľ teraz musel vedieť o všetkých hostiteľoch a službách/kontrolách v systéme a mal na ich distribúciu medzi zónami (dev-test, staging-prod, ext), ale odoslanie zmien cez Icinga API trvá pár sekúnd, no zostavenie adresára Puppet všetkých služieb pre všetkých hostiteľov trvá pár minút. Toto sa mi stále vyčíta, hoci som už niekoľkokrát vysvetľoval, ako všetko funguje a prečo to všetko tak dlho trvá.

Po tretie, existuje veľa snehových vločiek - vecí, ktoré vyčnievajú zo všeobecného systému, pretože majú niečo špeciálne, takže na ne neplatia všeobecné pravidlá. Vyriešilo sa to frontálnym útokom - ak sú alarmy, ale v skutočnosti je všetko v poriadku, potom musím ísť hlbšie a zistiť, prečo ma to varuje, hoci by to nemalo. Alebo naopak – prečo Nagios spanikári, ale Icinga nie.

Po štvrté, Nagios tu predo mnou pracoval tri roky a spočiatku v neho bola väčšia dôvera ako v môj novodobý hipsterský systém, takže zakaždým, keď Icinga vyvolala paniku, nikto nič nerobil, kým sa Nagios nevzrušil pre rovnakú tému. Ale veľmi zriedka Icinga generovala skutočné alarmy skôr ako Nagios a považujem to za vážny problém, o ktorom budem hovoriť v časti „Závery“.

V dôsledku toho sa uvedenie do prevádzky oneskorilo o viac ako 5 mesiacov (plánované na 28. júna 2018, v skutočnosti - 3. decembra 2018), najmä kvôli „kontrole parity“ - to svinstvo, keď je v Nagiose niekoľko služieb, ktoré nikto nepozná. za posledných pár rokov som o ničom nepočul, ale PRÁVE TERAZ kurva rozdali kritiku bez dôvodu a musel som vysvetliť, prečo neboli v mojom paneli a musel som ich pridať do Icinga, takže „kontrola parity je kompletné" (všetky služby/šeky v Nagiose zodpovedajú službám/šekom v Icinga)

Implementácia:
Prvým je vojna Kód vs Dáta, ako napríklad Puppet Style. Všetky údaje, úplne všetko, musia byť v Hiere a nič iné. Všetok kód je v súboroch .pp. Premenné, abstrakcie, funkcie – všetko ide v pp.
Výsledkom je, že máme veľa virtuálnych strojov (v čase písania článku 165) a 68 webových aplikácií, ktoré je potrebné monitorovať z hľadiska výkonu a platnosti SSL certifikátov. Ale kvôli historickým hemoroidom sú informácie pre monitorovacie aplikácie prevzaté zo samostatného úložiska gitlab a formát údajov sa od Puppet 3 nezmenil, čo vytvára ďalšiu zložitosť v konfigurácii.

Bábkový kód pre aplikácie, chráňte si 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'], {} )) } # adds notifications for default group (systems) + any group defined in 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')        # Pick a regex to check

    $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 | {        # Split an app by domains if there are two or more
      $vhost_name = {'http_vhost' => $vhost}

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

      $web_ipaddress = is_array($vdata['web_ipaddress']) ? {  # Make IP-address an array if it's not, because askizzy has 2 ips and it's an array
        true  => $vdata['web_ipaddress'],
        false => [$vdata['web_ipaddress']],
      }

      $access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Merge default zone (where the app is defined) and extra zones if they exist
      $web_ipaddress.each | String $ip_address | {            # For each IP (if we have multiple)
        $suffix = length($web_ipaddress) ? {                  # If we have more than one - add IP as a suffix to this hostname to avoid duplicating resources
          1       => '',
          default => "_${ip_address}"
        }
        $octets = split($ip_address, '.')
        $ip_tag = "${octets[2]}.${octets[3]}" # Using last octet only causes a collision between nginx-vip 203.15.70.94 and 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}" # If it's a host for ext - prefix becomes '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
              }
            }
          }
        }
      }
    }
  }
}

Konfiguračný kód pre hostiteľov a služby tiež vyzerá 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 <<| |>> # will be enabled when we move to icinga completely
#### 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('Please ensure the node has $location fact set (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 <<| |>> # This is for spaceship invasion when it's ready.
  $hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')

  $hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | {           # Splitting site lists by hostgroups - 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 |{  # Splitting grouplists by hosts
      # Is this host in the array $hosts_has_large_disks ? If so set 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){                                                                # All hosts managed by API
    $hosts_api.each | String $zone, Hash $hosts_api_zone | {                            # Split api hosts by zones
      $hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | {   # Splitting site lists by hostgroups - 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 |{        # Splitting grouplists by hosts
          # Is this host in the array $hosts_has_large_disks ? If so set 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'],
          }
        }
      }
    }
  }

#### END OF HOSTS ####

####   SERVICES   ####

  $services.each | String $service_group, Hash $s_list |{             # Service_group and list of services in that group
    $service_list = $s_list['checks']                                 # List of actual checks, separately from SG settings
    $service_list.each | String $service_name, Hash $data |{

      $merged_defaults = merge($service_defaults, $s_list['settings']) # global service defaults + service group defaults
      $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)

      # If we override default check_timeout, but not nrpe_timeout, make nrpe_timeout the same as check_timeout
      if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
        # NB: Icinga will convert 1m to 60 automatically!
        $nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
      } else {
        $nrpe = {}
      }

      # By default we use nrpe and all commands are run via nrpe. So vars.nrpe_command = $service_name is a default value
      # If it's server-side Icinga command - we don't need 'nrpe_command'
      # but there is no harm to have that var and the code is shorter

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

      # Assembling $vars from Global Default service settings, servicegroup settings, this particular check settings and let's not forget nrpe settings.
      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 is required everywhere, otherwise becomes "The value '' cannot be converted to Numeric"

      $service_notify_group = $service_notify ? {
        []      => $service_defaults['vars']['notify_group'],
        default => $service_notify
      } # Assing default group (systems) if no other groups are defined

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

      # This needs to be merged separately, because merging it as part of MERGED_DATA overwrites arrays instead of merging them, so we lose some "assign" and "ignore" values

      $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'],
      }
    }
  }
#### END OF SERVICES ####

#### OTHER BORING STUFF ####

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

  # Merging notification settings for users with other settings
  $users_oncall = deep_merge($users, $oncall)
  # Magic. Do not touch.
  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',
  }

}

Na týchto rezancoch stále pracujem a vylepšujem ich, ako najlepšie viem. Bol to však tento kód, ktorý nám umožnil použiť jednoduchú a zrozumiteľnú syntax v Hiera:

Dáta

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, každá skupina má predvolené nastavenia ako kde a ako často tieto kontroly spúšťať, aké upozornenia posielať a komu.

V každej kontrole môžete prepísať ľubovoľnú možnosť a to všetko sa v konečnom dôsledku sčítava s predvolenými nastaveniami všetkých kontrol ako celku. Preto je taký nezmysel napísaný v config.pp - spája všetky predvolené nastavenia so skupinovými nastaveniami a potom s každou jednotlivou kontrolou.

Ďalšou veľmi dôležitou zmenou bola možnosť používať funkcie v nastaveniach, napríklad funkcia nahradenia portu, adresy a url na 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 je v definícii hostiteľa premenná http_port — použite, inak 443. Napríklad webové rozhranie jabber visí na 9090 a Unifi visí na 7443.
http_vhost znamená ignorovať DNS a vziať túto adresu.
Ak je uri špecifikované v hostiteľovi, postupujte podľa neho, v opačnom prípade použite „/“.

S http_ssl vyšiel vtipný príbeh - táto infekcia sa nechcela na požiadanie odpojiť. Dlho som bol zmätený z tohto riadku, až kým mi nedošlo, že premenná v definícii hostiteľa je:

http_ssl: false

Nahradené do výrazu

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

ako nepravdivý a nakoniec sa ukáže

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

to znamená, že kontrola ssl je vždy aktívna. Vyriešil som to nahradením syntaxe:

http_ssl: no

Závery:

Pros:

  • Teraz máme jeden monitorovací systém a nie dva, ako sme mali za posledných 7-8 mesiacov, alebo jeden, ktorý je zastaraný a zraniteľný.
  • Štruktúra údajov hostiteľov/služieb (kontrol) je teraz (podľa môjho názoru) oveľa čitateľnejšia a zrozumiteľnejšia. Pre ostatných to nebolo také zrejmé, takže som musel uverejniť pár stránok na miestnej wiki, aby som vysvetlil, ako to všetko funguje a čo kde upraviť.
  • Kontroly je možné flexibilne konfigurovať pomocou premenných a funkcií, napríklad kontrolovať http_regexp, v nastaveniach hostiteľa je možné nastaviť požadovaný vzor, ​​návratový kód, url a port.
  • Existuje niekoľko dashboardov, pre každý z nich si môžete definovať vlastný zoznam zobrazených alarmov a to všetko spravovať prostredníctvom žiadostí Puppet a merge.

Nevýhody:

  • Zotrvačnosť členov tímu - Nagios pracoval, pracoval a pracoval a táto vaša Isinga je neustále zabugovaná a pomalá. Ako tu môžete vidieť históriu? Ach, sakra, nie je to aktualizované... (Skutočný problém je, že história alarmov sa neaktualizuje automaticky, iba pomocou F5)
  • Zotrvačnosť systému - keď vo webovom rozhraní kliknem na „aktualizovať“ (skontrolovať teraz) - výsledok vykonania závisí od počasia na Marse, najmä od zložitých služieb, ktoré vyžadujú desiatky sekúnd. Takýto výsledok je normálny. Migrácia z Nagios do Icinga2 v Austrálii
  • Vo všeobecnosti, podľa polročných štatistík fungovania dvoch systémov vedľa seba, Nagios vždy fungoval rýchlejšie ako Icinga a to ma naozaj hnevalo. Zdá sa mi, že niečo pokazili s časomierou a kontrola sa robí každých päť minút, v skutočnosti sa to deje každých 5:30 alebo podobne.
  • Ak kedykoľvek reštartujete službu (systemctl reštart icinga2) - všetky kontroly, ktoré v tom čase prebiehali, vygenerujú kritický alarm na obrazovke a zvonku to vyzerá, akoby všetko spadlo (potvrdená chyba).

Celkovo to však funguje.

Zdroj: hab.com

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