Witajcie wszyscy.
Jestem administratorem linux, przeprowadziłem się z Rosji do Australii na niezależną wizę zawodową w 2015 roku, ale artykuł nie będzie o tym, jak świnek hodować traktor. Takich artykułów już jest wystarczająco dużo (jednakże, jeśli będzie zainteresowanie — napiszę i o tym), więc chciałbym opowiedzieć o tym, jak w mojej pracy w Australii na stanowisku inżyniera linux-ops byłem inicjatorem migracji z jednego systemu monitorowania na inny. Konkretnie — Nagios => Icinga2.
Artykuł jest częściowo techniczny, a częściowo dotyczy interakcji z ludźmi i problemów związanych z różnicami kulturowymi oraz metodami pracy.
Niestety, tag «code» nie podświetla kodu Puppet i yaml, więc musiałem użyć «plaintext».
Nic nie zapowiadało kłopotów rankiem 21 grudnia 2016 roku. Jak zwykle, czytałem Habr jako niezarejestrowany anonim, przez pierwsze pół godziny pracy, pijąc kawę i natknąłem się na .
Ponieważ w mojej firmie używano Nagios, nie zastanawiając się długo, stworzyłem zgłoszenie w Redmine i przesłałem link na wspólny czat, ponieważ uznałem to za ważne. Inicjatywa jest karana nawet w Australii, więc główny inżynier przyczepił ten problem do mnie, skoro go zauważyłem.
Zrzut ekranu z Redmine
W naszym dziale przed przedstawieniem swojego zdania należy zaproponować przynajmniej jedną alternatywę, nawet jeśli wybór jest oczywisty, więc zacząłem od googlowania, jakie systemy monitorowania są obecnie aktualne, ponieważ w mojej ostatniej pracy w Rosji miałem własny system napisany ręcznie, bardzo prymitywny, ale wciąż działający i spełniający wszystkie nałożone na niego zadania. Python, Politechnika Petersburska i metro rządzą. Nie, metro — to porażka. To osobista sprawa (11 lat pracy) i zasługuje na osobny artykuł, ale nie teraz.
Trochę o zasadach wprowadzania zmian w konfiguracji infrastruktury w moim obecnym miejscu pracy. Używamy Puppet, Gitlab i zasady Infrastructure as Code, więc:
- Żadne ręczne zmiany przez SSH poprzez ręczną edycję jakichkolwiek plików na maszynach wirtualnych. Przez trzy lata pracy dostałem za to po głowie wiele razy, ostatnio tydzień temu i nie sądzę, że to był ostatni raz. No naprawdę — poprawić jedną linię w konfiguracji, zrestartować usługę i sprawdzić, czy problem został rozwiązany — 10 sekund. Utworzyć nową gałąź w Gitlabie, wypchnąć zmiany, poczekać, aż r10k zadziała na Puppetmasterze, uruchomić Puppet —environment=mybranch i jeszcze poczekać kilka minut, aż to wszystko się wykona — co najmniej 5 minut.
- Wszelkie zmiany są dokonywane poprzez utworzenie Merge Request w Gitlabie i konieczne jest uzyskanie zgody przynajmniej od jednego członka zespołu. Poważne zmiany wymagają dwóch lub trzech zgód od lidera zespołu.
- Wszystkie zmiany są w jakiś sposób tekstowe (ponieważ manifesty Puppet, skrypty i dane Hiera to tekst), pliki binarne są zdecydowanie niezalecane i do zatwierdzenia takich plików potrzebne są uzasadnione powody.
A więc, możliwości, które rozważyłem:
- Munin — jeśli w infrastrukturze jest więcej niż 10 serwerów, administracja zamienia się w piekło (z . Nie miałem szczególnej ochoty tego sprawdzać, więc uwierzyłem na słowo).
- Zabbix — od dłuższego czasu się przyglądałem, jeszcze w Rosji, ale wtedy był zbyteczny dla moich zadań. Tutaj — musiałem zrezygnować z powodu używania Puppet jako menedżera konfiguracji i Gitlab jako systemu kontroli wersji. W tamtym czasie, jak zrozumiałem — Zabbix przechowuje całą konfigurację w bazie danych, przez co było niejasne, jak zarządzać konfiguracją w obecnych warunkach i jak śledzić zmiany.
- Prometheus — to, do czego w końcu dojdziemy, sądząc po nastrojach w dziale, ale w tamtym czasie nie dałem rady tego ogarnąć i nie mogłem zaprezentować rzeczywistego działającego wzoru (Proof of Concept), więc musiałem zrezygnować.
- Było jeszcze kilka innych możliwości, które wymagałyby pełnej przebudowy systemu lub były w początkowej fazie / porzucone i z tej samej przyczyny zostały odrzucone.
Ostatecznie zdecydowałem się na Icinga2 z trzech powodów:
1 — zgodność z Nrpe (klientem, który uruchamia kontrole na podstawie poleceń z Nagios). To było bardzo ważne, ponieważ w tym czasie mieliśmy 135 (obecnie w 2019 roku jest ich 165) maszyn wirtualnych z mnóstwem niestandardowych serwisów/sprawdzianów, a przerabianie tego wszystkiego byłoby ogromnym problemem.
2 — wszystkie pliki konfiguracyjne są tekstowe, co pozwala łatwo je edytować, tworzyć merge requests z możliwością zobaczenia, co zostało dodane lub usunięte.
3 — to żywy i rozwijający się projekt OpenSource. Bardzo lubimy OpenSource i dajemy z siebie wszystko, tworząc Pull Requests i Issues w celu rozwiązania problemów.
A więc, zaczynamy, Icinga2.
Pierwszą rzeczą, z którą musiałem się zmierzyć, była oporność kolegów. Wszyscy przyzwyczaili się do Nagiosa/Nadgjosa (nawet tutaj nie mogliśmy dojść do kompromisu, jak to wymawiać) i interfejsu CheckMK. W przypadku Icinga interfejs wygląda zupełnie inaczej (to był minus), ale istnieje możliwość elastycznego dostosowywania widoków za pomocą filtrów praktycznie na każdą kategorię (to był plus, ale musiałem za to ostro walczyć).
Filtry
Oceń stosunek rozmiaru paska przewijania do rozmiaru obszaru przewijania.
Po drugie — wszyscy przyzwyczaili się widzieć całą infrastrukturę na jednym monitorze, ponieważ CheckMk pozwalał pracować z wieloma hostami Nagios, ale interfejs Icinga tego nie potrafił (w rzeczywistości potrafił, ale więcej o tym później). Alternatywą była rzecz zwana Thruk, ale jej projekt wywoływał odruch wymiotny u wszystkich członków zespołu, z wyjątkiem jednego — tego, który to zaproponował (nie ja).
Do kosza z Thruk — jednogłośna decyzja zespołu.
Po kilku dniach burzy mózgów zaproponowałem pomysł monitorowania klastra, gdzie jest jeden główny host w strefie produkcyjnej i dwóch podrzędnych — jeden w dev/test, a drugi zewnętrzny host u innego dostawcy, mający na celu monitorowanie naszych usług z punktu widzenia klienta lub zewnętrznego obserwatora. Taka konfiguracja pozwalała widzieć wszystkie problemy w jednym interfejsie webowym i całkiem dobrze działała, ale Puppet... Problem z Puppet polegał na tym, że główny host musiał teraz znać wszystkie hosty oraz usługi/sprawdzenia w systemie i musiał rozdzielać je pomiędzy strefami (dev-test, staging-prod, ext), ale przesyłanie zmian przez Icinga API zajmowało kilka sekund, podczas gdy kompilacja katalogu Puppet wszystkich usług dla wszystkich hostów — kilka minut. To do tej pory jest mi wypominane, chociaż już kilka razy wyjaśniałem, jak to wszystko działa i dlaczego to trwa tak długo.
Po trzecie — masa SnowFlakes (płatków śniegu) — rzeczy, które wybiegają poza ogólny system, ponieważ mają w sobie coś szczególnego, dlatego ogólne zasady do nich nie mają zastosowania. Rozwiązywano to poprzez atak frontalny — jeśli są alarmy, ale w rzeczywistości wszystko jest w porządku, to trzeba drążyć i zrozumieć, dlaczego wyświetla alarmy, chociaż nie powinno. Albo odwrotnie — dlaczego Nagios panikuje, a Icinga — nie.
Po czwarte — Nagios działał tutaj przede mną przez trzy lata i miał początkowo większe zaufanie niż mój nowoczesny hipsterski system, więc za każdym razem, gdy Icinga podnosiła panikę — nikt nic nie robił, dopóki Nagios nie wybudzał się w tej samej sprawie. Ale bardzo rzadko Icinga wykazywała rzeczywiste alarmy przed Nagios i uważam to za poważny błąd, o którym opowiem w sekcji 'Wnioski'.
W rezultacie wdrożenie przedłużyło się o ponad 5 miesięcy (planowano 28 czerwca 2018, faktycznie — 3 grudnia 2018), głównie z powodu 'parity check' — tej głupoty, kiedy jest kilka usług w Nagios, o których nikt nie słyszał przez ostatnie kilka lat, ale WŁAŚNIE TERAZ wydają crit bez jakiejkolwiek przyczyny i musiałem wyjaśniać, dlaczego ich nie ma na moim panelu i musiałem dodać je do Icinga, aby 'sprawdzenie parytetu zostało zakończone' (wszystkie usługi/sprawdzenia w Nagios odpowiadają usługom/sprawdzeniom w Icinga).
Wdrożenie:
Pierwsze - wojna Code vs Data, typ Puppet Style. Wszystkie dane, dosłownie wszystkie, muszą być w Hiera i inaczej być nie może. Cały kod - w plikach .pp. Zmienne, abstrakcje, funkcje - wszystko znajduje się w pp.
W efekcie - mamy mnóstwo maszyn wirtualnych (165 w momencie pisania artykułu) i 68 aplikacji webowych, które należy monitorować pod kątem działania i ważności certyfikatów SSL. Jednak z powodu historycznych komplikacji, informacje do monitorowania aplikacji są pobierane z osobnego repozytorium gitlab, a format danych nie zmienił się od czasów Puppet 3, co wprowadza dodatkowe komplikacje w konfiguracji.
Kod Puppet dla aplikacji, dbajcie o 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,
)
{
#### APLIKACJE ####
$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'], {} )) } # dodaje powiadomienia dla domyślnej grupy (systemy) + każda grupa zdefiniowana w 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') # Wybierz regex do sprawdzenia
$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 | { # Podziel aplikację według domen, jeśli jest ich dwie lub więcej
$vhost_name = {'http_vhost' => $vhost}
$vars = $data['vars'] + $vhost_name + $check_regex + $check_url
$web_ipaddress = is_array($vdata['web_ipaddress']) ? { # Zrób IP-adres tablicą, jeśli nie jest, ponieważ askizzy ma 2 IP i jest tablicą
true => $vdata['web_ipaddress'],
false => [$vdata['web_ipaddress']],
}
$access_from_zones = [$zone] + $apps_access_list[$data['accessible_from']] # Połącz domyślną strefę (gdzie aplikacja jest zdefiniowana) i dodatkowe strefy, jeśli istnieją
$web_ipaddress.each | String $ip_address | { # Dla każdego IP (jeśli mamy wiele)
$suffix = length($web_ipaddress) ? { # Jeśli mamy więcej niż jedno - dodaj IP jako sufiks do tej nazwy hosta, aby uniknąć duplikowania zasobów
1 => '',
default => "_${ip_address}"
}
$octets = split($ip_address, '.')
$ip_tag = "${octets[2]}.${octets[3]}" # Użycie ostatniego oktetu powoduje kolizję między nginx-vip 203.15.70.94 a 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}" # Jeśli jest to host dla ext - prefiks staje się '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 konfiguracji hostów i usług również wygląda tragicznie:
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 <<| |>> # będą aktywowane, gdy całkowicie przejdziemy na icinga
#### APPS ####
case $location {
'int', 'ext': {
$apps_by_zone = {}
}
'pm': {
$int_apps = hiera('int_docker_apps')
$int_app_defaults = hiera('int_docker_app_common')
$st_apps = hiera('staging_docker_apps')
$srs_apps = hiera('pm_docker_apps_srs')
$pm_apps = hiera('pm_docker_apps') + $st_apps + $srs_apps
$pm_app_defaults = hiera('pm_docker_app_common')
$apps_by_zone = {
'int' => $int_apps,
'pm' => $pm_apps,
}
$app_access_by_zone = {
'int' => {'accessible_from' => $int_app_defaults['accessible_from']},
'pm' => {'accessible_from' => $pm_app_defaults['accessible_from']},
}
}
default: {
fail('Upewnij się, że węzeł ma ustawiony fakt $location (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 <<| |>> # To jest dla inwazji statków kosmicznych, gdy będzie gotowa.
$hosts_has_large_disks = query_nodes('mountpoints.*.size_bytes >= 1099511627776')
$hosts.each | String $hostgroup, Hash $list_of_hosts_with_settings | { # Podział list witryn według grup hostów - 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 |{ # Podział grup hostów według hostów
# Czy ten host znajduje się w tablicy $hosts_has_large_disks? Jeśli tak, ustaw 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)) # Łączenie zmiennych osobno
$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){ # Wszystkie hosty zarządzane przez API
$hosts_api.each | String $zone, Hash $hosts_api_zone | { # Podział hostów API według stref
$hosts_api_zone.each | String $hostgroup, Hash $list_of_hosts_with_settings | { # Podział list witryn według grup hostów - 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 |{ # Podział grup hostów według hostów
# Czy ten host znajduje się w tablicy $hosts_has_large_disks? Jeśli tak, ustaw 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'],
}
}
}
}
}
#### KONIEC HOSTÓW ####
#### USŁUGI ####
$services.each | String $service_group, Hash $s_list |{ # Grupa usług i lista usług w tej grupie
$service_list = $s_list['checks'] # Lista rzeczywistych kontroli, osobno od ustawień grupy SG
$service_list.each | String $service_name, Hash $data |{
$merged_defaults = merge($service_defaults, $s_list['settings']) # globalne domyślne usługi + domyślne ustawienia grupy usług
$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)
# Jeśli nadpisujemy domyślny check_timeout, ale nie nrpe_timeout, sprawiamy, że nrpe_timeout jest taki sam jak check_timeout
if ( $merged_data['check_timeout'] and ! $this_service_vars['nrpe_timeout'] ) {
# UWAGA: Icinga automatycznie przekształci 1m na 60!
$nrpe = { 'nrpe_timeout' => $merged_data['check_timeout'] }
} else {
$nrpe = {}
}
# Domyślnie używamy nrpe i wszystkie polecenia są uruchamiane przez nrpe. Tak więc vars.nrpe_command = $service_name jest domyślną wartością
# Jeśli jest to polecenie Icinga po stronie serwera - nie potrzebujemy 'nrpe_command'
# ale nie zaszkodzi mieć tę zmienną, a kod jest krótszy
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 = {}
}
# Składanie $vars z globalnych domyślnych ustawień usługi, ustawień grupy usług, ustawień tej konkretnej kontroli i nie zapominajmy o ustawieniach nrpe.
if $all_service_vars['graphite_template'] {
$graphite_template = {'check_command' => $all_service_vars['graphite_template']}
}else{
$graphite_template = {'check_command' => $service_name}
}
$service_notify = [] + pick($settings_vars['notify_group'], []) + pick($this_service_vars['notify_group'], []) # pick jest wymagany wszędzie, w przeciwnym razie staje się "Wartość '' nie może być przekonwertowana na typ numeryczny"
$service_notify_group = $service_notify ? {
[] => $service_defaults['vars']['notify_group'],
default => $service_notify
} # Przypisz grupę domyślną (systemy), jeśli nie zdefiniowano innych grup
$vars = $all_service_vars + $nrpe + $check_command + $graphite_template + {'notify_group' => $service_notify_group}
# To musi być łączone osobno, ponieważ scalanie tego jako część MERGED_DATA nadpisuje tablice zamiast je łączyć, więc tracimy niektóre "przypisania" i "ignorowanie" wartości
$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'],
}
}
}
#### KONIEC USŁUG ####
#### INNE NUDNE RZECZY ####
$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'],
}
}
# Scalanie ustawień powiadomień dla użytkowników z innymi ustawieniami
$users_oncall = deep_merge($users, $oncall)
# Magia. Nie ruszaj.
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 tym kodem i ulepszam go w miarę możliwości. Jednak to taki kod umożliwił stosowanie prostego i zrozumiałego składni w Hiera:
Dane
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 są podzielone na grupy, z których każda ma domyślne ustawienia dotyczące tego, gdzie i jak często uruchamiać te kontrole, jakie powiadomienia wysyłać i do kogo.
W każdej kontroli można nadpisać dowolną opcję, a wszystko to łączy się jeszcze z domyślnymi ustawieniami wszystkich kontroli jako całości. Dlatego w pliku config.pp przedstawiono taki kod – tam zachodzi łączenie wszystkich domyślnych ustawień z ustawieniami grup oraz następnie z każdą indywidualną kontrolą.
Bardzo ważną zmianą stała się również możliwość używania funkcji w ustawieniach, na przykład funkcji zamiany portu, adresu i URL do 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 jej, w przeciwnym razie 443. Na przykład, interfejs webowy jabber działa na 9090, a Unifi — na 7443.
http_vhost oznacza zignorować DNS i wziąć ten adres.
Jeśli w hoście podano uri — to podążaj za nim, w przeciwnym razie weź "\/".
Jeśli chodzi o http_ssl, to miałem zabawną historię — ten parametr nigdy nie chciał się wyłączyć na żądanie. Długo myślałem nad tą linią, aż zrozumiałem, że zmienna jest w definicji hosta:
http_ssl: falseWstawia się w wyrażenie
if(host.vars.http_ssl) { return false } else { return true }jak false i w rezultacie otrzymujemy
if(false) { return false } else { return true }to znaczy, że sprawdzenie ssl zawsze jest aktywne. Rozwiązanie polegało na zmianie składni:
http_ssl: noWnioski:
Zalety:
- Teraz mamy jeden system monitorowania, a nie dwa, jak przez ostatnie 7-8 miesięcy, ani jeden, przestarzały i podatny na ataki.
- Struktura danych hostów / usług (sprawdzania) teraz (moim zdaniem) jest znacznie bardziej czytelna i zrozumiała. Dla innych okazało się to nie tak oczywiste, więc musiałem stworzyć kilka stron w lokalnej wiki, aby wyjaśnić, jak to wszystko działa i co gdzie poprawić.
- Istnieje możliwość elastycznego skonfigurowania sprawdzeń za pomocą zmiennych i funkcji, na przykład, aby sprawdzić http_regexp można określić poszukiwany wzór, kod zwrotny, url i port w ustawieniach hosta.
- Jest kilka paneli (dashboards), dla każdego z nich można określić swoją listę wyświetlanych alarmów i zarządzać tym wszystkim przez Puppet i merge requests.
Wady:
- Inercja członków zespołu — Nagios działał, działał i działał, a ten twój Icinga wiecznie się wiesza i zwalnia. A jak tu zobaczyć historię? A, cholera, ona się nie aktualizuje… (Rzeczywisty problem — historia alarmów nie aktualizuje się automatycznie, tylko po F5)
- Inercja systemu — kiedy klikam w interfejsie webowym na „odśwież” (check now) — wynik wykonania zależy od pogody na Marsie, szczególnie w przypadku złożonych usług, które wymagają dziesiątków sekund na wykonanie. Taki rezultat to normalna sprawa.

- Ogólnie w półrocznej statystyce pracy dwóch systemów obok siebie, Nagios zawsze działał szybciej niż Icinga i bardzo mnie to irytowało. Moim zdaniem, coś tam namieszali z timerami i sprawdzenie co pięć minut w rzeczywistości odbywa się co 5:30 lub coś w tym stylu.
- Jeśli w każdym momencie czasu zrestartuję usługę (systemctl restart icinga2) — wszystkie sprawdzenia, które w danym momencie były w trakcie wykonywania, wygenerują alarm critical <terminated by signal 15> na ekranie i z zewnątrz wygląda to, jakby wszystko padło ().
Ale generalnie — działa.
Źródło: habr.com

