Ville spare penger – erstatta NodePing med 1.350 linjer egen kode

- Jeg mener man ofte abonnerer på langt flere tjenester enn man trenger, skriver Kirill Miazine.

Kirill Miazine, jurist og utvikler, ville kutte ned på utgiftene, og kvitte seg med NodePing. Det viste seg å gå fint å kode sin egen tjeneste i stedet. 📸: Privat / kode24
Kirill Miazine, jurist og utvikler, ville kutte ned på utgiftene, og kvitte seg med NodePing. Det viste seg å gå fint å kode sin egen tjeneste i stedet. 📸: Privat / kode24 Vis mer

En hobby er noe man gjerne bruker penger på, og har man teknologi som en hobby, som bruker man gjerne penger på det.

Dårlig kronekurs, stadig økende teknologikostnader og stadig økende renter gjorde at jeg ville gjøre noe med IT-budsjettet mitt: Det var flere greier der jeg ikke trengte, og det var flere greier der jeg kunne erstatte.

Det var likevel én linje der som plaget meg litt lenge, fordi posten på en måte var uerstattelig: Abonnement på nettverk-sovervåkningstjenesten NodePing, som jeg har abonnert på siden september 2015.

Den har fungert veldig bra og hadde det meste av funksjonaliteten jeg trengte. Men den hadde ikke alt, og jeg hadde ikke noe å erstatte den med.

Ingen tekniske lån

Jeg mener at man ofte abonnerer på langt flere tjenester enn man trenger, og at man kan kutte ut – eller erstatte – en del av disse.

Mitt romjulsprosjekt ble derfor å erstatte gode gamle NodePing for meg selv. De metaforiske fluene som ett slikt slag ville slå i én smekk ville være lavere kostnader, bedre funksjonalitet, kompetanseutvikling og helt sikkert en del andre buzzwords.

Jeg måtte imidlertid passe meg for at ikke forsøk på å redusere kostnader ville medføre et opptak av teknisk lån. Det er nemlig lett å tro at man «bare» kan kode noe selv, men man må jo ikke glemme at dette også skal holdes ved like og oppdateres når det skjer endringer i teknologien man bruker.

Teknologivalget måtte derfor være kjedelig og valget falt på slangespråket: Greia skulle få det lite kreative navnet Pyng, og altså skrives i Python.

Python 3.11 og eksterne verktøy

I starten var tanken å bruke Twisted, som jo har ganske mye i sin verktøykasse. Ganske fort bestemte jeg meg likevel for å gjøre alt jeg trenger med det som finnes i standardbiblioteket til Python 3.11, for det har jo alt som trenges for å orkestrere kjøring av eksterne prosesser med noen arbeidere og et par-tre køer. Det måtte bli 3.11, fordi det var der Task Groups kom.

Resultatet så langt ble ganske bra, altså: Jeg har fått til et system for nettverksovervåkning uten å skrive en eneste linje nettverkskode. For alt av sjekker bruker jeg nemlig eksterne verktøy: ping, netcat, curl, drill.

På denne måten får jeg dratt nytte av funksjonaliteten og robustheten i eksisterende verktøy, uten å gå i gang med forsøkene på å lage enda et hjul.

Foruten å sjekke om noe er oppe, kan systemet sjekke om endringer i resultatene eller om noe tar mer tid enn forventet. I alle tilfeller kan det bli sendt ut varsler: Jeg begynte med Matirx og SMS, men nå er det også Pushover, Mattermost, IRC og XMPP, og naturligvis e-post. Jeg hadde ikke trodd at curl kan brukes til å snakke SMTP, men det kan den altså.

«Resultatet så langt ble ganske bra, altså.»

1.350 linjer kode

Jeg fikk altså en del funksjoner som ikke NodePing hadde: Bli varslet ved endringer i resultatene, mulighet for å sjekke utløpsdato på DNSSEC-signaturer, bli varslet om kjøretiden for sjekkene går opp betraktelig.

Hvis noe ser ut til å være nede for systemet hvor Pyng normalt kjører (en virtuell maskin hos Hetzner i Helsniki) kjøres det en kontrollsjekk fra et annet sted før varselet går ut. Kontrollsjekken benyttet mitt allerede eksisterende opplegg for å ta imot webhooks, det trengtes bare en sikker måte å formidle informasjon om at det skulle kjøres en sjekk og sende tilbake resultatene – det var ganske enkelt med pickle og HMAC.

Ytelsen er helt grei: Jeg kjører normalt OpenBSD, der fikk jeg til omtrent 200 sjekker per sekund under stresstesten, noe som holder i massevis for meg. Et lite forsøk på Alpine Linux firedoblet dette til over 800 sjekker per sekund.

Uten sjekker er Pyng på omtrent 1.350 linjer kode. Per nå har jeg litt over 300 sjekker, som defineres i Python. Det var deilig å sitte lange romjulskvelder i tmux med vi, docs.python.org og noen man-sider for å finne ut av hvilke parametere forskjellige kommandoer ønsket å ha. Overvåkningen startes av og kjøres i tmux, og den måten å starte langtidskjørende programmer på kan få et eget innlegg etter hvert. Ja, jeg mener at tmux er meget godt egnet for å kjøre dæmoner.

Sånn ser sjekken ut

Et kode24-innlegg blir ikke fullstendig uten noe kode. Så la meg vise hvordan en sjekk ser ut.

Her er et eksemplar som kontrollerer at den lokale IRC-serveren er oppe, og varsler meg på IRC og Matrix hvis lokal server er nede av en eller annen grunn:

TCP('127.0.0.1', 6667,
   data='CAP LS 302\r\n',
   result=':stable.krot.org',
   alert=[km_irc_alert, km_mx_alert],
   desc='ngIRCd (stable)'),

Her er en annen, unødvendig sofistikert sjekk for å kontrollere at det ikke er noe tull med DNSSEC:

DNS('ping.krot.org', 'txt',
    dnssec=True,
    result=lambda x: True if any(['pong' in y for y in x]) else False,
    alert=[km_irc_alert, km_mx_alert],
    desc='ping.krot.org (lambda)'),

result-parameteret brukes for å sjekke resultatene, og den kan gjøre ganske mye uten å ta i bruk spesialbygde funksjoner, som lambda i dette tilfelle.

Sjekkene kan gjøre diverse greier når det skjer diverse ting, for eksempel når sjekken kjører første gang, eller når en sjekk får resultater eller når det skjer endringer.

All slik funksjonalitet er laget ved hjelp av hooks, som gjør at det er mulig å få til ganske mye hvis en spesiell sjekk trenger helt ekstraordinær spesiell funksjonalitet:

async def _apply_hook(state, check=None, res=None, diff=None):
    states = ['result', 'first', 'up', 'down', 'change']
    if state in states and (hook := getattr(check, f'on_{state}', None)):
    try:
        await hook(check, res, diff) if state == 'change' else hook(check, res)
    except:
        print('!!! apply hook exception:', sys.exception())

Quiz: ser du en hvalross gjemme seg der i kodesnutten?

«Jeg synes forsøket har vært vellykket.»

Savner ikke NodePing

Pyng brukes primært hos meg, og er også deploya i en nedstrippet, enfils-versjon hos noen venner til å sjekke at en spesialbygget autentiseringsløsning virker og ikke bruker alt for lang tid på å svare. Så langt har Pyng klart å oppdage ytelsesproblemene før standardovervåkningen slår til.

Jeg er nesten i mål med hvilken funksjonalitet jeg trenger før jeg anser prosjektet som sluttført: det som mangler er en mulighet for å beskrive at det skal skje en dialog i en sjekk, eksempelvis det som skjer i en SMTP-sesjon.

Så langt er funksjonalitet begrenset til å lese noe, eller sende noe og så lese noe: Jeg må finne en elegant måte å beskrive på hvordan en slik dialog kan foregå. Det finnes selvsagt et alternativ i å lage noe eksternt som er i stand til å håndtere dialogen og gi et ferdig svar tilbake, men jeg håper jeg får til noe direkte i Pyng, kanskje noe som likner litt på expect.

Jeg synes forsøket har vært vellykket, og jeg har fått erstattet en ekstern tjeneste med hjemmesnekret løsning basert på fri programvare. Jeg savner ikke NodePing.