Cześć wszystkim
Jestem administratorem systemu Linux, przeprowadziłem się z Rosji do Australii na niezależnej wizie zawodowej w 2015 roku, ale artykuł nie będzie dotyczył uruchomienia traktora dla świni. Takich artykułów jest już wystarczająco dużo (mimo wszystko, jeśli będzie zainteresowanie, to też o tym napiszę), więc chciałbym opowiedzieć o tym, jak podczas mojej pracy w Australii jako inżynier linux-ops zainicjowałem migrację z jednego systemu monitorującego do innego. Konkretnie - Nagios => Icinga2.
Artykuł ma charakter po części techniczny, po części o komunikacji z ludźmi i problemach związanych z różnicami kulturowymi i metodami pracy.
Niestety tag „code” nie podświetla kodu Puppet i YAML, więc musiałem użyć „zwykłego tekstu”.
Rankiem 21 grudnia 2016 r. nie było żadnych oznak problemów. Jak zwykle czytałem Habr przez niezarejestrowanego anonimowego użytkownika w pierwszej połowie dnia roboczego, popijając kawę i natknąłem się na .
Ponieważ moja firma korzystała z Nagios, bez zastanowienia utworzyłem zgłoszenie w Redmine i wysłałem link do czatu ogólnego, ponieważ uznałem to za ważne. Inicjatywa jest karalna nawet w Australii, więc główny inżynier zrzucił problem na mnie, odkąd go odkryłem.
Zrzut ekranu z Redmine
W naszym dziale, zanim wyrazisz swoją opinię, zwyczajem jest proponowanie przynajmniej jednej alternatywy, nawet jeśli wybór jest oczywisty, więc zacząłem od wygooglowania, jakie systemy monitorowania są obecnie istotne, ponieważ w Rosji w moim ostatnim miejscu pracy miałem swój własny, napisany przeze mnie system, bardzo prymitywny, ale mimo to całkiem działający i wykonujący wszystkie przypisane mu zadania. Python, Politechnika w Petersburgu i zasada metra. Nie, metro jest do niczego. To sprawa osobista (11 lat pracy) i godna osobnego artykułu, ale nie teraz.
Trochę o zasadach dokonywania zmian w konfiguracji infrastruktury w moim obecnym miejscu. Używamy Puppet, Gitlab i Infrastructure jako zasady Kodeksu, więc:
- Żadnych ręcznych zmian przez SSH poprzez ręczną zmianę plików na maszynach wirtualnych. Przez trzy lata pracy zostałem za to uderzony kapeluszem wiele razy, ostatni raz tydzień temu i myślę, że to nie był ostatni raz. No właśnie - popraw jedną linijkę w konfiguracji, zrestartuj usługę i zobacz czy problem został rozwiązany - 10 sekund. Utwórz nową gałąź w Gitlabie, wypchnij zmiany, poczekaj, aż r10k zacznie działać na Puppetmaster, uruchom Puppet -environment=mybranch i poczekaj jeszcze kilka minut, aż wszystko zadziała - minimum 5 minut.
- Wszelkie zmiany wprowadzane są poprzez utworzenie żądania połączenia w Gitlabie i muszą zostać zatwierdzone przez co najmniej jednego członka zespołu. Poważniejsze zmiany, o których decyduje kierownik zespołu, wymagają dwóch lub trzech zatwierdzeń.
- Wszystkie zmiany są w ten czy inny sposób tekstowe (ponieważ manifesty Puppet, skrypty i dane Hiera są tekstem), pliki binarne są zdecydowanie odradzane i musi istnieć ważny powód, aby zatwierdzić takie pliki.
Zatem opcje jakie brałem pod uwagę:
- Munin - jeśli w infrastrukturze jest więcej niż 10 serwerów, administracja zamienia się w piekło (od . Nie miałem szczególnej ochoty tego sprawdzać, więc uwierzyłem mu na słowo).
- Zabbix - Długo się nad tym zastanawiałem, jeszcze w Rosji, ale wtedy okazało się to zbędne do moich zadań. Tutaj - musiał zostać odrzucony ze względu na użycie Puppet jako menedżera konfiguracji i Gitlab jako systemu kontroli wersji. W tamtym czasie, o ile zrozumiałem, Zabbix przechowuje całą konfigurację w bazie danych i dlatego nie było jasne, jak zarządzać konfiguracją w bieżących warunkach i jak śledzić zmiany.
- Prometeusz to jest to, do czego w końcu dojdziemy, sądząc po nastrojach w dziale, ale wtedy nie opanowałem tego i nie byłem w stanie zademonstrować naprawdę działającej próbki (Proof of Concept), więc musiałem odmówić.
- Było też kilka innych opcji, które albo wymagały całkowitej przeróbki systemu, albo były w powijakach/porzucone i zostały odrzucone z tego samego powodu.
Ostatecznie zdecydowałem się na Icinga2 z trzech powodów:
1 - kompatybilność z Nrpe (usługą kliencką, która uruchamia sprawdzanie poleceń z Nagios). Było to bardzo ważne, ponieważ w tamtym czasie mieliśmy 135 (obecnie w 2019 r. jest ich 165) maszyn wirtualnych z mnóstwem niestandardowych usług/testów i ponowne wykonanie tego wszystkiego byłoby naprawdę uciążliwe.
2 - wszystkie pliki konfiguracyjne są tekstowe, co ułatwia edycję tej sprawy, tworzenie żądań scalania z możliwością zobaczenia, co zostało dodane lub usunięte.
3 to żywy i rozwijający się projekt OpenSource. Bardzo kochamy OpenSource i wkładamy w niego każdy możliwy wkład, tworząc żądania ściągnięcia i problemy w celu rozwiązania problemów.
No to chodźmy, Icinga2.
Pierwszą rzeczą, z którą musiałem się zmierzyć, była bezwładność moich kolegów. Wszyscy są przyzwyczajeni do Nagios/Naggios (choć nawet tutaj nie udało im się dojść do kompromisu w kwestii wymowy) i interfejsu CheckMK. Interfejs icinga wygląda zupełnie inaczej (to był minus), ale można elastycznie dostosować to, co trzeba zobaczyć, używając filtrów dla dosłownie dowolnego parametru (to był plus, ale bardzo się o to walczyłem).
Filtry
Oszacuj stosunek rozmiaru paska przewijania do rozmiaru pola przewijania.
Po drugie, wszyscy są przyzwyczajeni do oglądania całej infrastruktury na jednym monitorze, ponieważ CheckMk pozwala na współpracę z kilkoma hostami Nagios, ale interfejs Icinga nie potrafił tego zrobić (właściwie mógłby, ale o tym poniżej). Alternatywą było coś o nazwie Thruk, ale jego projekt sprawił, że wszyscy w zespole zwymiotowali z wyjątkiem jednego – tego, który to zasugerował (nie mnie).
Do pieca Thruk – jednomyślna decyzja zespołu
Po kilku dniach burzy mózgów zaproponowałem pomysł monitorowania klastra, gdy w strefie produkcyjnej znajduje się jeden host master i dwa slave – jeden w fazie dev/test i jeden host zewnętrzny zlokalizowany u innego dostawcy w celu monitorowania naszego usług z punktu widzenia klienta lub zewnętrznego obserwatora. Ta konfiguracja umożliwiła zobaczenie wszystkich problemów w jednym interfejsie internetowym i działała całkiem dobrze, ale Puppet... Problem z Puppetem polegał na tym, że główny host musiał teraz wiedzieć o wszystkich hostach i usługach/kontrolach w systemie i miał aby rozdzielić je pomiędzy strefami (dev-test, staging-prod, ext), ale wysłanie zmian poprzez Icinga API zajmuje kilka sekund, natomiast kompilacja katalogu Puppet wszystkich usług dla wszystkich hostów zajmuje kilka minut. Wciąż zrzuca się na mnie tę winę, choć już kilka razy tłumaczyłem jak to wszystko działa i dlaczego to wszystko trwa tak długo.
Po trzecie, jest cała masa Płatków Śniegu - rzeczy, które wyróżniają się na tle ogólnego systemu, ponieważ mają w sobie coś wyjątkowego, więc ogólne zasady ich nie dotyczą. Rozwiązano to poprzez atak frontalny - jeśli są alarmy, ale faktycznie wszystko jest w porządku, to muszę pogrzebać głębiej i dowiedzieć się, dlaczego mnie ostrzega, chociaż nie powinno. Lub odwrotnie – dlaczego Nagios panikuje, a Icinga nie.
Po czwarte, Nagios pracował tu przede mną przez trzy lata i początkowo pokładano w nim większe zaufanie niż w moim nowomodnym systemie hipsterów, więc za każdym razem, gdy Icinga wzbudzał panikę, nikt nic nie robił, dopóki Nagios nie był podekscytowany tą samą sprawą. Jednak bardzo rzadko Icinga generował prawdziwe alarmy wcześniej niż Nagios i uważam to za poważny problem, o którym napiszę w części „Wnioski”.
W rezultacie uruchomienie zostało opóźnione o ponad 5 miesięcy (planowane na 28 czerwca 2018 r., a właściwie na 3 grudnia 2018 r.), głównie z powodu „kontroli parzystości” - tego badziewia, gdy w Nagios jest kilka usług, o których nikt nie wie o niczym nie słyszałem przez ostatnie kilka lat, ale TERAZ, kurwa, krytykowali bez powodu i musiałem wyjaśnić, dlaczego nie ma ich na moim panelu, i musiałem dodać ich do Icinga, żeby „kontrola parzystości była ukończone” (Wszystkie usługi/kontrole w Nagios odpowiadają usługom/kontrolom w Icinga)
Realizacja:
Pierwsza to wojna Kod kontra Dane, taka jak Puppet Style. Wszystkie dane, absolutnie wszystko, muszą być w Hierze i nic więcej. Cały kod znajduje się w plikach .pp. Zmienne, abstrakcje, funkcje - wszystko mieści się w s.
W rezultacie mamy kilka maszyn wirtualnych (165 w momencie pisania tego tekstu) i 68 aplikacji internetowych, które należy monitorować pod kątem wydajności i ważności certyfikatów SSL. Jednak ze względu na historyczne hemoroidy informacje dotyczące aplikacji monitorujących pobierane są z osobnego repozytorium gitlab, a format danych nie zmienił się od wersji Puppet 3, co powoduje dodatkową złożoność konfiguracji.
Kod marionetkowy dla aplikacji, chroń swoje oczy
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
}
}
}
}
}
}
}
}Kod konfiguracyjny hostów i usług również wygląda okropnie:
monitorowanie/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',
}
}Wciąż pracuję nad tymi makaronami i udoskonalam je najlepiej jak potrafię. Jednak to właśnie ten kod pozwolił nam zastosować w Hierze prostą i zrozumiałą składnię:
Te
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'Wszystkie kontrole podzielone są na grupy, każda grupa ma ustawienia domyślne, takie jak gdzie i jak często przeprowadzać te kontrole, jakie powiadomienia wysyłać i do kogo.
W każdym sprawdzeniu możesz zastąpić dowolną opcję, a wszystko to ostatecznie składa się na domyślne ustawienia wszystkich kontroli jako całości. Dlatego w config.pp zapisano takie bzdury - łączy wszystkie ustawienia domyślne z ustawieniami grupowymi, a następnie z każdym indywidualnym sprawdzeniem.
Kolejną bardzo ważną zmianą była możliwość skorzystania z funkcji w ustawieniach, np. funkcji zamiany portu, adresu i url w celu sprawdzenia 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: trueOznacza to - jeśli w definicji hosta znajduje się zmienna http_port — użyj go, w przeciwnym razie 443. Na przykład interfejs sieciowy Jabber zawiesza się na 9090, a Unifi zawiesza się na 7443.
http_vhost oznacza zignoruj DNS i weź ten adres.
Jeśli w hoście określono uri, postępuj zgodnie z nim, w przeciwnym razie użyj „/”.
Zabawna historia wyszła z http_ssl - ta infekcja nie chciała się rozłączyć na żądanie. Długo zastanawiałem się nad tą linijką, aż dotarło do mnie, że zmienna w definicji hosta to:
http_ssl: falseZastąpione wyrażeniem
if(host.vars.http_ssl) { return false } else { return true }jak fałszywy i w końcu się okazuje
if(false) { return false } else { return true }oznacza to, że kontrola SSL jest zawsze aktywna. Rozwiązałem to, zastępując składnię:
http_ssl: noodkrycia:
Plusy:
- Mamy teraz jeden system monitorowania, a nie dwa, jak mieliśmy przez ostatnie 7–8 miesięcy, albo taki, który jest przestarzały i podatny na ataki.
- Struktura danych hostów/usług (kontroli) jest teraz (moim zdaniem) znacznie bardziej czytelna i zrozumiała. Dla innych okazało się to nie takie oczywiste, więc musiałem zamieścić kilka stron na lokalnej wiki, aby wyjaśnić, jak to wszystko działa i co i gdzie edytować.
- Możliwe jest elastyczne konfigurowanie kontroli za pomocą zmiennych i funkcji, na przykład sprawdzanie http_regexp, żądany wzorzec, kod powrotu, adres URL i port można ustawić w ustawieniach hosta.
- Dostępnych jest kilka dashboardów, dla każdego z nich można zdefiniować własną listę wyświetlanych alarmów i zarządzać tym wszystkim poprzez żądania Puppet i scalania.
Wady:
- Bezwładność członków zespołu - Nagios pracował, pracował i pracował, a ta Twoja Isinga jest ciągle pełna błędów i powolna. Jak tu zobaczyć historię? O kurczę, nie jest aktualizowana... (Prawdziwym problemem jest to, że historia alarmów nie jest aktualizowana automatycznie, tylko przez F5)
- Bezwładność systemu - kiedy klikam „aktualizuj” (sprawdź teraz) w interfejsie internetowym – wynik wykonania zależy od pogody na Marsie, szczególnie w przypadku skomplikowanych usług, których wykonanie wymaga kilkudziesięciu sekund. Taki wynik jest normalny.

- Ogólnie rzecz biorąc, według półrocznych statystyk pracy obu systemów obok siebie, Nagios zawsze działał szybciej niż Icinga i to mnie bardzo irytowało. Wydaje mi się, że coś pomieszali z timerami i sprawdzanie odbywa się co pięć minut, a faktycznie dzieje się to co 5:30 czy jakoś tak.
- Jeśli w dowolnym momencie zrestartujesz usługę (systemctl restart icinga2) - wszystkie kontrole, które były w tym momencie w toku, wygenerują alarm krytyczny na ekranie i z zewnątrz wygląda tak, jakby wszystko upadło ().
Ale ogólnie to działa.
Źródło: www.habr.com

