Hallo zusammen.
Ich bin Linux-Administrator und bin 2015 aus Russland nach Australien mit einem unabhängigen Fachkräftevisum gezogen, aber dieser Artikel handelt nicht davon, wie ein Hausschwein einen Traktor bekommt. Solche Artikel gibt es bereits genug (trotzdem, wenn Interesse besteht, schreibe ich auch darüber), also möchte ich erzählen, wie ich in meiner Arbeit als Linux-Operations-Ingenieur in Australien die Migration von einem Überwachungssystem zu einem anderen initiiert habe. Konkret von Nagios zu Icinga2.
Der Artikel ist teilweise technisch und teilweise handelt er von der Kommunikation mit Menschen und den Problemen, die sich aus kulturellen Unterschieden und unterschiedlichen Arbeitsmethoden ergeben.
Leider hebt das Tag „code“ den Puppet- und YAML-Code nicht hervor, also musste ich „plaintext“ verwenden.
Nichts deutete am Morgen des 21. Dezember 2016 auf Probleme hin. Wie gewohnt las ich in den ersten halben Stunde des Arbeitstags als nicht registrierter Anonymer auf Habr, während ich Kaffee konsumierte und stieß auf .
Da in meinem Unternehmen Nagios verwendet wurde, erstellte ich ohne lange zu überlegen ein Ticket in Redmine und teilte den Link im allgemeinen Chat, da ich es für wichtig hielt. Initiative wird selbst in Australien bestraft, also übertrug der leitende Ingenieur dieses Problem auf mich, da ich es entdeckt hatte.
Screenshot aus Redmine
In unserer Abteilung ist es üblich, vor der Äußerung einer Meinung mindestens eine Alternative vorzuschlagen, auch wenn die Wahl offensichtlich ist. Daher habe ich mit einer Google-Suche begonnen, um herauszufinden, welche Überwachungssysteme derzeit relevant sind. Bei meiner letzten Arbeitsstelle in Russland hatte ich mein eigenes, sehr primitives, aber dennoch funktionierendes und alle anfallenden Aufgaben erfüllendes System. Python, die Polytechnische Universität in St. Petersburg und die U-Bahn sind top. Nein, die U-Bahn ist schlecht. Das ist ein persönlicher Blickwinkel (11 Jahre Erfahrung) und wäre einen eigenen Artikel wert, aber nicht jetzt.
Ein wenig zu den Regeln für Änderungen an der Infrastrukturkonfiguration in meinem aktuellen Job. Wir verwenden Puppet, Gitlab und das Prinzip "Infrastructure as Code", also:
- Keine manuellen Änderungen über SSH durch die manuelle Bearbeitung von Dateien auf virtuellen Maschinen. In drei Jahren Arbeit habe ich dafür oft Ärger bekommen, das letzte Mal vor einer Woche, und ich denke nicht, dass es das letzte Mal war. Es ist doch wirklich so — eine Zeile in der Konfiguration ändern, den Dienst neu starten und sehen, ob das Problem behoben ist — 10 Sekunden. Einen neuen Branch in GitLab erstellen, Änderungen pushen, warten, bis r10k auf dem Puppetmaster arbeitet, Puppet mit —environment=mybranch starten und noch ein paar Minuten warten, bis das alles läuft — mindestens 5 Minuten.
- Alle Änderungen erfolgen durch die Erstellung eines Merge Requests in GitLab und benötigen die Genehmigung von mindestens einem Teammitglied. Wesentliche Änderungen, die vom Teamleiter beschlossen werden, erfordern zwei oder drei Genehmigungen.
- Alle Änderungen sind auf die eine oder andere Weise textbasiert (da die Puppet-Manskripte und Hiera-Daten Text sind), binäre Dateien sind äußerst unerwünscht, und es sind triftige Gründe erforderlich, um solche Dateien genehmigen zu lassen.
Also, die Optionen, die ich in Betracht gezogen habe:
- Munin — wenn in der Infrastruktur mehr als 10 Server sind, wird die Administration zur Qual (aus . Ich hatte nicht wirklich das Verlangen, das zu überprüfen, also glaubte ich es einfach mal.
- Zabbix — ich hatte es bereits in Russland ins Auge gefasst, aber damals war es für meine Aufgaben überdimensioniert. Hier musste ich es aufgrund der Verwendung von Puppet als Konfigurationsmanager und GitLab als Versionskontrollsystem beiseitelegen. Zu diesem Zeitpunkt, so viel ich verstanden habe, speichert Zabbix die gesamte Konfiguration in einer Datenbank, weshalb unklar war, wie man die Konfiguration unter den gegebenen Bedingungen verwalten und Änderungen verfolgen kann.
- Prometheus — das ist das Ziel, auf das wir letztendlich hinarbeiten, judging by the mood in the department, aber damals konnte ich es nicht meistern und konnte keinen funktionierenden Prototyp (Proof of Concept) präsentieren, weshalb ich darauf verzichten musste.
- Es gab noch einige andere Optionen, die entweder eine vollständige Überarbeitung des Systems erforderten oder sich in einem embryonalen Zustand befanden bzw. aufgegeben wurden und aus diesem Grund ausgeschlossen wurden.
Letztendlich entschied ich mich für Icinga2 aus drei Gründen:
1 — Kompatibilität mit Nrpe (dem Client-Dienst, der Prüfungen basierend auf Befehlen von Nagios ausführt). Das war sehr wichtig, da wir zu diesem Zeitpunkt 135 (jetzt 165 im Jahr 2019) virtuelle Maschinen mit vielen selbstgeschriebenen Diensten/Prüfungen hatten und es ein riesiges Problem gewesen wäre, alles neu zu gestalten.
2 — Alle Konfigurationsdateien sind im Textformat, was eine einfache Bearbeitung ermöglicht und das Erstellen von Merge-Requests erleichtert, mit der Möglichkeit, hinzugefügte oder entfernte Elemente zu sehen.
3 — Es handelt sich um ein aktives und sich weiterentwickelndes OpenSource-Projekt. Wir schätzen OpenSource sehr und tragen unseren Teil dazu bei, indem wir Pull Requests und Issues zur Problemlösung einreichen.
Also, los geht's, Icinga2.
Die erste Herausforderung war die Trägheit der Kollegen. Alle waren an Nagios/Nadjius gewöhnt (obwohl wir uns hier nicht einmal auf die Aussprache einigen konnten) und an die Schnittstelle von CheckMK. Bei Icinga sieht die Schnittstelle ganz anders aus (das war ein Nachteil), aber es gibt die Möglichkeit, flexibel zu konfigurieren, was man durch Filter nach beliebigen Parametern sehen möchte (das war ein Vorteil, aber ich habe dafür hart gekämpft).
Filter
Bewerten Sie das Verhältnis der Größe der Scrollleiste zur Größe des Scrollfeldes.
Der zweite Punkt ist, dass alle es gewohnt sind, die gesamte Infrastruktur auf einem Monitor zu sehen, da CheckMk mit mehreren Nagios-Hosts arbeiten kann. Das Icinga-Interface hingegen konnte das nicht (eigentlich konnte es das, aber dazu später mehr). Eine Alternative war ein Tool namens Thruk, dessen Design bei allen Teammitgliedern, außer einem – dem, der es vorgeschlagen hat (nicht ich) – Erbrechensgefühle hervorrief.
Thruk kann weg – einstimmige Entscheidung des Teams.
Nach ein paar Tagen Brainstorming schlug ich eine Idee für das Cluster-Monitoring vor, bei dem es einen Master-Host in der Produktionszone und zwei Untergeordnete gibt – einen in dev/test und einen externen Host, der bei einem anderen Anbieter gehostet wird, um unsere Dienste aus der Sicht des Kunden oder eines externen Beobachters zu überwachen. Diese Konfiguration ermöglichte es, alle Probleme in einem einzigen Web-Interface zu sehen und funktionierte ziemlich gut, aber bei Puppet… Das Problem bei Puppet war, dass der Master-Host nun über alle Hosts und Dienste/Überprüfungen im System informiert sein musste und diese zwischen den Zonen (dev-test, staging-prod, ext) verteilen sollte. Das Senden von Änderungen über die Icinga API dauert nur ein paar Sekunden, während die Kompilierung des Puppet-Verzeichnisses aller Dienste für alle Hosts ein paar Minuten in Anspruch nimmt. Dies wird mir bis heute vorgeworfen, obwohl ich bereits mehrmals erklärt habe, wie alles funktioniert und warum das alles so lange dauert.
Drittens gibt es eine Menge SnowFlakes – Dinge, die aus dem System herausfallen, weil sie etwas Besonderes haben, weshalb die allgemeinen Regeln nicht auf sie zutreffen. Es wurde durch eine Frontalsuche gelöst – wenn es Alarme gibt, aber eigentlich alles in Ordnung ist, muss ich tiefer graben und herausfinden, warum es bei mir Alarm schlägt, obwohl es das nicht sollte. Oder umgekehrt – warum Nagios in Panik gerät, während Icinga das nicht tut.
Viertens – Nagios hat hier drei Jahre vor mir gearbeitet und hatte von Anfang an mehr Vertrauen als mein modernes, hipsterhaftes System, sodass jedes Mal, wenn Icinga Alarm schlug, niemand etwas unternahm, bis Nagios bei demselben Problem aktiv wurde. Aber sehr selten gab Icinga echte Alarme aus, bevor Nagios das tat, und ich betrachte das als einen ernsthaften Fehler, über den ich im Abschnitt „Fazit“ berichten werde.
Letztendlich hat sich die Inbetriebnahme um mehr als 5 Monate verzögert (ursprünglich für den 28. Juni 2018 geplant, tatsächlich am 3. Dezember 2018), hauptsächlich wegen des „Parity Checks“ – dieser dämlichen Angelegenheit, bei der es einige Dienste in Nagios gibt, von denen in den letzten paar Jahren niemand etwas gehört hat, aber GENAU JETZT haben sie, verdammtes nochmal, crit ohne irgendeinen Grund ausgegeben, und ich musste erklären, warum sie nicht in meinem Panel sind, und ich musste sie in Icinga hinzufügen, um zu zeigen, dass „parity check ist abgeschlossen“ (Alle Dienste/Überprüfungen in Nagios entsprechen den Diensten/Überprüfungen in Icinga).
Implementierung:
Erstens – der Krieg zwischen Code und Daten, quasi im Puppet-Stil. Alle Daten, wirklich alle, müssen in Hiera sein und sonst nirgendwo. Der gesamte Code befindet sich in .pp-Dateien. Variablen, Abstraktionen, Funktionen – alles geht in pp.
Insgesamt haben wir eine Menge virtueller Maschinen (165 zum Zeitpunkt des Schreibens) und 68 Webanwendungen, die auf Funktionsfähigkeit und die Gültigkeit der SSL-Zertifikate überwacht werden müssen. Aufgrund historischer Schwierigkeiten stammt die Information zur Überwachung der Anwendungen aus einem separaten GitLab-Repository, und das Datenformat hat sich seit Puppet 3 nicht geändert, was zusätzliche Komplikationen bei der Konfiguration verursacht.
Puppet-Code für Anwendungen, schont eure Augen.
definiere profile::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'], {} )) } # fügt Benachrichtigungen für die Standardgruppe (Systeme) + jede im int/pm_docker_apps.eyaml definierte Gruppe hinzu.
$data = merge($webhost_defaults, $apps_accessible_from, $app_data)
$site_domain = $app_data['site_domain']
$regexp = pick($app_data['check_regex'], 'html') # Wählt eine Regex zum Prüfen aus
$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 | { # Teilt eine App nach Domains auf, wenn es zwei oder mehr gibt
$vhost_name = {'http_vhost' => $vhost}
$vars = $data['vars'] + $vhost_name + $check_regex + $check_url
$web_ipaddress = is_array($vdata['web_ipaddress']) ? { # Macht die IP-Adresse zu einem Array, wenn sie es nicht ist, da askizzy 2 IPs hat und es ein Array ist
true => $vdata['web_ipaddress'],
false => [$vdata['web_ipaddress']],
}
$access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Merges Standardzone (wo die App definiert ist) und zusätzliche Zonen, falls vorhanden.
$web_ipaddress.each | String $ip_address | { # Für jede IP (wenn wir mehrere haben)
$suffix = length($web_ipaddress) ? { # Wenn wir mehr als eine haben - fügen Sie der Hostname-IP dieses Suffix hinzu, um Ressourcenduplikate zu vermeiden.
1 => '',
default => "_${ip_address}"
}
$octets = split($ip_address, '.')
$ip_tag = "${octets[2]}.${octets[3]}" # Die Verwendung des letzten Oktets führt zu Kollisionen zwischen nginx-vip 203.15.70.94 und 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}" # Wenn es ein Host für ext ist - wird das Präfix '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
}
}
}
}
}
}
}
}Der Konfigurationscode für Hosts und Services sieht ebenfalls furchtbar aus:
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',
}
}Ich arbeite weiterhin an diesem Code und verbessere ihn, wo immer es möglich ist. Doch genau dieser Code ermöglichte es, eine einfache und verständliche Syntax in Hiera zu verwenden:
Daten
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'Alle Prüfungen sind in Gruppen unterteilt, jede Gruppe hat Standardkonfigurationen, wo und wie oft diese Prüfungen durchgeführt werden, welche Benachrichtigungen gesendet werden und an wen.
In jeder Überprüfung können Sie jede Option überschreiben, und all dies kombiniert sich letztendlich auch mit den Standardeinstellungen aller Prüfungen insgesamt. Daher wurde in config.pp eine solche Logik geschrieben – dort findet die Verschmelzung aller Standardeinstellungen mit den Gruppeneinstellungen und anschließend mit jeder einzelnen Prüfung statt.
Eine weitere wichtige Änderung ist die Möglichkeit, Funktionen in den Einstellungen zu verwenden, zum Beispiel eine Funktion zum Ersetzen von Port, Adresse und URL für die http_regex-Prüfung.
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: trueDas bedeutet – wenn in der Hostdefinition eine Variable vorhanden ist http_port – verwenden Sie sie, andernfalls 443. Zum Beispiel hängt die Web-Oberfläche von Jabber an 9090, während Unifi an 7443 hängt.
http_vhost bedeutet, DNS zu ignorieren und diese Adresse zu verwenden.
Wenn im Host ein URI angegeben ist, dann folge diesem, andernfalls nimm „/“.
Eine amüsante Geschichte mit http_ssl — dieses Biest wollte sich einfach nicht auf Aufforderung abschalten. Ich habe lange über diese Zeile nachgedacht, bis mir klar wurde, dass in der Host-Definition eine Variable steht:
http_ssl: falsewird in den Ausdruck eingesetzt
if(host.vars.http_ssl) { return false } else { return true }als false und am Ende ergibt sich
if(false) { return false } else { return true }das heißt, die SSL-Prüfung ist immer aktiv. Gelöst wurde das durch einen Syntaxwechsel:
http_ssl: noFazit:
Vorteile:
- Wir haben jetzt ein Monitoring-System, nicht zwei, wie es in den letzten 7-8 Monaten war, oder eines, das veraltet und verwundbar war.
- Die Datenstruktur der Hosts/Services (Prüfungen) ist jetzt (meiner Meinung nach) viel lesbarer und verständlicher. Für andere schien das nicht so offensichtlich zu sein, also musste ich ein paar Seiten in der lokalen Wiki erstellen, um zu erklären, wie alles funktioniert und was wo bearbeitet werden muss.
- Es gibt die Möglichkeit, die Prüfungen flexibel mit Variablen und Funktionen anzupassen, beispielsweise für die http_regexp-Prüfung können das gesuchte Muster, Rückgabecode, URL und Port in den Host-Einstellungen festgelegt werden.
- Es gibt mehrere Panels (Dashboards), für die man eine eigene Liste von angezeigten Alarmen festlegen und alles über Puppet und Merge-Requests verwalten kann.
Nachteile:
- Inertheit der Teammitglieder – Nagios hat immer zuverlässig gearbeitet, während dein Icinga ständig Probleme hatte und zu spät reagiert. Wie kann ich die Historie einsehen? Ups, sie aktualisiert sich ja nicht... (Echt problematisch – die Alarmhistorie aktualisiert sich nicht automatisch, nur durch F5)
- Inertheit des Systems – wenn ich im Web-Interface auf 'Jetzt überprüfen' klicke, hängt das Ergebnis von den Gezeiten auf dem Mars ab, insbesondere bei komplexen Diensten, die viele Sekunden in Anspruch nehmen. Ein solches Ergebnis ist normal.

- Insgesamt hat Nagios in der sechsmonatigen Statistikanalyse der beiden Systeme, die nebeneinander liefen, immer schneller reagiert als Icinga, was mich sehr gestört hat. Ich habe das Gefühl, dass bei den Timern etwas schiefgelaufen ist und die Prüfung alle fünf Minuten im Grunde alle fünf Minuten und dreißig Sekunden erfolgt oder so etwas in der Art.
- Wenn der Dienst zu irgendeinem Zeitpunkt neu gestartet wird (systemctl restart icinga2), zeigen alle Prüfungen, die zu diesem Zeitpunkt ausgeführt werden, einen kritischen Alarm <terminated by signal 15> an, was den Eindruck erweckt, als ob alles ausgefallen ist.).
Aber insgesamt funktioniert es.
Quelle: habr.com

