HomeO mnie

[fixit] Dygresja o ingressie i błędzie 499

By Robert Duraj
Published in Inne
August 25, 2023
3 min read
[fixit] Dygresja o ingressie i błędzie 499

W ostatnim czasie bardzo dużo się u mnie technologicznie dzieje. Wypływam na różne nowe obszary, co też rodzi sporo sytuacji stykowych, a w konsekwencji: bóle głowy, wyrwane włosy i myśli czarne, jak niebo nad Mordorem.

Wypadkową tych wycieczek w nieznane mi obszary jest pomysł na nową serię: fixit, w której mam zamiar dokumentować ciekawe przypadki napotkanych błędów (z rozwiązaniami, rzecz jasna). Niniejszym otwieram kategorię wpisem o problemach z błędem 499 na nginx w konfiguracji kubernetesowej.

Ratunku, Timeout!

Jakiś czas temu, klienci aplikacji nad którą pracuję, zaczęli dostawać niecodzienne błędy timeoutu na jednej z końcówek. Sytuacja niepożądana, szczególnie, że w większości przypadków oznacza grubsze problemy. Inwestygacja wykazała, że jeden ze skryptów, odpalanych w (nazwijmy to) “mikroserwisie” 3rd-part, zaczął niebezpiecznie puchnąć, przez co konsumował zbyt dużo czasu na wykonanie. Brak dostępu do tej części aplikacji zmusiło nas do tymczasowego podniesienia limitu czasu wykonania… tylko na którym z serwerów?!

Oto, jak mniej więcej prezentowała się infrastruktura, z którą mieliśmy do czynienia:

Kubernetes infrastructure
Kubernetes infrastructure

Jak widać, mamy tutaj dwa gorące miejsca:

1) Ingress Controller do zarządzania ruchem wewnątrz klastra 2) Serwer nignx, który pracuje jako load balancer i steruje ruchem również poza klaster, m.in. do skryptu powodującego timeout.

Powstało pytanie: na którym serwerze mamy problem? Życie przyniosło szybką odpowiedź: na obu.

499 HTTP error 🫣

Oto sytuacja widziana oczami klienta:

  1. Wykonujemy request do https://sample.com/time/consuming/script/abc123
  2. Czekamy powyżej 60 sekund
  3. Dostajemy 504, podpisane przez nginx
  4. Żądamy zwrotu pieniędzy i piszemy negatywną recenzję na Google Maps.

Tymczasem po stronie serwera wygląda to nieco inaczej:

  1. Klient wykonuje request do https://sample.com/time/consuming/script/abc123
  2. Ingress Controller otrzymuje request i przekazuje do load balancera
  3. Load Balancer rozpoznaje, że ścieżka time/consuming/script/* wskazuje na skrypt spoza klastra.
  4. Load Balancer przekierowuje ruch poza klaster.
  5. Skrypt spoza klastra (który ma już ustawiony domyślnie wysoki timeout) wykonuje się przez 65 sekund.
  6. Load Balancer nie może dłużej czekać - rzuca błąd 504.
  7. Ingress Controller otrzymuje 504 od Load Balancera i zwraca błąd do klienta.

Najsensowniejszym rozwiązaniem wydaje się podbicie timeoutu na Load Balancerze, żeby zaczekał nieco dłużej i przekazał do ingressa rezultat, a nie ucinał requesta. No, ale niezupełnie. Jeśli ustawimy tam 75 sekund, Ingress Controller wciąż zwraca 504!

Szybki rzut oka do logów ingressa wskazuje winnego: 499 na Load Balancerze.

Ale zaraz… jak to możliwe?! Błąd HTTP 499 oznacza dosłownie client closed request. Jaki klient?! Przecież nasz, czerwony od złości klient sprzed komputera, choć niecierpliwy, czeka na wykonanie requesta ponad 60 sekund. Zgoda. Natomiast, jeśli rzucimy okiem na początkowy diagram zobaczymy, że tak naprawdę klientem Load Balancera jest… Ingress Controller. I to właśnie Ingress Controller jest “klientem”, który zamyka połączenie jeszcze przed jego zakończeniem.

Oto jak wygląda sytuacja w chwili obecnej:

  1. Klient wykonuje request do https://sample.com/time/consuming/script/abc123
  2. Ingress Controller otrzymuje request i przekazuje do load balancera
  3. Load Balancer rozpoznaje, że ścieżka time/consuming/script/* wskazuje na skrypt spoza klastra.
  4. Load Balancer przekierowuje ruch poza klaster.
  5. Skrypt spoza klastra (który ma już ustawiony domyślnie wysoki timeout) wykonuje się przez 65 sekund.
  6. Load Balancer czeka na wykonanie skryptu, bo ma podbity timeout.
  7. Ingress Controller nie może dłużej czekać, kończy request, zwraca 504, a w logach Load Balancera ląduje błąd: 499, połączenie zakończone przez klienta.

Mamy winnego, wysoki sądzie, można się rozejść.

Rozwiązanie?

Rozwiązanie jest proste - trzeba podnieść limit czasu żądania na ingress controllerze. Możemy to zrobić w definicji naszego ingressa na kubernetesie za pomocą anotacji przekazywanych później do nginksa. Z uwagi jednak na to, że zwiększony timeout wystawia naszą aplikację na niebezpieczeństwo niestabilności czy ataków DDoS, chcielibyśmy uniknąć sytuacji w której każda końcówka naszego API będzie miała wydłużony czas wykonania.

Domyślnie, uproszczona konfiguracja może wyglądać w następujący sposób:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sample-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- sample.com
- www.sample.com
rules:
- host: sample.com
http:
paths:
- backend:
service:
name: load-balancer-nginx
port:
number: 80
path: /
pathType: Prefix

To, co chcielibyśmy zrobić, to zapewnić, że tylko na ścieżce /time/consuming/script/* timeout będzie zwiększony. Nie możemy tego zrobić tą samą regułą, ponieważ zostałaby zaaplikowana do wszystkich zdefiniowanych w niej ścieżek, dlatego dodamy nową, która dokładnie opisze nasze potrzeby:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-timeout
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
spec:
tls:
- hosts:
- sample.com
- www.sample.com
rules:
- host: sample.com
http:
paths:
- backend:
service:
name: load-balancer-nginx
port:
number: 80
path: /time/consuming/script/
pathType: Prefix

To wszystko! Aplikujemy naszą konfigurację za pomocą kubectl i po chwili requesty, które trafiają na /time/consuming/script/ zostają wykonane ze zwiększonym limitem czasowym.

Mam nadzieję, że powyższy case uratuje Wam trochę czasu w zmaganiach z błędem 499 w przyszłości!


Tags

#fixit#kubernetes#devops

Share

Previous Article
Mój ulubiony typ: ten o którym nic nie wiem

Robert Duraj

Software Engineer

Related Posts

🗞 ☕ Prasówka #7 - O podróżach w czasie
July 19, 2024
1 min
© 2024, All Rights Reserved.
Powered By

Social Media

linkedingithubtwitter