Migrazione da Nagios a Icinga2 in Australia

Ciao a tutti.

Sono un amministratore di sistema Linux, trasferito dalla Russia in Australia con un visto professionale indipendente nel 2015, ma questo articolo non tratta di come un maialino possa acquistare un trattore. Ce ne sono già abbastanza di articoli del genere (comunque, se c'è interesse, scriverò anche su questo), quindi vorrei raccontare di come, nel mio lavoro in Australia come ingegnere Linux Ops, sono stato l'ideatore della migrazione da un sistema di monitoraggio a un altro. In particolare, da Nagios a Icinga2.

L'articolo è in parte tecnico e in parte riguarda la comunicazione con le persone e le difficoltà legate alle differenze culturali e ai metodi di lavoro.

Sfortunatamente, il tag «code» non evidenzia il codice Puppet e YAML, quindi ho dovuto utilizzare «plaintext».

Niente lasciava presagire problemi la mattina del 21 dicembre 2016. Come al solito, stavo leggendo Habr in modo anonimo nelle prime mezza ora della giornata lavorativa, bevendo caffè e mi sono imbattuto in questo articolo.

Poiché nella mia azienda usavamo Nagios, ho creato un ticket in Redmine e ho condiviso il link nella chat di gruppo, ritenendolo importante. L'iniziativa è punibile anche in Australia, quindi il senior engineer ha assegnato questo problema a me, dato che l'ho scoperto.

Screenshot da RedmineMigrazione da Nagios a Icinga2 in Australia

Nel nostro dipartimento, prima di esprimere la propria opinione, è consuetudine proporre almeno un'alternativa, anche se la scelta è ovvia, quindi ho iniziato a cercare su Google quali siano al momento i sistemi di monitoraggio rilevanti, dato che in Russia, nel mio ultimo lavoro, avevo il mio sistema personalizzato, molto primitivo, ma decisamente funzionante e in grado di svolgere tutti i compiti assegnati. Python, il Politecnico di San Pietroburgo e la metro sono fantastici. No, la metro è scadente. È una questione personale (11 anni di lavoro) e merita un articolo a parte, ma non ora.

Un po' sulle regole di modifica della configurazione dell'infrastruttura nel mio attuale lavoro. Utilizziamo Puppet, Gitlab e il principio dell'Infrastructure as Code, quindi:

  • Nessuna modifica manuale tramite SSH attraverso la modifica manuale di file sulle macchine virtuali. In tre anni di lavoro, ho ricevuto molte volte dei richiami per questo, l'ultimo solo una settimana fa, e non penso che sarà l'ultimo. Insomma, correggere una riga nel file di configurazione, riavviare il servizio e vedere se il problema si è risolto richiede 10 secondi. Creare un nuovo ramo in GitLab, caricare le modifiche, aspettare che r10k faccia il suo lavoro sul Puppetmaster, avviare Puppet —environment=mybranch e attendere qualche minuto che tutto venga eseguito richiede almeno 5 minuti.
  • Qualsiasi modifica viene effettuata creando una Merge Request in GitLab e necessita dell'approvazione di almeno un membro del team. Le modifiche significative, secondo la decisione del team leader, richiedono due o tre approvazioni.
  • Tutte le modifiche sono in un certo senso testuali (poiché i manifesti di Puppet, gli script e i dati di Hiera sono testi), i file binari sono fortemente sconsigliati e per l'approvazione di tali file sono necessarie valide motivazioni.

Quindi, le opzioni che ho considerato:

  • Munin — se nell'infrastruttura ci sono più di 10 server, l'amministrazione diventa un inferno (da di questo articolo. Non avevo molta voglia di verificarlo, quindi ho preso la parola per buono).
  • Zabbix — l'ho guardato a lungo, ancora in Russia, ma allora era eccessivo per le mie esigenze. Qui — ho dovuto scartarlo a causa dell'uso di Puppet come gestore di configurazione e GitLab come sistema di controllo versioni. A quel tempo, per quanto ho capito — Zabbix memorizza tutte le configurazioni nel database, il che ha reso poco chiaro come gestire la configurazione nelle attuali condizioni e come tenere traccia delle modifiche.
  • Prometheus — quello a cui arriveremo alla fine, a giudicare dall'atmosfera nel reparto, ma all'epoca non sono riuscito a padroneggiarlo e non sono stato in grado di dimostrare un campione funzionante (Proof of Concept), quindi ho dovuto rinunciare.
  • Ci sono stati anche altri pochi options, che richiedevano una revisione completa del sistema, oppure erano in uno stato embrionale / trascurati e per questo motivo sono stati scartati.

Alla fine ho scelto Icinga2 per tre motivi:

1 — compatibilità con Nrpe (il servizio client che esegue controlli secondo i comandi di Nagios). Questo era molto importante perché al momento avevamo 135 (ora nel 2019 sono 165) macchine virtuali con un sacco di servizi/controlli personalizzati e rifare tutto questo sarebbe stato un grande problema.
2 — tutti i file di configurazione sono testuali, il che consente una facile modifica, oltre a creare merge requests con la possibilità di vedere cosa è stato aggiunto o rimosso.
3 — è un progetto OpenSource vivo e in evoluzione. A noi piace molto l'OpenSource e contribuiamo attivamente creando Pull Requests e Issues per risolvere problemi.

Quindi, andiamo, Icinga2.

La prima difficoltà è stata l'inerzia dei colleghi. Tutti erano abituati a Nagios/Nagios (anche qui non siamo riusciti a trovare un compromesso su come pronunciarlo) e all'interfaccia di CheckMK. Con Icinga, l'interfaccia è completamente diversa (questo era uno svantaggio), ma c'è la possibilità di personalizzare in modo flessibile cosa vedere tramite filtri su qualsiasi parametro (questo è stato un vantaggio, ma ho dovuto lottare molto per questo).

FiltriMigrazione da Nagios a Icinga2 in Australia

Valuta la relazione tra le dimensioni della barra di scorrimento e le dimensioni dell'area di scorrimento.

Il secondo punto è che tutti erano abituati a visualizzare l'intera infrastruttura su un unico monitor, poiché CheckMk consente di gestire più host Nagios, ma l'interfaccia di Icinga non lo sapeva fare (in realtà lo sapeva, ma lo spiegherò più avanti). L'alternativa era qualcosa chiamato Thruk, ma il suo design suscitava reazioni di disgusto in tutti i membri del team, tranne uno — colui che lo aveva proposto (non io).

A Thruk, quindi — decisione unanime del teamMigrazione da Nagios a Icinga2 in Australia

Dopo qualche giorno di brainstorming, ho proposto l'idea del monitoraggio dei cluster, con un master host nella zona di produzione e due slave — uno in dev/test e uno esterno, situato presso un altro provider, per monitorare i nostri servizi dal punto di vista del cliente o di un osservatore esterno. Questa configurazione consentiva di vedere tutti i problemi in un'unica interfaccia web e funzionava correttamente, ma Puppet... Il problema con Puppet era che il master host doveva ora conoscere tutti gli host e i servizi/controlli nel sistema e doveva distribuirli tra le zone (dev-test, staging-prod, ext), ma inviare le modifiche tramite l'API di Icinga richiede qualche secondo, mentre la compilazione del catalogo Puppet di tutti i servizi per tutti gli host richiede alcuni minuti. Questo viene ancora considerato un mio difetto, anche se ho già spiegato più volte come funziona tutto e perché ci vogliono così tanti tempi.

Il terzo punto riguarda un gran numero di SnowFlakes (fiocchi di neve) — elementi che si discostano dal sistema generale perché contengono qualcosa di speciale, quindi le regole generali non si applicano. È stato affrontato con un attacco frontale: se ci sono avvisi, ma in realtà tutto va bene, significa che bisogna scavare più a fondo per capire perché mi segnala un avviso quando non dovrebbe. Oppure, al contrario — perché Nagios entra in allerta ma Icinga no.

Il quarto punto è che Nagios ha lavorato qui per tre anni prima di me e inizialmente c'era più fiducia in lui rispetto al mio sistema hipster di nuova concezione, quindi ogni volta che Icinga avvertiva un allerta — nessuno faceva nulla finché Nagios non si allertava sulla stessa questione. Tuttavia, molto raramente Icinga emetteva avvisi reali prima di Nagios, e considero questo un grave errore, di cui parlerò nella sezione «Conclusioni».

Alla fine, l'implementazione ha subito un ritardo di oltre 5 mesi (inizialmente prevista per il 28 giugno 2018, effettivamente realizzata il 3 dicembre 2018), principalmente a causa del «parity check» — quella cosa in cui ci sono diversi servizi in Nagios che nessuno ha sentito nominare negli ultimi due anni, ma PROPRIO ADESSO hanno, cavolo, emesso un crit senza alcun motivo e ho dovuto spiegare perché non erano presenti nel mio pannello, e ho dovuto aggiungerli in Icinga, affinché il «parity check sia completo» (Tutti i servizi/verifiche in Nagios corrispondono ai servizi/verifiche in Icinga)

Implementazione:
Primo — guerra tra Code e Data, in stile Puppet. Tutti i dati, proprio tutti, devono essere in Hiera e nient'altro. Tutto il codice è in file .pp. Variabili, astrazioni, funzioni — tutto va in pp.
Come risultato, abbiamo una miriade di macchine virtuali (165 al momento della scrittura di questo articolo) e 68 applicazioni web che devono essere monitorate per verificare la loro funzionalità e la validità dei certificati SSL. Ma a causa di problemi storici, le informazioni per il monitoraggio delle applicazioni provengono da un repository gitlab separato e il formato dei dati non è cambiato dai tempi di Puppet 3, il che crea ulteriori difficoltà nella configurazione.

Codice Puppet per le applicazioni, curate i vostri occhi

definire profili::servizi::monitoraggio::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'], {} )) } # aggiunge notifiche per il gruppo predefinito (sistemi) + qualsiasi gruppo definito 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')        # Scegli una regex per il controllo

    $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 | {        # Dividi un'app per domini se ci sono due o più
      $vhost_name = {'http_vhost' => $vhost}

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

      $web_ipaddress = is_array($vdata['web_ipaddress']) ? {  # Rendi l'indirizzo IP un array se non lo è, perché askizzy ha 2 ips ed è un array
        true  => $vdata['web_ipaddress'],
        false => [$vdata['web_ipaddress']],
      }

      $access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Unisci la zona predefinita (dove è definita l'app) e le zone extra se esistono
      $web_ipaddress.each | String $ip_address | {            # Per ogni IP (se abbiamo più di uno)
        $suffix = length($web_ipaddress) ? {                  # Se ne abbiamo più di uno - aggiungi l'IP come suffisso a questo hostname per evitare di duplicare risorse
          1       => '',
          default => "_${ip_address}"
        }
        $octets = split($ip_address, '.')
        $ip_tag = "${octets[2]}.${octets[3]}" # Usare solo l'ultimo ottetto causa una collisione tra nginx-vip 203.15.70.94 e 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}" # Se è un host per ext - il prefisso diventa '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
              }
            }
          }
        }
      }
    }
  }
}

Il codice di configurazione degli host e dei servizi appare davvero terribile:

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

}

Sto ancora lavorando su questo codice e lo miglioro ogni volta che posso. Tuttavia, è proprio questo codice che ha reso possibile utilizzare una sintassi semplice e comprensibile in Hiera:

Dati

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'

Tutti i controlli sono raggruppati e ogni gruppo ha impostazioni predefinite su dove e con quale frequenza eseguire questi controlli, quali notifiche inviare e a chi.

In ogni verifica puoi sovrascrivere qualsiasi opzione, il che si accumula anche con le impostazioni predefinite di tutte le verifiche in generale. Ecco perché in config.pp c'è una simile complessità: lì si effettua una fusione di tutte le impostazioni predefinite con quelle dei gruppi e poi con ogni singola verifica.

Un cambiamento molto importante è stato anche il fatto di poter utilizzare funzioni nelle impostazioni, per esempio, la funzione di sostituzione della porta, dell'indirizzo e dell'URL per il controllo 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

Questo significa che se nella definizione dell'host c'è una variabile http_port — usarla, altrimenti utilizzare 443. Ad esempio, l'interfaccia web di jabber è su 9090, mentre Unifi è su 7443.
http_vhost significa ignorare il DNS e prendere questo indirizzo.
Se nel host è specificato un uri, seguirlo, altrimenti prendere «/».

Con http_ssl è emersa una storia divertente: questo accrocchio non voleva disattivarsi su richiesta. Ho impiegato del tempo a capire questa riga, finché non mi è venuto in mente che nella variabile nella definizione dell'host:

http_ssl: false

Si inserisce nell'espressione

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

ZFS archivia i dati su disco. false e alla fine si ottiene

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

quindi, il controllo ssl risulta sempre attivo. Si è risolto sostituendo la sintassi:

http_ssl: no

Conclusioni:

Vantaggi:

  • Ora abbiamo un sistema di monitoraggio unico, e non due, come negli ultimi 7-8 mesi, o uno, obsoleto e vulnerabile.
  • La struttura dei dati degli host / servizi (controlli) ora è (a mio avviso) molto più leggibile e comprensibile. Per altri non è risultato così ovvio, quindi ho dovuto creare un paio di pagine nella wiki locale per spiegare come funziona tutto e cosa modificare.
  • C'è la possibilità di impostare le verifiche in modo flessibile tramite variabili e funzioni; ad esempio, per il controllo http_regexp, il pattern cercato, il codice di ritorno, url e porta possono essere impostati nelle impostazioni dell'host.
  • Ci sono diversi pannelli (dashboard) per ciascuno dei quali è possibile definire la propria lista di avvisi visualizzati e gestire tutto ciò tramite Puppet e merge request.

Contro:

  • Inerzia dei membri del team - Nagios ha funzionato, funzionato e funzionato, mentre la tua Icinga continua a bloccarsi e rallentare. Ma come faccio a vedere la cronologia? Ah, cavolo, non si aggiorna… (Problema reale: la cronologia degli avvisi non si aggiorna automaticamente, solo con F5)
  • Inerzia del sistema - quando clicco su "controlla ora" nell'interfaccia web, il risultato dell'esecuzione dipende dalla situazione su Marte, soprattutto sui servizi complessi che richiedono decine di secondi per l'esecuzione. Un simile risultato è la norma. Migrazione da Nagios a Icinga2 in Australia
  • Nel complesso, dal punto di vista delle statistiche semestrali di funzionamento di entrambi i sistemi affiancati, Nagios ha sempre lavorato più velocemente di Icinga e questo mi ha molto infastidito. Mi sembra che abbiano fatto confusione con i timer e che il controllo ogni cinque minuti in realtà avvenga ogni 5:30 o qualcosa di simile.
  • Se si riavvia il servizio in qualsiasi momento (systemctl restart icinga2) — tutte le verifiche che stavano eseguendosi daranno un allerta critical sullo schermo e da parte di chi guarda sembra che tutto sia andato in crash.bug confermato).

Ma in generale — funziona.

Fonte: habr.com

Acquista un hosting affidabile per siti con protezione DDoS, server VPS VDS 🔥 Acquista un hosting affidabile per siti con protezione DDoS, server VPS VDS | ProHoster