Servis ve Cihaz İzleme: Bir Şey Koptuğunda E-posta ile Haber Alın
Bir servisin çalışıp çalışmadığını ve ağdaki bir cihaza ulaşılıp ulaşılamadığını düzenli kontrol eden, yalnızca durum değiştiğinde haber veren hafif bir izleme betiği ve systemd timer kurulumu.
Büyük şirketler izleme için Zabbix, Prometheus ya da bulut hizmetleri kullanır. Tek bir Raspberry Pi ve birkaç cihazdan oluşan bir sistem içinse bunların hepsi fazladır. Yine de bir servis sessizce düştüğünde ya da ağdaki bir cihaz koptuğunda sizin öğrenmeniz gerekir; müşteri ya da çalışan değil. Bu yazıda, yirmi satırlık bir betikle bunu nasıl çözeceğinizi gösteriyoruz.
Tasarım ilkeleri
İyi bir alarm üç özelliğe sahiptir:
- Durum değiştiğinde haber verir. Servis düştüğünde bir e-posta, geri geldiğinde bir e-posta. Aynı sorun için her kontrolde yeni mesaj göndermek, size "alarm yorgunluğu" yaşatır ve gerçek alarmı gözden kaçırtır.
- Yanlış alarmı azaltır. Bir ping'in bir kez kaybolması kopukluk demek değildir; birkaç ardışık başarısızlık sonrası alarm verin.
- Kendi başarısızlığını da fark eder. Alarm betiği çalışmıyorsa kimse haber vermez; bunu günlük özette bir "alarm sistemi son kontrol zamanı" satırıyla yakalayabilirsiniz.
Kontroller
İki tür kontrol örneği:
import subprocess
def servis_aktif(ad):
r = subprocess.run(["systemctl", "is-active", ad], capture_output=True, text=True)
return r.stdout.strip() == "active"
def ping(adres, deneme=3):
r = subprocess.run(["ping", "-c", str(deneme), "-W", "2", adres], capture_output=True)
return r.returncode == 0
ping -c 3 -W 2 üç paket gönderir, her biri için 2 saniye bekler; en az bir cevap gelirse komut başarılı döner. Cihazınız ping'e cevap vermiyorsa (bazı cihazlar kapatır), bunun yerine ilgili porta bağlanmayı deneyin:
import socket
def port_acik(adres, port, sure=3):
try:
with socket.create_connection((adres, port), timeout=sure):
return True
except OSError:
return False
Servis kontrolünün tek başına yeterli olmadığına dikkat edin: active bir servisin gerçekten cevap verdiğini göstermez. Bir web uygulaması için http://127.0.0.1:5000/ adresine istek atıp yanıt kodunu kontrol etmek daha anlamlıdır.
Durum hafızası: yalnızca değişimde alarm
Betik her çalıştığında önceki sonucu hatırlamalı. Basit bir JSON dosyası yeter:
import json, os, time
DURUM = "/var/lib/izleme/durum.json"
ESIK = 3 # kaç ardışık başarısızlıktan sonra alarm
def yukle():
try:
return json.load(open(DURUM))
except (OSError, ValueError):
return {}
def kaydet(d):
os.makedirs(os.path.dirname(DURUM), exist_ok=True)
gecici = DURUM + ".tmp"
json.dump(d, open(gecici, "w"))
os.replace(gecici, DURUM) # yarım dosya kalmasın
def degerlendir(ad, tamam, durum, bildirimler):
k = durum.setdefault(ad, {"hata": 0, "alarm": False})
if tamam:
if k["alarm"]:
bildirimler.append(f"DÜZELDİ: {ad}")
k.update(hata=0, alarm=False)
else:
k["hata"] += 1
if k["hata"] >= ESIK and not k["alarm"]:
k["alarm"] = True
bildirimler.append(f"SORUN: {ad} {k['hata']} kontroldür cevap vermiyor")
os.replace, dosya yazımını atomik yapar: betik tam yazım sırasında kesilse bile durum dosyası bozuk kalmaz. Durum dosyasını kalıcı bir dizine (/var/lib/...) yazın; Raspberry Pi'de /tmp bellekte ise yeniden başlatmada silinir ve her açılışta eski alarmlar yeniden gönderilir.
Ana betik
def main():
durum, bildirim = yukle(), []
kontroller = {
"Web paneli": lambda: servis_aktif("panel"),
"Veritabanı": lambda: servis_aktif("mariadb"),
"Kart okuyucu": lambda: port_acik("192.168.1.60", 80),
}
for ad, fn in kontroller.items():
try:
tamam = fn()
except Exception:
tamam = False
degerlendir(ad, tamam, durum, bildirim)
kaydet(durum)
if bildirim:
gonder("İzleme bildirimi", "\n".join(bildirim)) # e-posta gönderen fonksiyon
gonder fonksiyonu için e-posta yazısındaki örneği kullanabilirsiniz. İnternet kesikse e-posta da gidemez; bu durumda hata yakalayın ve bildirimi bir dosyaya yazın ki sonraki çalıştırmada tekrar denensin.
Her 2 dakikada bir çalıştırmak
/etc/systemd/system/izleme.service:
[Unit]
Description=Servis ve cihaz izleme
[Service]
Type=oneshot
User=kullanici
EnvironmentFile=/home/kullanici/ozet.env
ExecStart=/usr/bin/python3 /home/kullanici/izleme.py
/etc/systemd/system/izleme.timer:
[Unit]
Description=Her 2 dakikada izleme
[Timer]
OnBootSec=2min
OnUnitActiveSec=2min
AccuracySec=10s
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now izleme.timer
systemctl list-timers izleme.timer
journalctl -u izleme.service -n 20
OnUnitActiveSec, servisin son etkinleşmesinden itibaren 2 dakika sonra yeniden çalıştırır. ESIK=3 ile birlikte bir sorun yaklaşık altı dakika içinde bildirilir. Bu süreyi ihtiyaca göre ayarlayın: sıkı bir eşik hızlı haber verir ama yanlış alarm üretir; gevşek eşik tersini yapar. Timer ayrıntıları için systemd timer yazısına bakın.
Yazma trafiği
2 dakikada bir durum dosyası yazmak, günde 720 küçük yazma demektir; bu SD kart için fazla değildir, ama dosyayı yalnızca durum değiştiğinde yazmak daha iyidir. Yukarıdaki kaydet her seferinde yazar; bu betiği, durum değişmişse yazacak şekilde (eski ve yeni içeriği karşılaştırarak) iyileştirmek kolaydır. SD kart ömrü için genel ilkeler bu yazıda.
Kendi kendini izleyen bir sistem hatası
Alarm betiğiniz bir hata verip çökerse ne olur? systemctl status izleme.service failed gösterir, ama sizi kimse aramaz. İki önlem alın: servise OnFailure= ile bir bildirim servisi bağlayın ya da günlük özet e-postasında timer'ın son çalışma zamanını (systemctl show izleme.timer -p LastTriggerUSec) raporlayın. İzleme sisteminin kendisinin de gözlemlenmesi gerekir, ama her şeyi sonsuza kadar iç içe geçirmeyin; haftada bir bakmak yeter.
Kontrol listesi
- Her kontrol bir "tamam/değil" sonucu döndürüyor ve istisnaları yakalıyor.
- Alarm yalnızca durum değişiminde (ve ardışık N hatadan sonra) gönderiliyor.
- Düzelme de bildiriliyor.
- Durum dosyası kalıcı bir dizinde ve atomik yazılıyor.
- Betiğin çalıştığı günlükten doğrulanabiliyor.
- Alarmlar gerçekten test edildi: bir servisi durdurup bildirimin geldiğini gördünüz.