הגירה מנאג'וס ל-Icinga2 באוסטרליה

שלום לכולם.

אני מנהל מערכת לינוקס, עברתי מרוסיה לאוסטרליה עם ויזה מקצועית עצמאית בשנת 2015, אבל המאמר לא יעסוק איך להתחיל טרקטור לחזיר. יש כבר מספיק מאמרים כאלה (למרות זאת, אם יש עניין, אני אכתוב גם על זה), אז אני רוצה לדבר על איך, בעבודה שלי באוסטרליה כמהנדס לינוקס, יזמתי הגירה מניטור מערכת אחת לאחר. ספציפית - Nagios => Icinga2.

המאמר בחלקו טכני ובחלקו עוסק בתקשורת עם אנשים ובבעיות הקשורות להבדלים בתרבות ובשיטות עבודה.

לרוע המזל, התג "קוד" אינו מדגיש את קוד ה-Puppet ו-yaml, אז נאלצתי להשתמש ב"טקסט רגיל".

לא היו סימני צרות בבוקר ה-21 בדצמבר 2016. כרגיל, קראתי את "הבר" על ידי משתמש אנונימי לא רשום בחצי השעה הראשונה של יום העבודה, תוך כדי שתיית קפה, ונתקלתי מאמר זה.

מכיוון שהחברה שלי השתמשה ב-Nagios, בלי לחשוב פעמיים, יצרתי כרטיס ב-Redmine ושלחתי את הקישור לצ'אט הכללי, כי חשבתי שזה חשוב. היוזמה ניתנת לעונש אפילו באוסטרליה, אז המהנדס הראשי הצמיד את הבעיה עליי מאז שגיליתי אותה.

צילום מסך מ-Redmineהגירה מנאג'וס ל-Icinga2 באוסטרליה

במחלקה שלנו, לפני שמביעים את דעתכם, נהוג להציע לפחות חלופה אחת, גם אם הבחירה ברורה, אז התחלתי בגוגל איזה סוג של מערכות ניטור רלוונטיות כרגע, שכן ברוסיה במקום עבודתי האחרון אני הייתה לי מערכת בכתב עצמי, מאוד פרימיטיבית, אבל בכל זאת די עובדת ומבצעת את כל המשימות שהוטלו עליה. פייתון, פוליטכניק סנט פטרסבורג ושלטון המטרו. לא, הרכבת התחתית מבאסת. זה אישי (11 שנות עבודה) וראוי למאמר נפרד, אבל לא עכשיו.

קצת על הכללים לביצוע שינויים בתצורת התשתית במקום הנוכחי שלי. אנו משתמשים ב-Puppet, Gitlab והתשתית כעיקרון קוד, לכן:

  • אין שינויים ידניים באמצעות SSH על ידי שינוי ידני של קבצים כלשהם במכונות הוירטואליות. במהלך שלוש שנות העבודה, חטפתי כובע על כך פעמים רבות, הפעם האחרונה הייתה לפני שבוע ואני לא חושב שזו הייתה הפעם האחרונה. ובכן, באמת - תקן שורה אחת בתצורה, הפעל מחדש את השירות ותראה אם ​​הבעיה נפתרה - 10 שניות. צור סניף חדש ב-Gitlab, דחף את השינויים, המתן ש-r10k יעבוד על Puppetmaster, הפעל את Puppet -environment=mybranch והמתן עוד כמה דקות עד שהכל יעבוד - מינימום 5 דקות.
  • כל שינוי מתבצע על ידי יצירת בקשת מיזוג ב-Gitlab וחייב להיות מאושרת על ידי לפחות חבר צוות אחד. שינויים גדולים שהוחלט על ידי ראש הצוות דורשים שניים או שלושה אישורים.
  • כל השינויים הם טקסט בצורה כזו או אחרת (מכיוון שמניפסטים של Puppet, סקריפטים ונתוני Hiera הם טקסט), קבצים בינאריים מאוד לא מעודדים וצריכה להיות סיבה משכנעת לאשר קבצים כאלה.

אז, האפשרויות ששקלתי:

  • מונין - אם יש יותר מ-10 שרתים בתשתית, הניהול הופך לגיהנום (מ במאמר זה. לא היה לי רצון מיוחד לבדוק את זה, אז לקחתי את המילה שלו על זה).
  • Zabbix - הסתכלתי על זה הרבה זמן, עוד ברוסיה, אבל אז זה היה מיותר למשימות שלי. כאן - נאלץ לזרוק עקב השימוש ב-Puppet כמנהל תצורה ו- Gitlab כמערכת בקרת גרסאות. באותה תקופה, למיטב הבנתי, Zabbix מאחסנת את כל התצורה במסד הנתונים, ולכן לא היה ברור כיצד לנהל את התצורה בתנאים הנוכחיים וכיצד לעקוב אחר שינויים.
  • פרומתאוס זה מה שנגיע אליו בסופו של דבר, אם לשפוט לפי הלך הרוח במחלקה, אבל באותה תקופה לא שלטתי בזה ולא הצלחתי להדגים מדגם עובד באמת (Proof of Concept), אז נאלצתי לסרב.
  • היו גם מספר אפשרויות אחרות שדרשו עיבוד מחדש לחלוטין של המערכת, או שהיו בחיתולין / ננטשו ונדחו מאותה סיבה.

בסופו של דבר, התפשרתי על Icinga2 משלוש סיבות:

1 - תאימות עם Nrpe (שירות לקוחות המריץ בדיקות פקודות מ-Nagios). זה היה חשוב מאוד, מכיוון שבאותו זמן היו לנו 135 (כעת יש 2019 ב-165) מכונות וירטואליות עם חבורה של שירותים/צ'קים שנכתבו בהתאמה אישית, ולחזור על הכל היה כאב אמיתי.
2 - כל קבצי התצורה הם טקסט, מה שמקל על עריכת עניין זה, יצירת בקשות מיזוג עם יכולת לראות מה הוסיף או נמחק.
3 הוא פרויקט OpenSource חי ומתפתח. אנחנו מאוד אוהבים את OpenSource ותורמות לו כל תרומה אפשרית על ידי יצירת Pull Requests and Issues לפתרון בעיות.

אז בוא נלך, Icinga2.

הדבר הראשון שהייתי צריך להתמודד הוא האינרציה של הקולגות שלי. כולם רגילים ל-Nagios/Naggios (למרות שגם כאן לא הצליחו להגיע לפשרה על איך לבטא את זה) ולממשק CheckMK. ממשק ה-icinga נראה שונה לחלוטין (זה היה מינוס), אבל אפשר להתאים באופן גמיש את מה שאתה צריך לראות באמצעות פילטרים עבור כל פרמטר ממש (זה היה יתרון, אבל נלחמתי הרבה על זה).

מסנניםהגירה מנאג'וס ל-Icinga2 באוסטרליה

הערך את היחס בין גודל פס הגלילה לגודל שדה הגלילה.

שנית, כולם רגילים לראות את כל התשתית על צג אחד, מכיוון ש-CheckMk מאפשר לך לעבוד עם מספר מארחים של Nagios, אבל ממשק Icinga לא הצליח לעשות זאת (למעשה, הוא יכול, אבל עוד על כך בהמשך). האלטרנטיבה הייתה משהו שנקרא Thruk, אבל העיצוב שלו גרם לכל אחד בצוות להסתבך חוץ מאחד - זה שהציע את זה (לא אני).

לתוך תנור Thruk - החלטה פה אחד של הצוותהגירה מנאג'וס ל-Icinga2 באוסטרליה

לאחר כמה ימים של סיעור מוחות, הצעתי את הרעיון של ניטור אשכולות, כאשר יש מארח מאסטר אחד באזור הייצור ושני עבדים - אחד בפיתוח/בדיקה ומארח חיצוני אחד שנמצא אצל ספק אחר כדי לפקח על שירותים מנקודת מבטו של לקוח או מתבונן מבחוץ. התצורה הזו אפשרה לראות את כל הבעיות בממשק אינטרנט אחד ועבדה די טוב, אבל Puppet... הבעיה עם Puppet הייתה שהמאסטר היה צריך לדעת על כל המארחים והשירותים/בדיקות במערכת והיה לו להפיץ אותם בין אזורים (dev-test, staging-prod, ext), אבל שליחת שינויים דרך Icinga API לוקחת כמה שניות, אבל הידור של ספריית Puppet של כל השירותים עבור כל המארחים לוקח כמה דקות. את זה עדיין מאשימים אותי, למרות שכבר הסברתי כמה פעמים איך הכל עובד ולמה כל זה לוקח כל כך הרבה זמן.

שלישית, יש חבורה של פתיתי שלג - דברים בולטים מהמערכת הכללית כי יש בהם משהו מיוחד, אז הכללים הכלליים לא חלים עליהם. זה נפתר על ידי התקפה חזיתית - אם יש אזעקות, אבל למעשה הכל בסדר, אז אני צריך לחפור עמוק יותר ולהבין למה זה מתריע, למרות שזה לא צריך. או להיפך - למה נגיוס נבהל, אבל איסינגה לא.

רביעית, נאגיוס עבד כאן לפני שלוש שנים ובהתחלה היה בו יותר אמון מאשר במערכת ההיפסטרית החדשה שלי, כך שבכל פעם שאיצ'ינגה עורר בהלה, אף אחד לא עשה כלום עד שנגיוס התלהב מאותו נושא. אבל לעתים רחוקות מאוד Icinga יצר אזעקות אמיתיות לפני Nagios ואני מחשיב זאת כבעיה רצינית, עליה אדבר בסעיף "המסקנות".

כתוצאה מכך, ההפעלה התעכבה יותר מ-5 חודשים (מתוכנן ל-28 ביוני 2018, למעשה - 3 בדצמבר 2018), בעיקר בגלל "בדיקת זוגיות" - הזבל הזה כשיש כמה שירותים בנגיוס שאף אחד לא מכיר בערך לא שמעתי שום דבר בשנתיים האחרונות, אבל כרגע הם לעזאזל נתנו קריט ללא סיבה והייתי צריך להסביר למה הם לא היו בפאנל שלי והייתי צריך להוסיף אותם לאיצ'ינגה כדי ש"בדיקת זוגיות היא להשלים" (כל השירותים/המחאות בנגיוס תואמים לשירותים/המחאות באיסינגה)

יישום:
הראשון הוא מלחמת הקוד נגד הנתונים, כמו Puppet Style. כל הנתונים, ממש הכל, חייבים להיות בהירה ולא שום דבר אחר. כל הקוד נמצא בקבצי .pp. משתנים, הפשטות, פונקציות - הכל נכנס בעמוד.
כתוצאה מכך, יש לנו חבורה של מכונות וירטואליות (165 בזמן כתיבת שורות אלו) ו-68 יישומי אינטרנט שיש לנטר את הביצועים ואת התוקף של תעודות SSL. אבל בגלל טחורים היסטוריים, מידע לניטור יישומים נלקח ממאגר גיטלב נפרד ופורמט הנתונים לא השתנה מאז Puppet 3, מה שיוצר מורכבות נוספת בתצורה.

קוד בובה ליישומים, הגן על העיניים שלך

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

גם קוד התצורה של מארחים ושירותים נראה נורא:

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

}

אני עדיין עובד על האטריות האלה ומשפר אותן כמיטב יכולתי. עם זאת, הקוד הזה הוא שאפשר לנו להשתמש בתחביר פשוט ומובן ב-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 - זה ממזג את כל הגדרות ברירת המחדל עם הגדרות הקבוצה ולאחר מכן עם כל בדיקה בודדת.

עוד שינוי חשוב מאוד היה היכולת להשתמש בפונקציות בהגדרות, למשל פונקציית החלפת הפורט, הכתובת וה-url לבדיקת 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

זה אומר - אם יש משתנה בהגדרת המארח http_port - השתמש בו, אחרת 443. לדוגמה, ממשק האינטרנט של jabber תלוי ב-9090, ו-Unifi תלוי ב-7443.
http_vhost פירושו להתעלם מ-DNS ולקחת את הכתובת הזו.
אם ה-uri מצוין במארח, עקוב אחריו, אחרת קח "/".

יצא סיפור מצחיק עם http_ssl - הזיהום הזה לא רצה להתנתק לפי דרישה. הייתי מבולבל לגבי השורה הזו הרבה זמן עד שהתברר לי שהמשתנה בהגדרת המארח הוא:

http_ssl: false

מוחלף לביטוי

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

איך שקר ובסוף מסתבר

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

כלומר, בדיקת ssl תמיד פעילה. פתרתי את זה על ידי החלפת התחביר:

http_ssl: no

ממצאים:

יתרונות:

  • כעת יש לנו מערכת ניטור אחת, ולא שתיים, כפי שהייתה לנו ב-7-8 החודשים האחרונים, או אחת מיושנת ופגיעה.
  • מבנה הנתונים של hosts/services(checks) הוא כעת (לדעתי) הרבה יותר קריא ומובן. עבור אחרים, זה התברר כל כך לא ברור, אז הייתי צריך לפרסם כמה עמודים בוויקי המקומי כדי להסביר איך הכל עובד ומה לערוך איפה.
  • אפשר להגדיר צ'קים בצורה גמישה באמצעות משתנים ופונקציות, למשל, לבדוק http_regexp, ניתן להגדיר את התבנית הרצויה, קוד החזרה, url ויציאה בהגדרות המארח.
  • ישנם מספר לוחות מחוונים, שלכל אחד מהם תוכלו להגדיר רשימה משלכם של אזעקות המוצגות ולנהל את כל זה באמצעות בקשות בובה ומיזוג.

חסרונות:

  • אינרציה של חברי צוות - נאגיוס עבד, עבד ועבד, והאיסינגה הזו שלך היא כל הזמן באגי ואיטי. איך אתה יכול לראות את ההיסטוריה כאן? אה, לעזאזל, זה לא מעודכן... (הבעיה האמיתית היא שהיסטוריית האזעקות לא מתעדכנת אוטומטית, רק על ידי F5)
  • האינרציה של המערכת - כאשר אני לוחץ על "עדכן" (בדוק עכשיו) בממשק האינטרנט - תוצאת הביצוע תלויה במזג האוויר במאדים, במיוחד בשירותים מורכבים שדורשים עשרות שניות לביצוע. תוצאה כזו היא נורמלית. הגירה מנאג'וס ל-Icinga2 באוסטרליה
  • באופן כללי, לפי הנתונים הסטטיסטיים של חצי שנה של פעולת שתי המערכות זו לצד זו, נאגיוס תמיד עבד מהר יותר מאיצ'ינגה וזה ממש עיצבן אותי. נראה לי שהם פישלו משהו בטיימרים והבדיקה מתבצעת כל חמש דקות, בעצם זה קורה כל 5 וחצי או משהו כזה.
  • אם תפעיל מחדש את השירות בכל עת (systemctl restart icinga2) - כל הבדיקות שהיו בעיצומן באותו זמן ייצרו אזעקה קריטית על המסך ומבחוץ זה נראה כאילו הכל נפל (באג אושר).

אבל בסך הכל, זה עובד.

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster