Sådan erstattede én GitHub Actions-fil min kundes uptime-overvågning til 150 kr/måned
Du betaler allerede for et distribueret cron-job med indbyggede alarmer. Du peger det bare ikke på den rigtige URL.
En af mine klienter kører en lille bookingapp. Forrige måned gik den ned en lørdag morgen omkring kl. 9. Han fandt ud af det på den måde, ingen ønsker at finde ud af det: en kunde skrev til ham. Da han endelig kom tilbage til en laptop og fik genstartet containeren, var halvanden time gået, og der lå otte sure beskeder.
Mandagen efter spurgte han, om jeg kunne sætte uptime-overvågning op. Han havde kigget på Pingdom og Better Stack og spurgte, om 135 kr om måneden var en fair pris. Jeg sagde han skulle beholde sine 135 kr og gav ham den samme opsætning, jeg bruger til mine egne apps.
Hvorfor jeg ikke anbefalede en betalt løsning
Hele hans stack består af én lille Go-API, der snakker med Postgres. To containere på én VPS. Tre brugere på en god dag. Han har ikke brug for et globalt netværk af målepunkter eller en statusside med eget domæne. Han har brug for at vide, lige når API’en holder op med at svare, på en telefon der ikke er hans egen.
Betalte tjenester er gode produkter. De er bare bygget til et andet problem.
| Tjeneste | Billigste plan med alarmer | Årlig pris |
|---|---|---|
| Pingdom | 105 kr/md | 1.260 kr/år |
| UptimeRobot Pro | 50 kr/md | 590 kr/år |
| Better Stack | 135 kr/md | 1.595 kr/år |
| GitHub Actions | 0 kr | 0 kr |
GitHub-rækken er ikke et trick. Hvis du pusher kode til GitHub, har du allerede en gratis, distribueret cron-runner stående med mail-alarmer indbygget. De fleste opdager bare ikke, at de gerne må pege den på produktion.
Filen
Her er hele workflowet. Læg det i .github/workflows/uptime.yml i et repo, du ejer.
name: Uptime check
on:
schedule:
- cron: "*/10 * * * *"
workflow_dispatch:
jobs:
ping:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Hit healthcheck
run: |
curl --fail --silent --show-error --max-time 10 \
https://api.example.com/healthz
- name: Notify on failure
if: failure()
run: |
curl -X POST -H "Content-Type: application/json" \
-d '{"content":"🚨 api.example.com healthcheck failed"}' \
${{ secrets.DISCORD_WEBHOOK }}
22 linjer inklusive blanke. Lad mig gennemgå de dele, der betyder noget.
cron: "*/10 * * * *" kører jobbet hvert tiende minut. Du kan gå ned til fem minutter, men GitHub siger lige ud, at planlagte workflows kan blive forsinket i perioder med høj belastning. Ti minutter giver en god margin, hvor en forsinket cyklus stadig betyder, du hører om nedbruddet inden for tyve minutter.
curl --fail er den bærende flag. Uden --fail printer curl fejlsiden og afslutter med 0, jobbet bliver grønt, du hører intet, og du finder ud af det fra en kunde igen. Med --fail afsluttes ethvert ikke-2xx-svar med fejlkode, og jobbet falder.
--max-time 10 betyder at en hængende server (TCP åben, svarer aldrig) udløser alarmen i stedet for at timeoute hele jobbet. Justér hvis dit healthcheck er langsomt.
if: failure() er alarmen. GitHub sender allerede en mail til repo-ejeren, når et workflow fejler, hvilket er nok for de fleste. Hvis du vil have noget mere højlydt, så peg det andet trin på et Discord- eller Slack-webhook, som jeg gør ovenfor. Push-notifikationer på telefonen, ingen mail-regler at pille ved.
workflow_dispatch lader dig trykke “Run workflow” i Actions-fanen, når du vil bekræfte at alarmstien stadig virker. Test den én gang i kvartalet ved at pege den på en URL, der returnerer 500. Det er det eneste stykke overvågning, jeg stoler på, fordi jeg faktisk har set den hive mig ud af sengen.
Healthcheck-endpointet
Et healthcheck, der kun returnerer 200 hvis processen lever, er værdiløst. At processen lever, fortæller systemd dig allerede. Du vil have et healthcheck, der pinger de ting, appen rent faktisk afhænger af.
For bookingappen var det Postgres. Endpointet er femten linjer Go:
func healthz(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
http.Error(w, "db unreachable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
}
Samme mønster i Node (await pool.query('SELECT 1')), Python (cursor.execute('SELECT 1')) eller hvad du nu kører. Hvis DB’en er den eneste kritiske afhængighed, er det alt, hvad du behøver. Har du en kø eller et eksternt API, appen ikke kan undvære, så læg et hurtigt tjek på dem også. Lad være med at overdrive: hvert tjek er noget, der kan give dig et falsk positivt kl. 4 om natten.
Det samme GitHub Actions-cron-trick brugte jeg til scraperen jeg byggede til en censor-klient. Det viser sig at “gratis planlagt job, der kører din kode og mailer dig, når der sker noget” dækker overraskende meget af det, små virksomheder betaler servere for.
Hvad den ikke kan
Ærlig liste. Når man lader som om noget andet, er det sådan man ender med at stole på det forkerte værktøj.
- GitHub Actions-cron er best-effort, ikke garanteret. Planlagte workflows kan blive forsinket 15+ minutter ved høj belastning. Til et hobbyprojekt er det fint. Til en betalt SLA, find en rigtig monitor.
- Ingen alarmer hvis GitHub selv ligger ned. Sjældent, men det sker. Hvis GitHub går mørkt samtidig med din app, hører du om det fra dine kunder.
- Ingen statusside, ingen incident-tidslinje, ingen vagtrotation. Det her er et ping med en mail. Hvis du har brug for ceremonien rundt om, er du allerede ude over, hvad opsætningen er til.
- Ingen regionale målepunkter. Du tester fra hvor GitHubs runner nu lige spinnede op. Hvis du specifikt går op i, om dit site kan nås fra Asien, er det her ikke nok.
- Fem minutter er den praktiske bund. GitHub planlægger cron, det lover ikke præcision på under et minut. Skal du opdage nedbrud på sekunder, er du i et andet værktøj.
Har du ikke nogen af de behov, så er der heller ikke noget at betale for.
Hvad koster det at køre
Seks tjek i timen, 24 timer, 30 dage: ca. 4.320 kørsler om måneden. Hver tager omkring 15-30 sekunder. Lad os sige 30 for at være på den sikre side. Det er cirka 36 minutters compute om måneden.
GitHub giver dig 2.000 gratis minutter om måneden på private repos. Ligger workflowet i et public repo, er det gratis uden loft. Uanset hvad bruger du under 2 % af kvoten. Du skulle sætte uptime-overvågning på tyve flere services, før det overhovedet blev en post i regnskabet.
Kører dit projekt allerede andre workflows, og er du nervøs for budgettet, så sæt cron til hvert 15. minut. Så er du nede på 24 minutter om måneden, og du slår stadig ethvert menneske på reaktionstid til et nedbrud kl. 9 om morgenen.
Hvornår skal du ringe til mig
Jeg sætter det her op for klienter som en del af min vedligeholdelse, sammen med resten af den kedelige, men bærende type arbejde (backups der faktisk kan genskabes, logrotation der faktisk roterer, alarmer der ringer til den rigtige person). Hvis du hellere vil slippe for at røre ved YAML’en, eller vil have en til at tænke healthcheck-endpointet ordentligt igennem fra start, så skriv til mig.
Selve opsætningen er 22 linjer og en eftermiddag. Det svære er at vide hvad der skal tjekkes, hvad der er værd at blive vækket for, og at stole på alarmen, når den endelig går. Det er den del, der tager tid.
– Christian