all

Case study

NGINX 502 greske sa jednog load balancera: kako je segfault izazvao trajne upstream neuspehe

Klijent je prijavio neobican problem u produkciji: zahtevi usmereni kroz jedan load balancer povremeno su vracali 502 Bad Gateway, dok su drugi cvorovi u istom klasteru radili normalno.

Sami backend servisi izgledali su zdravo.

Health checkovi su prolazili. Direktni zahtevi sa pogođenog hosta ka upstream serverima su radili. Mreza je izgledala normalno.

Ipak je NGINX na jednom cvoru nastavljao da vraca 502 odgovore.

Neposredni popravak se ispostavio kao jednostavan:

1
systemctl restart nginx

Vazniji deo bio je da se razume zasto je restart to popravio, i da se obezbedi da isti kvar vise ne moze da ostane neprimecen.

Simptomi

Infrastruktura je otprilike izgledala ovako:

  flowchart LR
    C[Clients] --> LB1[NGINX node 1]
    C --> LB2[NGINX node 2]
    C --> LB3[NGINX node 3]

    LB1 --> APP[Backend pool]
    LB2 --> APP
    LB3 --> APP

    LB1 -. "502 errors" .-> C

Pogođen je bio samo jedan load balancer.

NGINX logovi na tom cvoru sadrzali su upstream timeout greske, sto je ukazivalo na problem sa backendom ili mrezom.

To je u pocetku kvar cinilo jednostavnim:

1
2
3
4
5
NGINX
upstream timeout
problem sa backendom/mrezom?

Ali dokazi nisu podupirali taj zakljucak.

Sa pogođenog hosta, konekcije ka istim backend serverima su uspevale:

1
curl -v http://backend.example.internal/health

Health checkovi su takodje prolazili.

Ako bi upstream aplikacija ili mrezna putanja zaista bili nedostupni, ocekivali bismo greske sa vise od jednog NGINX cvora.

Ta razlika postala je najkorisniji dijagnosticki signal.

Sta se zapravo dogodilo

Kernel logovi su otkrili drugi dogadjaj oko trenutka kada je problem poceo:

1
nginx[24317]: segfault at 8 ip 00000000004602b1 sp 00007ffe0a8183f0 error 4

Pad se dogodio dok je izvrsavana rotacija NGINX logova.

Posle segfaulta, pogođena NGINX instanca ostala je u degradiranom stanju. Konekcije su se gomilale u njenoj listening/backlog putanji umesto da budu normalno obradjene.

Spolja je to proizvelo obmanjujucu kombinaciju simptoma:

  • host je bio dostupan;
  • backend serveri su bili dostupni;
  • backend health checkovi su uspevali;
  • sam NGINX je i dalje postojao;
  • ali zahtevi kroz ovaj konkretan load balancer su isticali i vracali 502.

Restart NGINX-a je ponovo kreirao njegovo runtime stanje i odmah vratio normalnu obradu zahteva.

  sequenceDiagram
    participant C as Client
    participant N as Affected NGINX
    participant B as Backend

    C->>N: HTTP request
    N--xB: Request processing stalls
    N-->>C: 502 / upstream timeout

    Note over N: NGINX had previously segfaulted

    B->>B: Backend remains healthy

    Note over N: systemctl restart nginx

    C->>N: HTTP request
    N->>B: Forward request
    B-->>N: 200 OK
    N-->>C: 200 OK

Restart je resio incident.

Nije resio operativnu slabost koja je omogucila da incident ostane neprimecen.

Zasto health checkovi nisu bili dovoljni

Tradicionalni monitoring zdravlja je proveravao da li:

  • NGINX radi;
  • server je dostupan;
  • upstream servisi odgovaraju;
  • application health endpointi vracaju success.

Sve te provere mogle su da prodju tokom ovog incidenta.

Ono sto je nedostajalo bilo je pracenje stvarnog ishoda saobracaja.

Load balancer koji vraca neobicno visoku stopu 5xx odgovora nije zdrav iz korisnicke perspektive, cak i ako proces postoji i svaki backend health check je zelen.

Za to je bio potreban drugi sloj monitoringa.

Alert za neobicnu NGINX upstream 5xx stopu

Okruzenje je vec izlagalo NGINX VTS metrike ka Prometheusu.

Korisni signal je:

1
2
3
4
5
6
7
sum(
  rate(
    nginx_vts_upstream_requests_total{
      code="5xx"
    }[5m]
  )
) by (instance, upstream)

Umesto da se gleda samo ukupan zbir za ceo klaster, vazno je zadrzati instance u agregaciji.

U ovom incidentu, globalna metrika je mogla da prikrije kvar jer je bio pokvaren samo jedan load balancer.

Na primer:

1
2
3
node-1    0.1 5xx/s
node-2   27.4 5xx/s
node-3    0.0 5xx/s

Klaster kao celina i dalje bi mogao da izgleda uglavnom funkcionalno, dok bi svaki klijent koji pogodi node-2 vidjao greske.

Prometheus alert moze to da otkrije direktno:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
additionalPrometheusRules:
  - name: nginx
    groups:
      - name: nginx.rules
        rules:
          - alert: NginxUpstream5xxRateHigh
            expr: |
              sum(
                rate(
                  nginx_vts_upstream_requests_total{
                    code="5xx"
                  }[5m]
                )
              ) by (instance, upstream) > 20
            for: 5m
            labels:
              severity: warning
            annotations:
              summary: "Visoka NGINX upstream 5xx stopa na {{ $labels.instance }}"
              description: >-
                NGINX na {{ $labels.instance }} vraca vise od
                20 upstream 5xx odgovora u sekundi za upstream
                {{ $labels.upstream }}.

Tacan prag zavisi od normalnog obima saobracaja, ali princip je vazniji od broja:

Alert po load-balancer instanci, a ne samo na agregiranom servisu.

To bi brzo identifikovalo cvor koji otkazuje cak i bez ikakvog znanja o segfaultu.

Otkrivanje osnovnog uzroka iz Linux kernel logova

Drugi sloj monitoringa cilja sam kvar.

Linux belezi segmentation faultove aplikacija u kernel logovima, koji su obicno vidljivi kroz dmesg, journalctl -k, /var/log/messages, ili /var/log/syslog, u zavisnosti od distribucije.

Na primer:

1
grep -E 'nginx.*segfault' /var/log/messages

Ako je Monit vec instaliran, moze da nadgleda kernel log i reaguje na NGINX segfault.

1
2
3
4
5
6
check file kernel_messages with path /var/log/messages
    ignore content = "monit\[[0-9]+\]"

    if content = "nginx\[[0-9]+\]: segfault at [0-9a-f]+" then alert

    if content = "nginx\[[0-9]+\]: segfault at [0-9a-f]+" then exec "/usr/bin/systemctl restart nginx"

Proverite konfiguraciju pre ponovnog ucitavanja Monit-a:

1
monit -t

Zatim ga ponovo ucitajte:

1
monit reload

Ovo obezbeđuje dve stvari koje su nedostajale tokom originalnog incidenta:

  1. eksplicitni alert da je NGINX pao;
  2. automatski oporavak pogođenog cvora.

Servis vise ne sme da ostane neograniceno u delom pokvarenom stanju koje smo posmatrali.

Dva signala su bolja od jednog

Krajnji dizajn monitoringa namerno otkriva incident iz dva razlicita pravca.

  flowchart TD
    S[NGINX failure]

    S --> U[User-visible symptom]
    S --> K[Kernel-level symptom]

    U --> P[Prometheus<br/>NGINX 5xx rate]
    K --> M[Monit<br/>segfault detection]

    P --> A[Alertmanager alert]
    M --> A
    M --> R[Restart NGINX]

    R --> OK[Traffic restored]

Prometheus pravilo odgovara na pitanje:

Da li ovaj load balancer zaista ispravno opsluzuje saobracaj?

Monit pravilo odgovara na pitanje:

Da li je NGINX proces pao na nacin koji zahteva odmah oporavak?

Ovo su komplementarne provere.

Monitoring samo segfaulta bi propustio druge uzroke povecanih 502 odgovora.

Monitoring samo 5xx stopa bi otkrio problem, ali ne bi objasnio niti automatski oporavio ovaj konkretan kvar.

Zasto su prvobitne dijagnostike bile varljive

Najzbunjujuce u ovom incidentu bilo je to sto je NGINX prijavljivao upstream timeoutove, dok je direktna upstream konekcija radila.

Bilo bi lako potrositi znatno vise vremena na istrazivanje:

  • firewall pravila;
  • pracenja konekcija;
  • zasicenja backenda;
  • DNS-a;
  • rutiranja;
  • TCP timeouta;
  • stanja aplikacije.

To su bile razumnе stvari za proveru, ali najjaci trag je bio da je problem pratio jedan load-balancer cvor, a ne backend servis.

Kada se samo jedan cvor inace identicnog klastera ponasa drugacije, rano proverite lokalno stanje:

1
2
3
4
5
6
journalctl -k
journalctl -u nginx
dmesg
ss -s
ss -lnt
ps auxf

Proces koji radi ne znaci nuzno i servis koji ispravno funkcionise.

Rezultat

Restart NGINX-a je odmah vracao saobracaj na pogođenom cvoru.

Trajno poboljsanje nije bio sam restart.

Dodali smo monitoring na oba nivoa:

  • Prometheus i Alertmanager otkrivaju neobicnu upstream 5xx stopu nezavisno za svaki NGINX cvor;
  • Monit otkriva NGINX segmentation faultove u kernel logovima, salje alert i automatski restartuje servis.

Isto pravilo kvara zato treba da bude detektovano u roku od nekoliko minuta i, za poznati segfault scenario, da se oporavi automatski.

Sira lekcija vazi mnogo sire od NGINX-a:

Health checkovi infrastrukture treba da mere i to da li je komponenta ziva i to da li i dalje proizvodi ocekivani rezultat.

Proces moze da radi. Njegove zavisnosti mogu da budu zdrave. Mreza moze da radi savrseno.

A servis i dalje moze da bude pokvaren.