Konfiguracja prostego monitoringu serwera narzędziami systemowymi

Opublikowano 17 min czytania

Skrypt kontrolny, timer systemd i przetestowany kanał powiadomień wystarczą dla pojedynczego serwera root. Ten poradnik pokazuje, co monitorować, jak sprawdzić każdy krok i od kiedy opłaca się duża skrzynka narzędziowa.

Serwer nie zgłasza się sam, kiedy coś idzie nie tak. Działa dalej, dopóki nie przestanie działać, a pierwszą informację zwrotną dostajesz od klienta albo od siebie samego, jeśli akurat spojrzysz na maszynę. Ten poradnik buduje najmniejszy monitoring, który kładzie temu kres: skrypt kontrolny, timer systemd i jedną drogę powiadomień. Bez bazy szeregów czasowych, bez dashboardu, bez dodatkowego otwartego portu.

Systemami odniesienia są Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS oraz Ubuntu 22.04 LTS. Tam, gdzie te cztery systemy się różnią, jest to wyraźnie zaznaczone. Wszystkie polecenia napisano z myślą o pracy jako root; jako zwykły użytkownik poprzedzasz każde polecenie słowem sudo. Ten poradnik rozwija krok 8 z listy kontrolnej dla nowego serwera root.

Co w ogóle warto monitorować

Najczęstszym błędem przy pierwszym monitoringu nie jest zbyt mała liczba mierzonych wartości, tylko zbyt duża. Kto zbiera 40 wskaźników, nie ogląda żadnego z nich. Sens ma wyłącznie to, co potrafi zatrzymać pracę serwera i na co jesteś w stanie zareagować. Zostaje siedem punktów.

WskaźnikDlaczego trafia na listęSkąd bierze się wartośćSensowny próg
Miejsce na dyskuNajczęstsza przyczyna awarii, której nikt nie widzi nadchodzącejdf --output=pcent,targetod 85 procent
InodyDysk pozornie wolny, a mimo to „No space left on device”df --output=ipcent,targetod 85 procent
Wolna pamięć RAMOOM killer rzadko trafia akurat w ten proces, który sam byś poświęciłMemAvailable/proc/meminfoponiżej 200 MB
Obciążenie systemuPokazuje zator niezależnie od tego, czy winna jest CPU, czy dysktrzecie pole w /proc/loadavgśrednia z 15 minut powyżej dwukrotności liczby rdzeni
Usługi w stanie failedUsługa, która padnie w nocy, inaczej pozostaje martwa aż do ranasystemctl is-system-runningwszystko poza running
Wygasanie certyfikatuZ dnia na dzień odcina każdego odwiedzającego, a nie tylko częśćopenssl x509 -checkendponiżej 21 dni pozostałej ważności
Dostępność z zewnątrzJako jedyna kontrola odpowiada na pytanie, czy serwer jeszcze istniejedrugi host, curldwie nieudane próby z rzędu

Na liście nie ma obciążenia CPU w procentach: serwer, który ciągnie 100 procent, bo pracuje na nim koder wideo, robi dokładnie to, do czego go postawiono. Z tego samego powodu brakuje przepustowości sieci oraz liczby procesów. Jedno i drugie pomaga w szukaniu przyczyny, ale nie nadaje się na alarm, bo nie istnieje wartość, od której koniecznie musiałbyś zadziałać.

Droga odwrotu, zanim powstanie pierwszy plik

Monitoring to operacja czysto odczytowa i w normalnych warunkach nie jest w stanie niczego zepsuć. Trzy rzeczy mimo wszystko potrafią.

Skrypt, który naprawia, zamiast meldować. Pomysł jest kuszący: skoro nginx padł, niech skrypt po prostu uruchomi go ponownie. Wychodzi z tego usługa, która startuje co dziesięć minut, działa pół sekundy i przykrywa prawdziwą przyczynę. A skrypt, który przy pełnym dysku samodzielnie kasuje pliki, kiedyś skasuje coś, co było potrzebne. Pierwsza wersja wyłącznie czyta i nie wywołuje ani systemctl restart, ani rm, ani kill.

Otwarte interfejsy sieciowe. Eksportery metryk nasłuchujące na wszystkich adresach to jedna z najczęstszych niezamierzonych publikacji danych na pojedynczych serwerach. Opisana tu metoda nie otwiera żadnego portu i nie wymaga reguły firewalla.

Sama droga alarmu. Monitoring, którego powiadomienia nigdy nie przetestowano, nie jest monitoringiem, tylko dobrym samopoczuciem. Test znajdziesz niżej i nie jest on opcjonalny.

Jak dostać się na serwer bez SSH

Serwery root z KVM oraz serwery dedykowane nie mają ani IPMI, ani iDRAC. Drogą dostępu, kiedy SSH przestaje odpowiadać, jest konsola VNC w panelu klienta. Nie wisi ona na stosie sieciowym systemu gościa, więc nie zablokuje jej ani reguła firewalla, ani przeciążona usługa SSH. Zaloguj się tam raz zawczasu i upewnij się, że znasz hasło roota.

Wyłącznik

Jeśli sam monitoring stanie się problemem, na przykład zacznie wysyłać alarmy co minutę, potrzebujesz dwóch poleceń. Zapamiętaj je, zanim zaczniesz:

systemctl disable --now kh-monitor.timer
systemctl mask kh-monitor.service

Pierwsze natychmiast zatrzymuje timer i sprawia, że nie wróci przy kolejnym starcie. Drugie to hamulec bezpieczeństwa: zamaskowanej usługi nie da się już przypadkiem uruchomić ręcznie, a cofasz to poleceniem systemctl unmask kh-monitor.service. Metoda jest mało ryzykowna przede wszystkim dlatego, że powstają wyłącznie nowe pliki; wycofanie zmian sprowadza się do skasowania tych plików. Mimo wszystko trzymaj otwartą drugą sesję SSH, dopóki pracujesz na systemie.

Najpierw sprawdź wskaźniki ręcznie

Zanim skrypt zacznie cokolwiek oceniać, każdą wartość powinieneś raz zobaczyć na własne oczy. Inaczej później nie rozstrzygniesz, czy alarm jest uzasadniony, czy po prostu twój próg nie ma sensu.

Miejsce na dysku i inody

df -h
df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay
df -i

Wykluczenia są konieczne. Na Ubuntu 22.04 i 24.04 snap montuje swoje pakiety jako obrazy squashfs tylko do odczytu, a te stoją na stałe na 100 procentach. Bez -x squashfs twój monitoring już od pierwszego przebiegu melduje pełny dysk, codziennie, w nieskończoność. To samo dotyczy punktów montowania overlay należących do Dockera.

Dwie właściwości warto znać. df nie przyjmuje -P--output jednocześnie i przerywa pracę komunikatem o wzajemnie wykluczających się opcjach. A na ext4 fabrycznie pięć procent jest zarezerwowane dla roota, dlatego df melduje już 100 procent, podczas gdy root nadal może pisać. Co robić po takim alarmie, opisuje artykuł Pełny dysk w Linuksie.

Kontrola inodów nie jest tematem pobocznym. Katalog z milionami maleńkich plików sesji albo plików cache potrafi zużyć wszystkie inody, podczas gdy df -h pokazuje mnóstwo wolnego miejsca. Zapisy kończą się wtedy błędem No space left on device, a wyjaśnienie, które samo się nasuwa, jest tym błędnym.

Pamięć RAM

free -m
awk '/^MemAvailable:/ { printf "%d MB\n", $2 / 1024 }' /proc/meminfo

Kolumna free na zdrowym systemie Linux jest prawie zawsze mała, bo kernel wykorzystuje nieużywaną pamięć jako cache plików. Wiarygodna jest tylko kolumna available, czyli MemAvailable: ilość pamięci, którą może dostać nowa aplikacja bez przenoszenia czegokolwiek do swapa. Alarmuj na tę wartość, nigdy na free.

Czy było już wcześniej ciasno, zdradzi log kernela:

journalctl -k -b --grep "Out of memory"

Każde trafienie to proces, który kernel zabił, bo skończyła się pamięć. Jak zareagować, nie zakładając swapa na oślep, opisuje artykuł Out of Memory i poprawna konfiguracja swapa.

Obciążenie systemu

nproc
cat /proc/loadavg
uptime

Pierwsze trzy pola w /proc/loadavg to średnie z jednej, pięciu i piętnastu minut. Dwie rzeczy bywają regularnie źle rozumiane. Po pierwsze obciążenie w Linuksie nie jest czystą miarą CPU: liczą się do niego także procesy czekające na dostęp do dysku. Obciążenie 20 na czterech rdzeniach może oznaczać, że CPU się pali, albo że zaciął się nośnik danych. Po drugie wartość jednominutowa nie nadaje się do alarmów, bo każdy przebieg backupu na chwilę podbija ją w górę. Bierz średnią z 15 minut, a próg ustaw względem liczby rdzeni.

Jeśli twój kernel udostępnia statystykę zatorów, będzie ona bardziej wymowna, bo rozdziela CPU, wejście i wyjście oraz pamięć. Dostępna jest nie wszędzie:

test -d /proc/pressure && cat /proc/pressure/io || echo "brak statystyki zatorów w tym kernelu"

Usługi

systemctl is-system-running
systemctl --failed --no-pager
systemctl is-active nginx

systemctl is-system-running to najkrótsza sensowna kontrola całości. Zwraca running, kiedy żadna jednostka nie jest w stanie błędu, oraz degraded, gdy choć jedna w nim jest. Kod wyjścia wynosi odpowiednio 0 albo coś różnego od 0.

Dwie pułapki. Stan degraded utrzymuje się, dopóki po naprawie nie zresetujesz go poleceniem systemctl reset-failed; jedno zadanie, które raz zawiodło, inaczej podtrzymuje ten komunikat tygodniami. A przy sprawdzaniu pojedynczych usług systemy odniesienia się różnią: na Ubuntu 24.04 SSH startuje przez aktywację gniazda, ssh.service jest tam w spoczynku inactive, mimo że SSH działa bez zarzutu. Kto monitoruje tam ssh.service, dostaje stały fałszywy alarm. Na Ubuntu 24.04 monitorować należy ssh.socket, a na Debianie 12, Debianie 13 i Ubuntu 22.04 przeciwnie: ssh.service.

Wygasanie certyfikatu

openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/fullchain.pem
openssl x509 -checkend 1814400 -noout -in /etc/letsencrypt/live/example.com/fullchain.pem

Ciekawsze jest drugie polecenie. -checkend oczekuje liczby sekund, 1814400 to 21 dni. Jeśli certyfikat wygasa w tym terminie, polecenie wypisuje Certificate will expire i zwraca kod wyjścia 1, w przeciwnym razie Certificate will not expire oraz 0. Katalogi /etc/letsencrypt/live/etc/letsencrypt/archive są czytelne wyłącznie dla roota.

Ta kontrola ma lukę, którą wiele poradników przemilcza: sprawdza plik na dysku, a nie certyfikat, który wydaje twój serwer WWW. Jeśli odnowienie przejdzie, ale przeładowanie serwera WWW się nie powiedzie, plik jest nowy, a wydawany klucz stary. Kontrola pliku nie melduje niczego, podczas gdy odwiedzający widzą już ostrzeżenie o certyfikacie. Ten przypadek wyłapuje dopiero spojrzenie z zewnątrz:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate

-servername nie jest opcjonalne, gdy tylko na jednym adresie IP leży kilka certyfikatów. Bez tego dodatku dostajesz domyślny certyfikat serwera i sprawdzasz niewłaściwą domenę.

Skrypt kontrolny

Wszystkie kontrole razem składają się na skrypt, który nie robi nic poza czytaniem, porównywaniem i meldowaniem w razie błędu. Potrzebuje curl, a dla drogi przez webhooka także jq. W minimalnych instalacjach Debiana brakuje obu:

apt update
apt install -y curl jq
cat > /usr/local/sbin/kh-monitor <<'EOF'
#!/bin/bash
set -u

DISK_WARN="${DISK_WARN:-85}"
INODE_WARN="${INODE_WARN:-85}"
MEM_MIN_MB="${MEM_MIN_MB:-200}"
LOAD_FACTOR="${LOAD_FACTOR:-2}"
CERT_DAYS="${CERT_DAYS:-21}"
UNITS="${UNITS:-ssh nginx}"
STATE_DIR="${STATE_DIRECTORY:-/var/lib/kh-monitor}"

problems=""
add() { problems+="- ${1}"$'\n'; }

notify() {
  printf '%s | %s\n' "$1" "$(printf '%s' "$2" | tr '\n' ' ')"
  if [ -n "${WEBHOOK_URL:-}" ]; then
    printf '%s\n%s' "$1" "$2" | jq -Rs '{text: .}' \
      | curl -fsS -m 10 -o /dev/null -H 'Content-Type: application/json' \
             --data-binary @- "$WEBHOOK_URL"
  fi
  if [ -n "${MAILTO:-}" ]; then
    printf '%s\n' "$2" | mail -s "$1" "$MAILTO"
  fi
}

while read -r pcent target; do
  pcent="${pcent%\%}"
  case "$pcent" in ''|*[!0-9]*) continue ;; esac
  [ "$pcent" -ge "$DISK_WARN" ] && add "Dysk ${target} zajęty w ${pcent} procentach"
done < <(df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay | tail -n +2)

while read -r ipcent target; do
  ipcent="${ipcent%\%}"
  case "$ipcent" in ''|*[!0-9]*) continue ;; esac
  [ "$ipcent" -ge "$INODE_WARN" ] && add "Inody na ${target} zajęte w ${ipcent} procentach"
done < <(df --output=ipcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay | tail -n +2)

mem_avail=$(awk '/^MemAvailable:/ { printf "%d", $2 / 1024 }' /proc/meminfo)
[ "${mem_avail:-0}" -lt "$MEM_MIN_MB" ] && add "tylko ${mem_avail} MB wolnej pamięci RAM"

cores=$(nproc)
load15=$(awk '{ print $3 }' /proc/loadavg)
awk -v l="$load15" -v c="$cores" -v f="$LOAD_FACTOR" 'BEGIN { exit !(l > c * f) }' \
  && add "Obciążenie w średniej z 15 minut ${load15} przy ${cores} rdzeniach"

sysstate=$(systemctl is-system-running)
[ "$sysstate" = "running" ] || add "systemd melduje stan ${sysstate}"

for unit in $UNITS; do
  systemctl is-active --quiet "$unit" || add "Usługa ${unit} jest w stanie $(systemctl is-active "$unit")"
done

for cert in /etc/letsencrypt/live/*/fullchain.pem; do
  [ -r "$cert" ] || continue
  openssl x509 -checkend $(( CERT_DAYS * 86400 )) -noout -in "$cert" >/dev/null 2>&1 \
    || add "Certyfikat ${cert} wygasa za mniej niż ${CERT_DAYS} dni"
done

mkdir -p "$STATE_DIR"
now=$(printf '%s' "$problems" | sha256sum | cut -d' ' -f1)
before=$(cat "${STATE_DIR}/last" 2>/dev/null || true)
printf '%s' "$now" > "${STATE_DIR}/last"

if [ -z "$problems" ]; then
  [ -n "${HEARTBEAT_URL:-}" ] && curl -fsS -m 10 -o /dev/null "$HEARTBEAT_URL"
  [ -n "$before" ] && [ "$now" != "$before" ] \
    && notify "Odwołanie alarmu $(hostname -s)" "Wszystkie kontrole znowu bez zastrzeżeń."
  exit 0
fi

[ "$now" = "$before" ] && exit 0
notify "Ostrzeżenie $(hostname -s)" "$problems"
EOF

Cztery miejsca zasługują na wyjaśnienie.

  • Pętle czytają z < <( ... ), a nie z potoku. Potok przenosi pętlę do osobnej podpowłoki, a komunikaty zebrane w środku zniknęłyby po done. Ten błąd jest podstępny, bo skrypt przebiega bezbłędnie i tylko nigdy niczego nie melduje.
  • Progi wczytują się ze środowiska. Każdą granicę możesz nadpisać na potrzeby pojedynczego wywołania. Na tym opiera się opisany niżej test alarmu.
  • Stan zapisywany jest jako suma kontrolna. Meldunek wychodzi tylko wtedy, gdy lista problemów się zmieniła. Inaczej pełny dysk wysyła ci co dziesięć minut tę samą wiadomość, a po dwóch dniach wyłączasz monitoring.
  • Pętla certyfikatów trafia w pustkę, jeśli katalog Let's Encrypt nie istnieje. Wzorzec pozostaje nierozwinięty, [ -r "$cert" ] zawodzi, a przebieg zostaje pominięty.

Sprawdź to teraz, zanim do gry wejdzie systemd:

chmod 700 /usr/local/sbin/kh-monitor
bash -n /usr/local/sbin/kh-monitor && echo "Składnia ok"
/usr/local/sbin/kh-monitor; echo "Kod wyjścia $?"

Na zdrowym serwerze trzecie polecenie nie wypisuje nic poza Kod wyjścia 0. Jeśli widzisz komunikat, to albo coś jest nie w porządku, albo któryś próg nie pasuje, na przykład dlatego, że w UNITS stoi usługa, której tu nie ma.

Timer systemd

Cronjob też by wystarczył. Timer ma jednak cztery konkretne zalety: nie startuje drugiej instancji, dopóki działa pierwsza, nadrabia przebieg opuszczony podczas wyłączenia serwera, jego wyjście trafia do journala i wyłącza się go jednym poleceniem. Jednostka usługi nie potrzebuje sekcji [Install], bo aktywuje ją nie start systemu, tylko timer.

cat > /etc/systemd/system/kh-monitor.service <<'EOF'
[Unit]
Description=Krótka kontrola stanu serwera
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/kh-monitor
EnvironmentFile=-/etc/default/kh-monitor
StateDirectory=kh-monitor
SyslogIdentifier=kh-monitor
Nice=10
IOSchedulingClass=idle
EOF
cat > /etc/systemd/system/kh-monitor.timer <<'EOF'
[Unit]
Description=Uruchamia kh-monitor regularnie

[Timer]
OnCalendar=*:0/10
RandomizedDelaySec=60
Persistent=true

[Install]
WantedBy=timers.target
EOF

StateDirectory=kh-monitor zakłada /var/lib/kh-monitor z odpowiednimi uprawnieniami i ustawia zmienną STATE_DIRECTORY, po którą sięga skrypt. Poprzedzający minus w EnvironmentFile=-/etc/default/kh-monitor oznacza, że brak pliku nie jest błędem. RandomizedDelaySec=60 rozprasza moment startu, a Persistent=true nadrabia przy rozruchu przebieg opuszczony w czasie wyłączenia.

Timer automatycznie aktywuje usługę o tej samej nazwie, wiersz Unit= nie jest potrzebny:

systemd-analyze verify /etc/systemd/system/kh-monitor.service
systemd-analyze calendar '*:0/10'
systemctl daemon-reload
systemctl start kh-monitor.service
systemctl enable --now kh-monitor.timer

Kontrola powodzenia:

systemctl list-timers kh-monitor.timer --no-pager
journalctl -u kh-monitor.service -n 20 --no-pager

Lista musi pokazać wiersz z najbliższym terminem wykonania. Jeśli zostaje pusta, timer nie jest aktywny. systemd-analyze calendar znajduje literówki w wyrażeniu czasowym: przelicza je na postać normalną i podaje najbliższy termin. Szczegóły o plikach jednostek i ich obrazach błędów znajdziesz w artykule Tworzenie usługi systemd.

Kanał powiadomień

Tutaj wykłada się większość samodzielnie zbudowanych systemów monitoringu. Nadajnik alarmu działa na tym samym serwerze, który nadzoruje, więc pada razem z nim. Melduje przez to niezawodnie wszystko poza tym jednym przypadkiem, który naprawdę się liczy.

Odpowiedzią jest sygnał życia, czyli heartbeat: serwer melduje się po każdym udanym przebiegu u zewnętrznego odbiorcy, a kiedy meldunek nie przychodzi, to odbiorca podnosi alarm. Skrypt powyżej robi dokładnie to, jeśli ustawisz HEARTBEAT_URL. Awaria sieci, zawieszony system plików i system po crashu wyglądają dla odbiorcy tak samo, a o wszystkich trzech chcesz wiedzieć.

Dane dostępowe należą do osobnego pliku, a nie do skryptu. Adres webhooka jest sekretem: kto go ma, może wysyłać wiadomości w twoim imieniu:

cat > /etc/default/kh-monitor <<'EOF'
WEBHOOK_URL=https://przyklad.example/hooks/xxxxxxxx
HEARTBEAT_URL=https://przyklad.example/heartbeat/xxxxxxxx
UNITS="ssh nginx"
EOF
chmod 600 /etc/default/kh-monitor

Cudzysłowy wokół UNITS są ważne: systemd poradziłby sobie i bez nich, ale przy opisanym niżej przebiegu testowym plik wczytuje shell, a tam nginx bez cudzysłowów zostałby potraktowany jako polecenie. Wpisuj wyłącznie jednostki, które w tym systemie istnieją, na Ubuntu 24.04 więc ssh.socket zamiast ssh.

Kto woli e-mail, potrzebuje drogi wysyłki. Pełnoprawny serwer pocztowy to przesada, wystarczy zwykły przekaźnik:

apt install -y msmtp msmtp-mta bsd-mailx

Konfigurujesz to w /etc/msmtprc przy użyciu danych dostępowych istniejącej skrzynki. Plik zawiera hasło i należy mu się chmod 600, inaczej msmtp odmówi pracy z odwołaniem do uprawnień pliku. Dwa uczciwe ograniczenia: e-mail z dopiero co przydzielonego adresu IP serwera często ląduje w folderze spamu albo zostaje odrzucony, a wiadomość, którą zobaczysz dopiero przy następnym zajrzeniu do skrzynki, jest przy awarii po prostu za wolna. Do alarmów praktyczniejszym wyborem jest doręczanie push.

Wywołaj alarm raz na próbę

Ten krok nie jest opcjonalny. Wymuś meldunek, ustawiając bezsensownie jeden z progów na czas pojedynczego wywołania:

set -a; . /etc/default/kh-monitor; set +a
DISK_WARN=0 /usr/local/sbin/kh-monitor
rm -f /var/lib/kh-monitor/last

Wiadomość musi teraz faktycznie do ciebie dotrzeć, a nie tylko wylądować w journalu. Trzeci wiersz kasuje zapisany stan, żeby testowy alarm nie stłumił kolejnego prawdziwego przebiegu. Jeden efekt uboczny jest zamierzony: jeśli doręczenie zawiedzie, kh-monitor.service kończy się błędem i pojawia się w systemctl --failed. Cichy kanał powiadomień byłby najgorszym możliwym błędem w monitoringu.

Widok z zewnątrz

Heartbeat mówi ci, że serwer żyje. Nie mówi, że twoja strona odpowiada. Do tego potrzebna jest kontrola z innej lokalizacji, według tego samego wzorca ze skryptem i timerem, tyle że na drugim hoście:

curl -fsS -m 10 -o /dev/null -w '%{http_code} %{time_total}\n' https://example.com/

O wartości tej kontroli decydują cztery punkty. Po pierwsze: sprawdzaj prawdziwą usługę, a nie sam ICMP. Serwer, który odpowiada na ping, podczas gdy serwer WWW wisi w nieskończonej pętli, przy kontroli pingiem uchodzi za zdrowy. Po drugie: -f każe curlowi zawieść przy kodach błędów HTTP, bez tego przełącznika także strona błędu liczy się jako sukces. Po trzecie: alarmuj dopiero po dwóch nieudanych próbach z rzędu, inaczej każde krótkie zakłócenie sieci zgłosi awarię. Po czwarte: wywołanie co minutę z tego samego adresu może trafić na ograniczenie liczby zapytań albo zostać uznane przez oprogramowanie blokujące za atak; wpisz adres hosta wykonującego kontrolę jako wyjątek.

Bez drugiego serwera zostaje hostowana usługa kontrolna. Darmowe oferty sprawdzają zwykle co pięć minut, o awarii dowiadujesz się więc z odpowiednim opóźnieniem. Dla pojedynczego serwera to wystarczy i jest lepsze niż alternatywa, czyli nie dowiedzieć się wcale.

Typowe błędy i ich rozwiązania

Failed to start kh-monitor.service: Unit kh-monitor.service not found.: po założeniu albo zmianie jednostki brakuje systemctl daemon-reload. Drugą najczęstszą przyczyną jest zły katalog; własne jednostki należą do /etc/systemd/system/.

The unit files have no installation config: wywołałeś systemctl enable kh-monitor.service zamiast kh-monitor.timer. Usługa świadomie nie ma sekcji [Install], aktywuje się timer.

code=exited, status=203/EXEC: systemd nie zdołał uruchomić pliku. Albo nie zgadza się ścieżka w ExecStart, albo brakuje chmod 700, albo plik edytowano pod Windowsem i ma końce wierszy ze znakiem powrotu karetki. Pomaga na to sed -i 's/\r$//' /usr/local/sbin/kh-monitor.

Syntax error: redirection unexpected: skrypt uruchomiono przez sh zamiast bash. Na Debianie i Ubuntu /bin/sh to powłoka dash, a ta nie zna ani < <( ... ), ani += dla łańcuchów znaków. Pierwszy wiersz musi brzmieć #!/bin/bash.

bash: mail: command not found: nie zainstalowano żadnego programu pocztowego. Polecenie mail pochodzi zależnie od systemu z bsd-mailx albo mailutils, a te dwa różnią się przełącznikami. Trzymaj się -s dla tematu wiadomości.

curl: (22) The requested URL returned error: 404: adres webhooka jest błędny albo został skasowany po drugiej stronie. Bez -f curl przełknąłby ten błąd po cichu.

curl: (60) SSL certificate problem: certificate has expired: przy kontroli z zewnątrz to nie błąd narzędzia, tylko szukany wynik. Przy własnym webhooku ten sam komunikat wskazuje na złą godzinę na serwerze wykonującym kontrolę.

Certificate will expire: zwykłe wyjście openssl x509 -checkend z kodem wyjścia 1. Sprawdź, czy odnawianie nadal działa i czy serwer WWW jest po nim przeładowywany.

Failed to parse calendar specification: wyrażenie za OnCalendar= jest nieprawidłowe. Przetestuj je osobno przez systemd-analyze calendar, zanim trafi do jednostki.

Warning: Stopping kh-monitor.service, but it can still be activated by: kh-monitor.timer: zatrzymałeś usługę zamiast timera. Usługa i tak działa tylko przez sekundy, wyłączyć trzeba timer.

Alarm przy każdym przebiegu, chociaż nic się nie zmieniło: stan nie jest zapisywany. Sprawdź, czy w jednostce stoi StateDirectory=kh-monitor oraz czy /var/lib/kh-monitor/last istnieje i jest zapisywalny.

Brak alarmu, chociaż coś jest wyraźnie zepsute: wywołaj wymuszony meldunek opisany wyżej. Jeśli i on nie dotrze, winna jest droga doręczenia, a nie same kontrole.

Różnice między czterema systemami

SystemJednostka SSH, którą trzeba monitorowaćPunkty montowania squashfsGdzie lądują komunikaty
Debian 13 (trixie)ssh.service, pojedyncze obrazy korzystają z ssh.socketz reguły braktylko journal, w minimalnych instalacjach brakuje rsysloga
Debian 12 (bookworm)ssh.servicez reguły brakjournal, rsyslog zależnie od wariantu instalacji
Ubuntu 24.04 LTSssh.socketzwykle obecne, potrzebne wykluczeniejournal i rsyslog
Ubuntu 22.04 LTSssh.servicezwykle obecne, potrzebne wykluczeniejournal i rsyslog

We wszystkich czterech systemach identyczne jest to, że systemd-analyze, StateDirectory=RandomizedDelaySec= są dostępne, a skrypt oraz pliki jednostek działają bez zmian.

Kiedy opłaca się duża skrzynka narzędziowa

To, co masz teraz, ma wyraźne granice. Nie zapisuje historii, nie sprawdzisz więc, czy zużycie pamięci rośnie od trzech tygodni. Nie zna korelacji między wieloma hostami. I nie ma ani eskalacji, ani reguł dyżuru, ani możliwości wyciszenia znanego alarmu na dwie godziny.

Dokładnie te punkty odpowiadają na pytanie o przesiadkę. Budowa systemu metryk z dashboardem opłaca się, gdy tylko spełniony jest jeden z nich:

  • Prowadzisz więcej niż garstkę serwerów i chcesz widzieć je obok siebie.
  • Potrzebujesz historii i trendów, na przykład do planowania pojemności albo po to, by odpowiedzieć liczbami na skargę o powolne działanie.
  • Dyżur dzieli między siebie kilka osób, potrzebne są więc stopnie eskalacji i wyciszanie.
  • Musisz udokumentować dostępność wobec osób trzecich.

Jeśli nie pasuje żaden z nich, taka budowa dla pojedynczego serwera jest zwykle złym interesem. Kolektor, baza danych, dashboard i eksporter zajmują razem bez trudu kilkaset megabajtów pamięci RAM na dokładnie tej maszynie, której wolną pamięć mają nadzorować. Do tego dochodzi kolejny otwarty port oraz drugie oprogramowanie, które trzeba aktualizować. Kluczowy punkt pozostaje bez zmian: jeśli kolektor działa na tym samym serwerze, to o awarii tego serwera nie powiadomi ani trochę lepiej niż skrypt. Przy przesiadce kolektor należy postawić na innym hoście, a eksporter wiąże się z 127.0.0.1 albo jest ograniczony firewallem do adresu tego hosta.

Droga pośrednia sprawdza się dobrze: timer i skrypt zostają, bo pokrywają przypadek alarmowy, a system metryk dochodzi wtedy, gdy potrzebujesz historii. Jedno nie wyklucza drugiego.

Wycofanie zmian

Jeśli chcesz się tej metody znowu pozbyć, wystarczy pięć wierszy:

systemctl disable --now kh-monitor.timer
rm -f /etc/systemd/system/kh-monitor.timer /etc/systemd/system/kh-monitor.service
rm -f /usr/local/sbin/kh-monitor /etc/default/kh-monitor
rm -rf /var/lib/kh-monitor
systemctl daemon-reload

Kontrola końcowa

Sześć kontroli, które pokazują stan faktyczny, a nie ten, na który liczysz:

  1. systemctl list-timers kh-monitor.timer --no-pager podaje najbliższy termin wykonania.
  2. journalctl -u kh-monitor.service --since "1 hour ago" --no-pager pokazuje przebiegi w oczekiwanych odstępach.
  3. Wymuszony alarm przez DISK_WARN=0 dociera do ciebie, a nie tylko do journala.
  4. Po poleceniu reboot timer działa dalej, bez żadnego działania z twojej strony.
  5. Heartbeat się odzywa: zatrzymaj timer i odczekaj, czy odbiorca podniesie alarm.
  6. systemctl --failed --no-pager nie wypisuje niczego, a w szczególności nie kh-monitor.service.

Punkt piąty jest najbardziej niewygodny i najważniejszy. Monitoring, w którym przypadek alarmowy nigdy nie nastąpił, jest tylko przypuszczeniem. Dopiero gdy celowo wywołasz awarię i otrzymasz wiadomość, łańcuch od serwera aż do twojego telefonu jest dowiedziony w całości.

Najczęstsze pytania

Czy dla pojedynczego serwera root naprawdę potrzebuję serwera metryk z dashboardem?
W większości przypadków nie. Kolektor, baza danych, dashboard i eksporter zajmują razem bez trudu kilkaset megabajtów pamięci RAM na dokładnie tej maszynie, której wolną pamięć mają nadzorować, a do tego dochodzi kolejny otwarty port oraz drugie oprogramowanie do aktualizowania. Skrypt kontrolny z timerem systemd pokrywa przypadek alarmowy w całości. Duża budowa nabiera sensu dopiero wtedy, gdy chcesz widzieć kilka serwerów obok siebie, potrzebujesz historii do planowania pojemności, dyżur dzieli między siebie kilka osób albo musisz udokumentować dostępność wobec osób trzecich.
Dlaczego timer systemd, a nie cronjob?
Jedno i drugie działa. Timer ma cztery praktyczne zalety: nie startuje drugiej instancji, dopóki działa jeszcze pierwsza, dzięki Persistent=true nadrabia przy rozruchu przebieg opuszczony podczas wyłączenia, jego wyjście trafia przez SyslogIdentifier automatycznie do journala i wyłącza się go jednym poleceniem systemctl disable --now kh-monitor.timer. Aktywuje się zawsze timer, a nie usługę: jednostka usługi świadomie nie ma sekcji [Install].
Mój monitoring stale melduje pełny dysk, chociaż wolnego miejsca jest dość. Z czego to wynika?
Prawie zawsze z punktów montowania snapa. Na Ubuntu 22.04 i 24.04 pakiety snap są montowane jako obrazy squashfs tylko do odczytu, a te z natury rzeczy stoją na stałe na 100 procentach. Wyklucz je, czyli df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs -x overlay. To samo dotyczy punktów montowania overlay należących do Dockera. Druga możliwa przyczyna: na ext4 fabrycznie pięć procent jest zarezerwowane dla roota, dlatego df może meldować już 100 procent, podczas gdy root nadal może pisać.
Skąd się dowiem, że serwer padł całkowicie?
Nie ze skryptu na tym serwerze, bo ten pada razem z nim. Są na to dwie uzupełniające się drogi. Sygnał życia, czyli heartbeat: serwer melduje się po każdym udanym przebiegu u zewnętrznego odbiorcy, a kiedy meldunek nie przychodzi, to odbiorca podnosi alarm. Oraz kontrola z zewnątrz: drugi host albo hostowana usługa kontrolna, która odpytuje prawdziwą usługę, a nie sam ICMP. Darmowe usługi kontrolne pracują zwykle co pięć minut, o awarii dowiadujesz się więc z odpowiednim opóźnieniem.
Którą jednostkę SSH trzeba monitorować?
To zależy od dystrybucji. Na Ubuntu 24.04 SSH startuje przez aktywację gniazda, ssh.service jest tam w spoczynku inactive, mimo że SSH działa bez zarzutu. Kto monitoruje tam ssh.service, dostaje stały fałszywy alarm; monitorować należy ssh.socket. Na Debianie 12, Debianie 13 i Ubuntu 22.04 jest odwrotnie, tam obowiązuje ssh.service. Pojedyncze obrazy Debiana 13 mają jednak również aktywny ssh.socket, sprawdź to poleceniem systemctl is-enabled ssh.socket.
Jak uniknąć tego, że co dziesięć minut dostaję to samo ostrzeżenie?
Przez plik stanu. Skrypt tworzy z listy znalezionych problemów sumę kontrolną, zapisuje ją w /var/lib/kh-monitor/last i melduje tylko wtedy, gdy ta suma zmieniła się względem poprzedniego przebiegu. Kiedy problem znika, przychodzi jednorazowe odwołanie alarmu. Jeśli mimo to dostajesz wiadomość przy każdym przebiegu, w jednostce brakuje wiersza StateDirectory=kh-monitor albo plik nie jest zapisywalny.
Certyfikat na dysku jest ważny, a odwiedzający i tak widzą ostrzeżenie. Jak to możliwe?
Bo kontrola pliku i wydawany certyfikat to dwie różne rzeczy. Jeśli automatyczne odnowienie przejdzie, ale przeładowanie serwera WWW się nie powiedzie, plik jest nowy, a wydawany klucz stary. openssl x509 -checkend nie melduje wtedy niczego. Sprawdzaj więc dodatkowo z zewnątrz przez openssl s_client -connect example.com:443 -servername example.com. Dodatek -servername nie jest opcjonalny, gdy tylko na jednym adresie IP leży kilka certyfikatów.
Jak szybko wyłączyć monitoring, kiedy zaczyna przeszkadzać?
Poleceniem systemctl disable --now kh-monitor.timer, które natychmiast zatrzymuje timer i sprawia, że nie wróci przy kolejnym starcie. Jako hamulec bezpieczeństwa systemctl mask kh-monitor.service blokuje dodatkowo każde ręczne uruchomienie, a cofasz to przez systemctl unmask. Całkowicie usuwasz tę metodę, kasując oba pliki jednostek, skrypt z /usr/local/sbin/, plik /etc/default/kh-monitor oraz katalog /var/lib/kh-monitor, a na koniec wywołując systemctl daemon-reload. Istniejąca konfiguracja pozostaje przy tym nietknięta, bo zakładane były wyłącznie nowe pliki.

Monitoring systemd Linux Debian Ubuntu Serwer root Monitorowanie serwera Bash