Jak doprowadziliśmy wdrożenia do zera błędów 502
Za każdym razem, gdy wdrażaliśmy backend Mythex, kilka żądań kończyło się błędem 502 Bad Gateway. Niewiele, ale błąd 502 podczas wdrożenia oznacza, że czyjeś kliknięcie nic nie zrobiło albo build, który ktoś obserwował, na chwilę stanął. Doszliśmy do dwóch osobnych problemów: samonaprawiającego się watchdoga, który walczył z naszymi wdrożeniami, oraz luki między momentem, w którym serwer przestał nasłuchiwać, a momentem, w którym load balancer przestał wysyłać mu ruch. W trzech kolejnych wdrożeniach produkcyjnych po poprawkach liczba spadła z 10 do 6, a potem do 0.
Konfiguracja
Nasze API i orkiestrator działają jako kontenery na małej grupie serwerów za AWS Application Load Balancer (ALB). Wdrożenie aktualizuje jeden serwer naraz: wygasza stare kontenery, uruchamia nowe i przechodzi dalej. Watchdog na każdym serwerze sprawdza, czy kontenery działają, i restartuje każdy, który nie działa — jest po to, żeby proces, który padł o 3 w nocy, wrócił bez budzenia kogokolwiek.
Oba mechanizmy są rozsądne same w sobie. Razem już nie.
Problem 1: watchdog ścigał się z wdrożeniem
Watchdog działał raz na minutę i „naprawiał” po jednej nieudanej kontroli stanu. Wdrożenie celowo wygasza usługi przez do 150 sekund — w tym czasie kontrola stanu ma się nie udać. Więc watchdog reagował podczas każdego wdrożenia.
Większość razy przegrywał wyścig bez szkody. Raz wygrał: przepisał plik compose (opis tego, które kontenery mają działać) w środku wdrożenia i zostawił serwer bez żadnego kontenera bramy — podczas gdy zadanie wdrożeniowe zgłosiło sukces.
Poprawki:
- Jedna blokada. Wdrożenie bierze teraz tę samą blokadę plikową co skrypt bootstrapowy serwera, więc watchdog i wdrożenie nigdy nie działają naraz.
- Cztery strikes, nie jeden. Watchdog potrzebuje czterech kolejnych nieudanych kontroli, zanim zareaguje. Wygaszana usługa nie wygląda już na martwą.
- Jedna definicja. Plik compose ma teraz jedno źródło, publikowane na serwery, zamiast kopii, które mogły się rozjechać.
- Sprawdzaj, nie ufaj. Zadanie wdrożeniowe nie przechodzi już dlatego, że polecenie zwróciło kod. Sprawdza API, orkiestrator, bramę i worker publikacji na każdym serwerze, zanim może się udać.
Problem 2: serwer odszedł przed ruchem
Drugi problem był cichszy. Gdy wdrożenie wygasza serwer, serwer zaczyna odpowiadać na swój endpoint zdrowia kodem 503, żeby load balancer wyjął go z rotacji. Nasz robił to przez 5 sekund, a potem się wyłączał.
Ale ALB sprawdzał stan tylko co jakiś czas i potrzebował kilku nieudanych kontroli z rzędu, zanim przestał kierować ruch do celu — do około 60 sekund od początku do końca. Przez mniej więcej 55 sekund load balancer wciąż wysyłał prawdziwe żądania do serwera, który już przestał nasłuchiwać. To były nasze 502.
Rozwiązaniem było doprowadzenie obu osi czasu do zgodności:
- Grupa docelowa sprawdza teraz stan co 5 sekund i oznacza cel jako niezdrowy po 2 nieudanych kontrolach — około 10 sekund, żeby to zauważyć.
- Wygaszanie obsługuje prawdziwy ruch przez 15 sekund po tym, jak zaczyna nie przechodzić kontroli stanu, więc zawsze jest nakładanie się.
- Te ustawienia żyją w skrypcie, który stawia infrastrukturę, a nie w konsoli, w której ktoś kliknął raz.
Pilnowanie, żeby liczby trzymały się razem
Takie limity czasu zależą od siebie nawzajem. Wygaszanie musi trwać dłużej niż balancer potrzebuje, żeby zauważyć; wygaszanie plus zamknięcie musi się skończyć, zanim kontener zostanie zabity siłą. Łatwo, żeby ktoś zmienił jedną liczbę za pół roku i po cichu przywrócił błąd.
Dlatego spisaliśmy te zależności jako testy: wygaszanie musi być dłuższe niż interwał kontroli razy próg niepowodzeń; czas do ponownego uznania za zdrowy musi pozostać krótki; całe wygaszanie musi się mieścić w okresie łaski zatrzymania kontenera. Zmień jedną liczbę bez reszty, a CI się wywali. Przetestowaliśmy też same testy mutacjami — zmieniliśmy każdą liczbę na błędną wartość i sprawdziliśmy, czy test to wyłapie.
Mierzenie wdrożenia łatwo zrobić źle
Nasz pierwszy pomiar mówił, że czyste wdrożenie jest zepsute w około 3%. Nie było. Liczyliśmy każdą odpowiedź inną niż 200, a 503 z endpointu zdrowia podczas wygaszania nie jest błędem — to wygaszanie robi swoje, mówiąc balancerowi, żeby nie wybierał tego serwera, podczas gdy on dalej obsługuje prawdziwe żądania.
To, co czuje użytkownik, to 502: żądanie nie trafiło w nic. Liczenie tylko tych dało prawdziwy obraz — 10, potem 6, potem 0 w trzech kolejnych wdrożeniach produkcyjnych.
Co z tego wynieśliśmy
- Samonaprawianie musi wiedzieć o zaplanowanych przestojach. Watchdog, który nie potrafi odróżnić wdrożenia od awarii, prędzej czy później zacznie walczyć z wdrożeniem.
- Wygaszanie to uzgodnienie. Serwer musi dalej obsługiwać ruch, dopóki load balancer nie przestanie na pewno wysyłać ruchu, a ten czas ustawia się na balancerze, a nie na serwerze.
- Zapisuj niezmienniki jako testy. Liczby operacyjne się rozjeżdżają. Testy, które wiążą je ze sobą, nie.
- Ustal, co mierzysz, zanim zaczniesz mierzyć. „Błędy” były złą metryką; „żądania, które nie trafiły w nic” było właściwą.
Jeśli publikujesz aplikację w Mythex, nic z tego nie jest widoczne — i o to chodzi. Powiązane: jak Twoja opublikowana aplikacja pozostaje dostępna podczas ponownej publikacji.