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 <<| |>> # sarà abilitato quando passeremo completamente a 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('Assicurati che il nodo abbia il fattore $location impostato (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 <<| |>> # Questo è per l'invasione delle navette quando sarà pronta.
  $hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')

  $hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | {           # Separare le liste di sito per gruppi di host - 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 |{  # Separare le liste di gruppo per host
      # Questo host è nell'array $hosts_has_large_disks ? In tal caso imposta 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)) # Fusione delle variabili separatamente

      $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){                                                                # Tutti gli host gestiti tramite API
    $hosts_api.each | String $zone, Hash $hosts_api_zone | {                            # Separare gli host API per zone
      $hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | {   # Separare le liste di sito per gruppi di host - 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 |{        # Separare le liste di gruppo per host
          # Questo host è nell'array $hosts_has_large_disks ? In tal caso imposta 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'],
          }
        }
      }
    }
  }

#### FINE DEGLI HOST ####

####   SERVIZI   ####

  $services.each | String $service_group, Hash $s_list |{             # Gruppo di servizi e lista di servizi in quel gruppo
    $service_list = $s_list['checks']                                 # Lista di controlli effettivi, separatamente dalle impostazioni SG
    $service_list.each | String $service_name, Hash $data |{

      $merged_defaults = merge($service_defaults, $s_list['settings']) # impostazioni di servizio globali + impostazioni di gruppo di servizio
      $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)

      # Se sovrascriviamo check_timeout di default, ma non nrpe_timeout, rendiamo nrpe_timeout uguale a check_timeout
      if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
        # NB: Icinga convertirà 1m in 60 automaticamente!
        $nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
      } else {
        $nrpe = {}
      }

      # Di default utilizziamo nrpe e tutti i comandi sono eseguiti tramite nrpe. Quindi vars.nrpe_command = $service_name è un valore predefinito
      # Se è un comando Icinga lato server - non abbiamo bisogno di 'nrpe_command'
      # ma non è dannoso avere quella variabile e il codice è più corto

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

      # Assemblare $vars da impostazioni globali di servizio di default, impostazioni del gruppo di servizio, impostazioni di questo particolare controllo e non dimentichiamo le impostazioni 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 è richiesto ovunque, altrimenti diventa "Il valore '' non può essere convertito in numerico"

      $service_notify_group = $service_notify ? {
        []      => $service_defaults['vars']['notify_group'],
        default => $service_notify
      } # Assegna il gruppo predefinito (sistemi) se non sono definiti altri gruppi

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

      # Questo deve essere fuso separatamente, perché fonderlo come parte di MERGED_DATA sovrascrive gli array invece di fonderli, quindi perdiamo alcune "assegna" e "ignora" valori

      $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'],
      }
    }
  }
#### FINE DEI SERVIZI ####

#### ALTRE COSE NOIOSE ####

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

  # Fusione delle impostazioni di notifica per utenti con altre impostazioni
  $users_oncall = deep_merge($users, $oncall)
  # Magia. Non toccare.
  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 hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster