# Hälsokontroll

## Vad det är

En hälsokontroll är ett litet test som upprepas enligt ett schema och frågar en
körande tjänst om den kan göra sitt jobb, tillsammans med en regel för vad som
händer när svaret är nej. Testet kan vara en HTTP-förfrågan till en särskild
sökväg, ett kommando som körs inuti en container eller ett försök att öppna en
TCP-port. Det är regeln som gör kontrollen till mer än en mätning: en misslyckad
hälsokontroll kan ta en server ur rotation, starta om en container, stoppa en
driftsättning eller väcka någon mitt i natten.

Docker bygger in idén i själva imagen. Instruktionen `HEALTHCHECK` i en
Dockerfile anger ett kommando som Docker kör inuti containern med ett fast
intervall. Slutkod 0 betyder att containern är frisk, 1 att den inte är det, och
2 är reserverad och får inte användas. En container med en kontroll börjar i
tillståndet `starting`, blir `healthy` så snart en kontroll går igenom och
övergår till `unhealthy` efter ett bestämt antal misslyckanden i rad.
Standardvärdena är en kontroll var 30:e sekund, en tidsgräns på 30 sekunder och
tre försök; en körning som överskrider sin tidsgräns räknas som ett
misslyckande, och dess process avslutas med tvång. En valfri startperiod ger ett
långsamt program tid att starta, och misslyckanden under den räknas inte. En
Dockerfile har en sådan instruktion, den sista gäller om det finns flera, och
`HEALTHCHECK NONE` stänger av en kontroll som ärvts från basimagen.

Kubernetes delar upp frågan i tre prober, var och en med sin egen konsekvens. En
startup-prob frågar om programmet har startat klart; tills den lyckas väntar de
två andra, och om den fortsätter att misslyckas avslutas containern och hanteras
enligt sin omstartspolicy. En liveness-prob frågar om containern ska startas om
och är avsedd för tillstånd som ett dödläge (deadlock), där processen kör men
inte kommer framåt. En readiness-prob frågar om containern ska få trafik. När
den misslyckas startas ingenting om; i stället tas Podens adress bort från
endpoints för varje Service som väljer den, och förfrågningarna går till de
andra replikerna tills proben går igenom igen.

Varje prob använder en av fyra mekanismer. En `httpGet`-prob går igenom på varje
statuskod från och med 200 till och med 399. En `tcpSocket`-prob går igenom om
porten tar emot en anslutning, även en som stängs direkt. En `grpc`-prob går
igenom när tjänstens hälsokontroll i gRPC svarar `SERVING`. En `exec`-prob kör
ett kommando i containern och går igenom på slutkod 0. Om inget annat anges
kontrollerar Kubernetes var tionde sekund, väntar en sekund på svar och agerar
efter tre misslyckanden i rad.

Lastbalanserare lägger till en andra sort. Kontrollerna ovan är aktiva: något
skickar en testförfrågan oavsett om det finns användare eller inte. En passiv
kontroll följer i stället den verkliga trafiken. Så fungerar nginx med öppen
källkod: när en förfrågan till en upstream-server slutar med ett fel markerar
nginx servern som felande och undviker den en stund, och med standardvärdet 1
för `max_fails` räcker ett enda misslyckat försök. Enligt dokumentationen för
nginx hör hälsokontroller av applikationer (application health checks) till
funktionerna i de betalda NGINX Plus-prenumerationerna. En passiv kontroll
kostar inget extra, men den får veta att en server är trasig genom en användares
misslyckade förfrågan, och den säger ingenting om en server som inte får någon
trafik.

## Varför det spelar roll

En process som kör är inte detsamma som en tjänst som fungerar. Dockers referens
nämner det typiska fallet: en webbserver som har fastnat i en oändlig loop och
slutat ta emot anslutningar medan processen fortfarande lever. Allt som bara
håller koll på om processen finns, till exempel en
[systemd](/sv/glossary/#systemd)-enhet med omstartspolicy, ser inget fel. Bara
en fråga som ställs genom samma dörr som användarna går in genom kan se
skillnaden, och det är hela poängen med en hälsokontroll: den byter ut ”kör
den?” mot ”svarar den?”.

Två beslut avgör om en kontroll är nyttig eller farlig. Det första är vad
kontrollen testar, vilket i praktiken är en definition av ”fungerar”, skriven i
kod. En hanterare som returnerar 200 utan att röra något bevisar att
webbramverket är uppe; en hanterare som också frågar databasen bevisar att
databasen går att nå, och misslyckas när den inte gör det. Det andra är vad som
händer vid ett misslyckande, och det avgör hur långt skadan når. Att ta en
replik ur rotation är billigt och går att ångra. Att starta om den kastar bort
det arbete den höll på med och dess varma cachar, och om alla repliker
misslyckas med samma kontroll samtidigt gör en omstart av alla ett långsamt
beroende till ett totalt driftstopp.

Kubernetes varnar för just detta. Dokumentationen säger att liveness-prober
måste ställas in så att de verkligen visar ett fel som programmet inte kan
återhämta sig från på egen hand, och att en felaktig implementering kan orsaka
kaskadfel: containrar som startas om under hög belastning, klientförfrågningar
som misslyckas och mer arbete för de poddar som är kvar. Rådet som följer är att
hålla isär de två frågorna. När ett program är strikt beroende av bakomliggande
tjänster ska dess liveness-prob gå igenom så länge programmet självt är friskt,
medan readiness-proben också kontrollerar varje nödvändig tjänst bakom. Då
undviker trafiken en Pod som bara kunde ha svarat med fel, och Poden dödas inte
för ett fel som ligger någon annanstans.

Hälsokontroller kommer också [övervakningen](/sv/glossary/#monitoring) till
godo. En endpoint som en lastbalanserare frågar kan också frågas utifrån, och
det larm som går när den fallerar är ofta det första tecknet på en
[incident](/sv/glossary/#incident). När Irakli Nadareishvili skrev Health Check
Response Format for HTTP APIs, ett Internet-Draft inom IETF, fanns det ingen
standard för vad en sådan endpoint ska returnera, och de flesta program nöjde
sig med HTTP-statuskoden eller använde ett eget format. Utkastet föreslog ett
JSON-svar med medietypen `application/health+json` och ett obligatoriskt
`status`-fält med värdet `pass`, `fail` eller `warn`, där `pass` och `warn` hör
ihop med en statuskod i 2xx eller 3xx och `fail` med en i 4xx eller 5xx. Den
sista versionen, från oktober 2021, löpte ut i april 2022, och IETF anger att
den varken är godkänd av IETF eller har någon formell status. Den är användbar
som ett gemensamt ordförråd, inte som en standard.

## Så ser det ut i praktiken

I en Dockerfile följer kontrollen med den image den kontrollerar. Här frågar
programmets eget lilla probprogram efter en sökväg på localhost och avslutar med
0 eller 1:

```dockerfile
HEALTHCHECK --interval=15s --timeout=3s --start-period=60s --retries=3 \
  CMD ["/app/bin/probe", "http://127.0.0.1:8080/livez"]
```

Docker kör proben var 15:e sekund och ger varje körning tre sekunder.
Misslyckanden under den första minuten räknas inte, om inte en kontroll redan
har gått igenom, och i Docker Engine 25.0 eller senare körs kontrollerna under
den minuten var femte sekund, vilket är standardintervallet under startperioden,
så att en snabb start märks snabbt. Efter tre misslyckanden i rad markeras
containern som `unhealthy`. De första 4096 byten av probens utdata sparas för
`docker inspect`, och varje statusändring skickar ut en
`health_status`-händelse. Referensen beskriver ett tillstånd, inte en åtgärd:
något annat måste agera på det.

I Kubernetes kan de tre proberna få var sin sökväg, så att var och en ställer
sin egen fråga:

```yaml
containers:
  - name: api
    ports:
      - containerPort: 8080
    startupProbe:
      httpGet: { path: /livez, port: 8080 }
      periodSeconds: 5
      failureThreshold: 24 # upp till 2 minuter att starta
    livenessProbe:
      httpGet: { path: /livez, port: 8080 }
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 6 # ungefär en minut före omstart
    readinessProbe:
      httpGet: { path: /readyz, port: 8080 }
      periodSeconds: 5
      timeoutSeconds: 2
      failureThreshold: 2
```

`/livez` svarar bara för själva processen, medan `/readyz` också kontrollerar
databasen och allt annat som en förfrågan inte klarar sig utan. Startup-proben
använder liveness-sökvägen, som dokumentationen för Kubernetes rekommenderar,
och ger 24 × 5 sekunder innan containern ges upp. När den har gått igenom tar
liveness över med lång stubin och readiness med kort: ungefär tio sekunder av
misslyckanden tar Poden ur Servicen, och en minut av dem startar om den.
Standardvärdena skulle ge tre misslyckanden med tio sekunders mellanrum, och det
är för lite för ett program som behöver två minuter för att läsa in sina data.
Dokumentationens egen tumregel är att en container som vanligtvis tar längre tid
att starta än initialDelaySeconds plus failureThreshold × periodSeconds behöver
en startup-prob.

En hälsokontroll kan också vändas ut och in. I
[värdövervakning med dödmansgrepp](/sv/portfolio/built-dead-mans-switch-host-monitoring/),
från september 2026, kontrollerar en värd på 464 MB som kör en git-forge, en
databas och en webbserver sig själv var femtonde minut, med slumpmässig
förskjutning: ett litet skript mäter tillgängligt minne och ledigt diskutrymme
och pingar en extern tjänst bara om båda ligger över sina tröskelvärden.
Tystnaden är larmet. En värd som har fått slut på minne, tappat nätverket eller
slutat starta ger samma signal som en som bara inte är frisk, och därför valdes
den här formen framför en agent som rapporterar en status. Kontrollen läser
tillgängligt minne och inte ledigt minne, eftersom kärnan använder outnyttjat
minne som cache, och sex variabelnamn som sa ”free” rättades, så att ingen
skulle ”laga” ett fungerande larm till ett som löser ut permanent. Sidan är lika
tydlig med begränsningen: dödmansgreppet bevisar att maskinen är frisk, inte att
webbplatsen svarar.

En kontroll vid uppstart besvarar ännu en fråga. I
[e-post med failover och leveransloggning](/sv/portfolio/built-email-with-failover-and-delivery-logging/),
från 2025, går posten genom tre leverantörer med automatisk failover, och
framför alltihop skickar en kontroll vid uppstart ett verkligt meddelande genom
hela stacken, bakom en flagga. I utveckling loggar ett misslyckande en varning
och programmet fortsätter; i staging och produktion är felet fatalt, och
processen avslutas i stället för att ett bygge som inte kan skicka e-post
driftsätts. Sidan sammanfattar resultatet så här: en hel kategori av tysta fel
flyttades från ”en användare märker det dagar senare” till ”driftsättningen
stoppas”.

## Var det går fel

**Kontrollen bevisar bara att processen svarar.** En hanterare som returnerar
200 utan att röra något förblir grön medan databasen inte går att nå, disken är
full eller varje verklig förfrågan slutar med ett fel. Låt readiness-kontrollen
pröva det en förfrågan behöver, på ett billigt sätt, och lägg till en kontroll
utifrån som följer användarens väg genom DNS, TLS och lastbalanseraren, eftersom
kubelet skickar sina prober direkt till Podens egen IP-adress och inte ser något
av det.

**Liveness beror på något utanför containern.** Om liveness-proben frågar
databasen får en långsam databas den att misslyckas i alla repliker på en gång,
och Kubernetes startar om dem alla precis när tjänsten redan har det svårt. Låt
liveness bara svara för själva processen, som dokumentationen för Kubernetes
råder, och lägg beroendena i readiness, där ett misslyckande leder bort trafiken
i stället för att döda containrar.

**Tidsinställningarna passar inte programmet.** Kubernetes väntar som standard
en sekund på svar, så en endpoint som blir långsammare under belastning
misslyckas medan programmet bara har mycket att göra, och en container som
startar långsammare än dess liveness-prob tillåter dödas innan den är klar, om
och om igen. Mät hur lång tid endpointen tar under belastning och sätt
tidsgränsen över det, och ge långsamma program en startup-prob som är
dimensionerad efter den långsammaste verkliga starten.

**En omdirigering går igenom.** En HTTP-prob i Kubernetes räknar varje status
från 200 till 399 som en framgång, så en sökväg som omdirigerar till en
inloggningssida ser frisk ut. Pekar omdirigeringen till en annan värd följer
kubelet den inte, utan registrerar proben som lyckad med en varning. Med HTTPS
hoppar kubelet dessutom över certifikatkontrollen, så ett utgånget certifikat
får aldrig en prob att misslyckas. Rikta proberna mot en sökväg som svarar 200
direkt och returnerar en 5xx när något är fel, och kontrollera certifikaten
utifrån.

**Endpointen säger för mycket till för många.** Ett detaljerat svar som räknar
upp varje beroende, version och värdnamn informerar en angripare lika väl som en
lastbalanserare, och säkerhetsavsnittet i utkastet påpekar att endpoints för
hälsokontroller kan behöva autentisering och rollbaserad åtkomstkontroll.
Begränsa det offentliga svaret till en status, och visa detaljerna bara på en
intern port eller för autentiserade klienter.

**Kontrollen kostar mer än den borde.** Kubernetes varnar för att `exec`-prober
startar nya processer vid varje körning, vilket på tätt packade noder med korta
intervall kan öka CPU-belastningen på noden, och en prob som kör en tung fråga
med några sekunders mellanrum på varje replik är en belastning i sig. Föredra
HTTP-, TCP- eller gRPC-prober, håll endpointen billig och cacha resultatet av en
dyr beroendekontroll i några sekunder i stället för att upprepa den för varje
prob.

## Läs mer

- [Dockerfile reference: HEALTHCHECK](https://docs.docker.com/reference/dockerfile/#healthcheck)
  — instruktionen, dess alternativ och standardvärden och vad varje slutkod
  betyder.
- [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/)
  — vad varje probtyp gör, de fyra kontrollmekanismerna och alla
  konfigurationsfält med sina standardvärden.
- [Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
  — genomgångna exempel, bland annat en startup-prob som ger ett långsamt
  program fem minuter.
- [Health Check Response Format for HTTP APIs](https://datatracker.ietf.org/doc/draft-inadarei-api-health-check/)
  — det utgångna Internet-Draftet bakom `application/health+json` och dess
  statusvärden pass, fail och warn.
- [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html)
  — de passiva hälsokontrollerna i nginx med öppen källkod, som sker i själva
  trafiken (in-band), och inställningarna `max_fails` och `fail_timeout`.

<https://engineer.company/sv/glossary/health-check/>
