オーストラリアの Nagios から Icinga2 への移行

こんにちは。

私は、2015 年に独立した専門家ビザでロシアからオーストラリアに移住した Linux システム管理者ですが、この記事は豚がトラクターを始動する方法についてのものではありません。このような記事はすでにたくさんありますが (ただし、興味があればこれについても書きます)、オーストラリアで Linux 運用エンジニアとして働いているときに、ある監視システムから別の監視システムへの移行をどのように開始したかについてお話ししたいと思います。具体的には、Nagios => Icinga2 です。

この記事は、一部は技術的な内容で、一部は人々とのコミュニケーションや、文化や仕事の方法の違いに関連する問題について書かれています。

残念ながら、「code」タグでは Puppet および yaml コードが強調表示されないため、「plaintext」を使用する必要がありました。

21年2016月XNUMX日の朝、トラブルの兆候はなかった。いつものように、私は仕事の最初のXNUMX分間、コーヒーを飲みながら、未登録の匿名ユーザーとしてHabrを読んでいたところ、 この記事.

私の会社ではたまたま Nagios を使用していたので、Redmine でチケットを作成し、そのリンクを一般チャットに送信することが重要だと考え、迷わずにそれを実行しました。オーストラリアでも積極性は罰せられるものなので、主任エンジニアは私が問題を発見した時から、その責任を私に押し付けました。

Redmineからのスクリーンショットオーストラリアの Nagios から Icinga2 への移行

私たちの部門では、意見を述べる前に、たとえ選択肢が明白であっても、少なくとも 11 つの代替案を提示するのが慣例となっています。そこで私は、現在関連性のある監視システムを Google で検索することから始めました。というのも、ロシアの前の職場では、非常に原始的でしたが、それでも十分に機能し、割り当てられたすべてのタスクを実行していた独自の自家製システムを使用していたからです。 Python、サンクトペテルブルク工科大学、そして地下鉄のルール。いや、地下鉄は最悪だ。これは個人的なこと(XNUMX 年間の取り組み)であり、別の記事で書く価値があるのですが、今ではありません。

現在の場所のインフラストラクチャ構成に変更を加えるためのルールについて少し説明します。私たちは Puppet、Gitlab、Infrastructure as a Code を使用しているので、次のようになります。

  • 仮想マシン上のファイルを手動で変更しても、SSH 経由で手動で変更されることはありません。 10年間の勤務中、私はこのことで何度も叱責を受けました。最後に叱責されたのは10週間前ですが、これが最後ではなかったと思います。まあ、実際には、構成内の 5 行を修正し、サービスを再起動して問題が解決したかどうかを確認します (XNUMX 秒)。 Gitlab で新しいブランチを作成し、変更をプッシュし、Puppetmaster で rXNUMXk が実行されるのを待ち、Puppet --environment=mybranch を実行して、すべてが実行されるまでさらに数分 (最低 XNUMX 分) 待ちます。
  • 変更はすべて Gitlab でマージ リクエストを作成することによって行われ、少なくとも 1 人のチーム メンバーからの承認が必要です。チームリーダーが決定した大きな変更には、2 回または 3 回の承認が必要です。
  • すべての変更は何らかの形でテキストです (Puppet マニフェスト、スクリプト、および Hiera データはテキストであるため)。バイナリ ファイルの使用は強く推奨されません。また、このようなファイルを承認するには正当な理由が必要です。

そこで私が検討した選択肢は次の通りです。

  • Munin - インフラストラクチャに10台以上のサーバーがある場合、管理は地獄になります( この記事。私は特にこれをテストしたいとは思っていなかったので、彼の言葉をそのまま信じました。
  • Zabbix – ロシアにいた頃から長い間検討してきましたが、当時は私のタスクには負担が大きすぎました。ここでは、構成マネージャーとして Puppet を使用し、バージョン管理システムとして Gitlab を使用しているため、破棄する必要がありました。当時、私が理解していた限りでは、Zabbix は設定全体をデータベースに保存するため、現状で設定をどのように管理し、変更をどのように追跡すればよいのかが不明でした。
  • 部門の雰囲気から判断すると、最終的には Prometheus にたどり着くことになりますが、当時はそれを習得しておらず、実際に機能するサンプル (概念実証) を示すことができなかったため、断念せざるを得ませんでした。
  • 他にも、システムの完全な再設計を必要とするオプションや、初期段階または放棄された状態にあり、同じ理由で拒否されたオプションがいくつかありました。

最終的に、次の 2 つの理由から IcingaXNUMX を選択しました。

1 - Nrpe (Nagios からのコマンドのチェックを実行するクライアント サービス) との互換性。これは非常に重要でした。当時、多数のカスタム サービス/チェックを備えた仮想マシンが 135 台 (現在 2019 年には 165 台) あり、これをすべてやり直すのは非常に面倒だったからです。
2 - すべての構成ファイルはテキストなので、この問題の編集が容易になり、追加または削除された内容を確認しながらマージ リクエストを作成できます。
3 は、現在も成長を続けるオープンソース プロジェクトです。私たちはオープンソースを愛しており、プルリクエストや問題を作成して問題を解決することで、できる限りオープンソースに貢献しています。

さあ、始めましょう、Icinga2。

私が最初に対処しなければならなかったのは、同僚たちの惰性でした。誰もが Nagios/Nadjios (ここでも発音の仕方については妥協できませんでしたが) と CheckMK インターフェースに慣れています。 Icinga のインターフェースは見た目がまったく異なります (これはマイナスです) が、文字通りあらゆるパラメータのフィルターを使用して、表示する必要のあるものを柔軟にカスタマイズできます (これはプラスですが、私はそのために苦労しました)。

フィルターオーストラリアの Nagios から Icinga2 への移行

スクロールバーのサイズとスクロール フィールドのサイズの比率を評価します。

2 番目に、CheckMk を使用すると複数の Nagios ホストを操作できるため、誰もが 1 つのモニターでインフラストラクチャ全体を確認することに慣れていますが、Icinga インターフェースではこれができませんでした (実際には可能ですが、詳細は後述します)。代替案は Thruk と呼ばれるものでしたが、そのデザインは、それを提案した 1 人 (私ではありません) を除くチーム全員をうんざりさせました。

スリュクを火の中に投げ込む - チーム全員一致の決定オーストラリアの Nagios から Icinga2 への移行

数日間のブレインストーミングの後、私はクラスター監視のアイデアを思いつきました。これは、実稼働ゾーンにマスターホストが 1 つ、スレーブが 2 つ (開発/テストに 1 つ、別のプロバイダーにある外部ホストに 1 つ) あり、クライアントまたは外部の観察者の観点からサービスを監視することを目的としています。この構成により、1 つの Web インターフェースですべての問題を確認でき、非常にうまく機能しましたが、Puppet では... Puppet の問題は、マスター ホストがシステム内のすべてのホストとサービス/チェックを認識し、それらをゾーン (dev-test、staging-prod、ext) 間で配布する必要があることです。ただし、Icinga API 経由で変更を送信するには数秒かかりますが、すべてのホストのすべてのサービスの Puppet カタログをコンパイルするには数分かかります。すべてがどのように機能し、なぜそれほど時間がかかるのかをすでに何度か説明しているにもかかわらず、いまだにこのことで私を責められています。

3 つ目は、スノーフレークと呼ばれる特別な性質を持つため一般的なシステムから外れた存在が多数存在することです。そのため、一般的なルールはスノーフレークには適用されません。それは正面攻撃によって解決されました - アラームはあっても、実際にはすべて正常である場合は、ここでさらに深く掘り下げて、なぜ警告が出ているのかを理解する必要があります。またはその逆 - なぜ Nagios はパニックに陥っているのに、Icinga はパニックに陥っていないのでしょうか。

4 番目に、Nagios は私より 3 年間ここで働いており、当初は私の最新式のヒップスター システムよりも信頼されていたため、Icinga がパニックになり始めたときは、Nagios が同じ問題で興奮するまで誰も何もしませんでした。しかし、Icinga が Nagios よりも早く実際のアラームを発行することは非常に稀であり、私はこれを重大な欠陥であると考えています。これについては、「結論」のセクションで説明します。

結局、コミッショニングは5か月以上(28年2018月3日の予定、実際は2018月XNUMX日)遅れました。主な原因は「パリティチェック」です。Nagiosには複数のサービスがあり、ここ数年誰もそのことを耳にしていなかったのですが、今まさに、何の理由もなく批判が出てきて、なぜ自分のパネルにサービスがないのかを説明し、「パリティチェックが完了」するようにIcingaに追加する必要がありました(Nagiosのすべてのサービス/チェックは、Icingaのサービス/チェックと一致しています)。

実装:
1 つ目は、Puppet スタイルのようなコード対データの戦いです。すべてのデータ、絶対にすべてのデータは Hiera に保存する必要があり、他には何も保存しないでください。すべてのコードは .pp ファイルにあります。変数、抽象化、関数 - すべてが pp に入ります。
その結果、多数の仮想マシン (執筆時点で 165 台) と、SSL 証明書の機能と有効性を監視する必要がある 68 個の Web アプリケーションが存在します。しかし、これまでの煩わしさから、監視アプリケーションの情報は別の gitlab リポジトリから取得され、データ形式は Puppet 3 以降変更されていないため、構成にさらなる困難が生じます。

アプリケーション用のPuppetコードで目を保護

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

ホストとサービスの構成コードもひどいようです:

監視/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',
  }

}

私はまだこの麺に取り組んでおり、できる限り改良しています。ただし、このコードにより、Hiera でシンプルで明確な構文を使用できるようになりました。

データ

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'

すべてのチェックはグループに分かれており、各グループには、チェックを実行する場所と頻度、送信する通知の内容と送信先などのデフォルト設定があります。

各チェックでは、任意のオプションを再定義することができ、最終的にはすべてのチェック全体のデフォルト設定になります。そのため、config.pp にはこのようなナンセンスが書き込まれます。すべてのデフォルト設定がグループ設定とマージされ、その後、個々のチェックがマージされます。

もう 1 つの非常に重要な変更は、設定で関数を使用できるようになったことです。たとえば、http_regex をチェックするためのポート、アドレス、URL を置き換える機能などです。

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

これは、ホスト定義に変数がある場合 http_ポート — 使用しない場合は 443 を使用します。たとえば、Jabber Web インターフェイスは 9090 でハングし、Unifi は 7443 でハングします。
http_vhost DNSを無視してこのアドレスを取得することを意味します。
ホストが URI を指定している場合はそれに従い、そうでない場合は「/」を使用します。

http_ssl では面白いことが起こりました。この感染は要求に応じて停止しようとしなかったのです。私はこの行で長い間行き詰まっていましたが、ホスト定義の変数が次のことに気付きました。

http_ssl: false

式に代入すると

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

方法 false そして結局

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

つまり、SSL チェックは常にアクティブになります。構文を変更することで解決しました。

http_ssl: no

所見:

長所:

  • 過去 7 ~ 8 か月間使用していた XNUMX つの監視システム、または時代遅れで脆弱な XNUMX つの監視システムではなく、現在 XNUMX つの監視システムしかありません。
  • ホスト/サービス (チェック) のデータ構造は (私の意見では) はるかに読みやすく、理解しやすくなりました。他の人にとってはそれほど明白ではなかったので、ローカル Wiki にいくつかのページを作成して、すべてがどのように機能し、どこで何を編集するかを説明する必要がありました。
  • 変数と関数を使用してチェックを柔軟に構成することが可能です。たとえば、http_regexp チェックの場合、検索パターン、戻りコード、URL、ポートをホスト設定で指定できます。
  • ダッシュボードは複数あり、それぞれに表示するアラームの独自のリストを定義し、すべてを Puppet とマージ リクエストを介して管理できます。

短所:

  • チーム メンバーの惰性 - Nagios はずっと動作し続けていますが、あなたの Isinga は頻繁に不具合が発生し、速度が低下します。ここで履歴を見るにはどうすればいいですか?ああ、くそ、更新されない...(本当の問題は、アラーム履歴が自動的に更新されず、F5 キーでのみ更新されることです)
  • システムの慣性 - Web インターフェースで「更新」(今すぐチェック)をクリックすると、実行結果は火星の天候に依存します。特に、実行に数十秒かかる複雑なサービスの場合はその傾向が顕著です。このような結果は正常です。 オーストラリアの Nagios から Icinga2 への移行
  • 全体的に、5 つのシステムを並行して稼働させた 30 か月間の統計によると、Nagios は常に Icinga よりも高速に動作しており、これは私を本当にイライラさせました。タイマーの何かがおかしくなって、XNUMX 分ごとのチェックが実際には XNUMX:XNUMX ごとなどに行われるようになったように思います。
  • いつでもサービスを再起動すると(systemctl restart icinga2)、その時点で進行中のすべてのチェックで重大なアラームが生成されます。画面上と横から見ると、すべてが落ちたように見える(確認されたバグ).

しかし、全体的にはうまく機能します。

出所: habr.com

DDoS 保護機能を備えた信頼性の高いサイト用ホスティング、VPS VDS サーバーを購入する 🔥 DDoS攻撃対策付きの信頼性の高いウェブサイトホスティング、VPS/VDSサーバーを購入しましょう | ProHoster